初三赛季杂题泛做(9-10月)

发布时间 2023-07-12 17:22:17作者: Anticipator

CF1580 div1

A:

傻逼题,一开始没想出来,冷静了一会发现可以枚举两行,中间记录个前缀后缀最小值就行了

B:

考虑dp,设f[n][m][k]为答案,发现枚举最大数的位置可以做,于是就做了

N^5tm能过

C:

想了一会,然后5ab说可以根号分治,我想了想好像真可以,于是就写了。

x+y大于sqrtn的直接开大桶算贡献,反正不会T

x+y小于sqrtn的,因为只有sqrt种x+y,直接对于每种开一个余数的小桶记录,修改的时候暴力修改,反正不会T

D:

只要会dicker树的人一般都会,可惜我不会

建出dicker树,考虑把答案转成这样:

$ \sum_{i=1}{n}\sum_{j=1} a_i+a_j-2\times a_{lca(i,j)}$

这东西很像距离,于是w(i,j)=ai-aj,这东西就是距离了

于是变成要求树上选m个点,两两距离和最大

这东西转成贡献,上树形dp就行了

f[i][j]表示考虑了i子树内选出j个点,边的贡献和最大是多少

$f[u][j]=\max(f[v][k]+f[u][j-k]+Wv×j×(M-j))$

O(N^2)

#include<bits/stdc++.h>
using namespace std;
int n,m;
const int N=4005;
int a[N],ls[N],rs[N],sz[N];
int st[N],tot;
typedef long long ll;
ll lw[N],rw[N],f[N][N];
void dfs(int u){
	int i,j;sz[u]=1;
	if(ls[u]){
		dfs(ls[u]);
		for(i=min(sz[u],m);i>=0;i--)
			for(j=min(sz[ls[u]],m);j>=0;j--)
				f[u][i+j]=max(f[u][i+j],f[ls[u]][j]+lw[u]*j*(m-j));
		sz[u]+=sz[ls[u]];
	}
	if(rs[u]){
		dfs(rs[u]);
		for(i=min(sz[u],m);i>=0;i--)
			for(j=min(sz[rs[u]],m);j>=0;j--)
				f[u][i+j]=max(f[u][i+j],f[u][i]+f[rs[u]][j]+rw[u]*j*(m-j));
		sz[u]+=sz[rs[u]];
	}
}
int main(){
	int i;
	cin>>n>>m;
	for(i=1;i<=n;i++){
		scanf("%d",&a[i]);
		while(tot>0&&a[st[tot]]>a[i])ls[i]=st[tot],lw[i]=a[st[tot]]-a[i],tot--;
		if(tot)rs[st[tot]]=i,rw[st[tot]]=a[i]-a[st[tot]];
		st[++tot]=i;
	}
	dfs(st[1]);
	cout<<f[st[1]][m];
	return 0;
}

接下来是更重要的反思:

这场比赛前4题都是力所能及的,zky90分钟就全过了

然而由于状态原因(累、太不激动),脑子不是很活跃。

排列的问题大多可以通过枚举最大值解决,但我忘了。

根号分治没想到可以把<sqrtn的直接暴力开桶

。。。

以后一定要早睡,给第二天一个好状态!

不要思维定势!!!

UOJ Round #22

估计是AFO前第一场也是最后一场UOJ了

A是个傻逼题,卡2log,煞笔煞笔煞笔

B、C完全不会

反思:过了大样例也要思考如何卡常!!!

LG 10月月赛I

C 构造题,一如既往地不会!!!

D 根本没来得及仔细想,也没透彻理解算本质不同子序列的性质!!!

反思:多刷构造题,数学构造都是解方程!

每道题都是可做的!要迎难而上!

LG 10月月赛II

C是个傻逼,但可惜我没发现单调性,于是只会90

D是个傻逼,经过CXY提醒后过了

反思:像C这种细节巨大的ds题,考场上直接交暴力!

CF 1592div2

ABC没看,是5ab写的

D也没想到dfs序的做法,搞了个子树大小,但既然数据水就算了。。。

E嘛,反正我只会每一位拆开算,懒得写。具体说就是枚举到一个位,区间长度是偶数并且与起来全是1,然后前面几位只能全是0(如果都是1那长度就是奇数了不符)所以,与起来为0且有偶数个1(前缀异或值相同)

F1,一直以为是DP,没想到贪心做法这么巧妙,给出题人点赞!Orz 9999!%%%

F2,巧妙的建二分图,Orz 9999!%%%

反思:树上二分考虑dfs序,这是好的

时间来不及了就不要写ds题!(和9999一样)

其实不需要啥事都往dp想,百、千级别的数据范围还可以是二分图!

实在不会做了或者太难写了就跳题罢!

NC 赛前训练营 4 T3

从小到大考虑选取第j个可以被贿赂的人,F[i][j]表示已经考虑了前i个人并且树上从根到i的路径上被取人的状态是j的方案数。组合数转移!

考虑n很小,爆搜出可能的序列,也很少,于是直接暴力带入序列进行DP就行了!!!

多做做鼠穴DP题

从小到大考虑dp,可以方便很多

CF EDU div2 115

E是个傻逼题,我不到10分钟就想到了,但因为没有选择比较好的实现方式,考试结束都没调完。

F也是个傻逼题,n很小,可以考虑状压dp,把括号序列转成1,-1的序列,再记录每个串的和,和前缀最小值的位置。直接在vector里面二分即可。

最近怎么看到DP就不会啊,还是要多做做DP


最近喜欢做一些似是而非的神必ds题

首先观察到答案就是按Bi排序的逆序对数

答案可以这么计算:

$\sum_{i=1}^k \sum_{j=i+1}^k f(S[b[i]],S[b[j]])$

$ 其中 S[i] 表示权值为i的位置集合,f(A,B)代表B中每个数在A中贡献逆序对数之和(类似归并)$

因为交换的是相邻的b[i],所以对答案的影响就是$2f(S[b[i]],S[b[i+1]])-c[b[i]]\times c[b[i+1]]$

考虑n是1e5级别的,同时这柿子也没啥性质,于是考虑根号分治。

考虑询问(b[x],b[x+1])时,分成两种情况:

  • $\max(cnt[b[x]],cnt[b[x+1]]) \le \sqrt n$
    :此时直接暴力双指针O(sqrt N)算贡献
  • $\max(cnt[b[x]],cnt[b[x+1]]) > \sqrt n$ 这样的b[x]不超过$\sqrt n $个(称为关键点),所以离线在每个关键点上O(n)处理每个包含此关键点的询问的答案
#include<bits/stdc++.h>
using namespace std;
int n,k,m;
const int N=100005,M=1000005;
int a[M],b[M];
#define lim 100  
int Q[M][2];
vector<int> g[M];
#define P pair<int,int>
#define fi first
#define se second
vector<P > q[M];
typedef long long ll;
ll c[M],s[M],w[M],u[M];
#define lowbit(x) x&-x
void add(int x){
	while(x<=k){
		u[x]++;
		x+=lowbit(x);
	}
}
ll sum(int x){
	ll res=0;
	while(x){
		res+=1ll*u[x];
		x-=lowbit(x);
	}
	return res;
}
inline ll solve(int x,int y){
	register int i;int p=1;
	ll ans=0;
	for(i=1;i<=g[x].size();++i){
		while(p<=c[y]&&g[y][p-1]<g[x][i-1])p++; 
		ans+=1ll*(p-1);
	}
	return ans;
}
inline void sol(int x){
	int tot=0;register int i;
	memset(w,0,sizeof(w));
	for(i=n;i>=1;--i){
		if(a[i]==x)tot++;
		else w[a[i]]+=1ll*tot;
	}
	for(i=0;i<q[x].size();++i){
		int to=q[x][i].fi,id=q[x][i].se;
		s[id]=w[to];
	}
}
inline int read(){
	char c=getchar();int O=0;
	while(c>'9'||c<'0')c=getchar();
	while(c>='0'&&c<='9')O=(O<<1)+(O<<3)+(c^48),c=getchar();
	return O;
}
int main(){
	register int i;
	n=read(),k=read(),m=read();
	for(i=1;i<=n;++i)a[i]=read(),g[a[i]].push_back(i),c[a[i]]++;
	for(i=n;i>=1;--i)s[0]+=sum(a[i]-1),add(a[i]);
	for(i=1;i<=k;++i)b[i]=i;
	for(i=1;i<=m;++i){
		int p=read();swap(b[p],b[p+1]);
		if(max(c[b[p]],c[b[p+1]])<=lim)s[i]=solve(b[p+1],b[p]);					
		else{
			if(c[b[p+1]]>c[b[p]])q[b[p+1]].push_back(make_pair(b[p],i));
			else q[b[p]].push_back(make_pair(b[p+1],i));
		}
		Q[i][0]=b[p+1],Q[i][1]=b[p];
	}
	for(i=1;i<=k;++i)if(c[i]>lim)sol(i);
	for(i=1;i<=m;++i){//求XI[x,y] when c[x],c[y]>sqrt(n) 
		int x=Q[i][0],y=Q[i][1];
		if(max(c[x],c[y])<=lim)s[i]=s[i-1]-s[i]*2+c[x]*c[y];
		else{
			if(c[x]<=c[y])s[i]=s[i-1]+s[i]*2-c[x]*c[y];
			else s[i]=s[i-1]-s[i]*2+c[x]*c[y];
		}
		printf("%lld\n",s[i]);
	}
	return 0;
}

[CCO2021]SWAP SWAP SORT


一开始写的是错解却过了

看到最小化极差想到双指针,毕竟已经被ZJOI搞过一次了。

每次枚举选进的点,看只选这些点和最短路上的边,能不能从1走到n,BFS就行了。

#include<bits/stdc++.h>
using namespace std;
const int N=100005;
int n,m;
typedef long long ll;
int cnt,h[N],nxt[N],to[N];
ll val[N],a[N];
void add(int x,int y,ll z){
	nxt[++cnt]=h[x];
	h[x]=cnt;
	to[cnt]=y,val[cnt]=z;
}
queue<int> q;
struct node{
	int id;ll v;
	bool operator < (const node &w)const{
		return v>w.v;
	}
};
priority_queue<node> Q;
int vis[N];
ll dis[N];
int ok[N],bl[N];
int cmp(int x,int y){
	return a[x]<a[y];
}
void dij(){
	for(int i=2;i<=n;i++)dis[i]=1e18;
	Q.push((node){1,0});
	while(!Q.empty()){
		node u=Q.top();Q.pop();
		for(int i=h[u.id];i;i=nxt[i]){
			int v=to[i];ll w=val[i];
			if(dis[v]>dis[u.id]+w){
				dis[v]=dis[u.id]+w;
				Q.push((node){v,dis[v]}); 
			}
		}
	}
}
int bfs(){
	while(!q.empty())q.pop();
	memset(vis,0,sizeof(vis));
	if(!ok[1]||!ok[n]){return 0;}
	q.push(1),vis[1]=1;
	while(!q.empty()){
		int x=q.front();q.pop();
		if(n==x){return 1;}
		for(int i=h[x];i;i=nxt[i]){
			int v=to[i];ll w=val[i];
			if(ok[v]&&dis[v]==dis[x]+w&&!vis[v]){
				vis[v]=1;
				q.push(v);
			}
		} 
	}
	return 0;
}
int main(){
	int i;
	cin>>n>>m;
	for(i=1;i<=n;i++)scanf("%lld",&a[i]),bl[i]=i;
	sort(bl+1,bl+n+1,cmp);
	for(i=1;i<=m;i++){
		int x,y;ll z;
		scanf("%d%d%lld",&x,&y,&z);
		add(x,y,z),add(y,x,z);
	}
	dij();
	cout<<dis[n]<<endl;
	int r=0;
	ll ans=2e18;
	for(i=1;i<=n;i++){
		r=max(r,i-1);
		int flag=0;
		while(r<=n){
			if(bfs()){
				flag=1;
				break;
			}
			ok[bl[++r]]=1;
		}
		if(flag)ans=min(ans,a[bl[r]]-a[bl[i]]);
		ok[bl[i]]=0;
	}
	cout<<ans<<endl;
	return 0;
}

题目叫不想爬坡


rnm,终于过了,不开O2荣登最劣解

建立因数trie

显然每次贪心往自己最小的因数走,不超过log步

用set维护一下子树的不同质因子个数(每个数最多8个)和儿子的倍数就行了

O(8nlogn^2),还用了一堆set,比cxy劣了128倍

#include<bits/stdc++.h>
using namespace std;
#define ri register int
const int N=1e5+5;
int n;
struct st{int x,id;}p[N];
int cmp(const st &a,const st &b){return a.x < b.x;}
int ok=1,fa[N];
namespace GTR {
const int bufl=1<<15;char buf[bufl],*s=buf,*t=buf;
inline int fetch(){if(s==t){t=(s=buf)+fread(buf,1,bufl,stdin);if(s==t)return EOF;}return *s++;}
inline int read(){int a=0,b=1,c=fetch();while(!isdigit(c))b^=c=='-',c=fetch();while(isdigit(c))a=(a<<1)+(a<<3)+(c^48),c=fetch();return b?a:-a;}
inline void write(int x){if(x>9)write(x/10);putchar(x%10+48);}}
using GTR::read;using GTR::write;
multiset<int> g[N],G[N]; 
const int M=1000000; 
int tot,pr[M+5],vis[M+5],mn[M+5],f[N];
int cnt,h[M],nxt[M*3],to[M*3];
inline void add(int x,int y){
	nxt[++cnt]=h[x];
	h[x]=cnt;
	to[cnt]=y;
}
void xs(int mx){
	ri i,j; 
	for(i=2;i<=mx;++i){
		if(!vis[i]){
			pr[++tot]=i;
			mn[i]=i;
		}
		for(j=1;pr[j]<=mx/i&&j<=tot;++j){
			mn[i*pr[j]]=min(mn[i],pr[j]);
			vis[i*pr[j]]=1;
			if(i%pr[j]==0)break;
		}
	}
}
void pan(int cur,int ban,int x){
	for(ri j=h[x];j;j=nxt[j])
		if(g[cur].count(to[j])>G[ban].count(to[j])){
			ok=0;
			return ;
		}
}
int mx;
#define P pair<int,int>
set<P > s[N];
void upd(int cur,int tar){
	while(ok){
		int fl=0;P w=make_pair(p[tar].x,0),x;
		if(p[cur].x==p[tar].x){
			fa[p[tar].id]=p[cur].id;
			return ;
		}
		if(s[cur].lower_bound(w)!=s[cur].end()){
			x=*s[cur].lower_bound(w);
			if(x.first==p[tar].x)pan(cur,x.second,p[tar].x/p[cur].x),fl=1,cur=x.second;
		}
		if (!fl){
			pan(cur,0,p[tar].x/p[cur].x);
			fa[p[tar].id]=p[cur].id; 
			f[tar]=cur;
			for(ri i=p[tar].x;i<=mx;i+=p[tar].x)s[cur].insert(make_pair(i,tar));
			int pos=tar;
			while(pos){
				for(ri i=h[p[tar].x/p[pos].x];i;i=nxt[i])g[pos].insert(to[i]);
				if(f[pos])for(ri i=h[p[tar].x/p[f[pos]].x];i;i=nxt[i])G[pos].insert(to[i]); 
				pos=f[pos];
			}
			return;
		}
	}
}
int main(){
	ri i,j;
	n=read();
	for(i=1;i<=n;++i)p[i]=(st){read(),i},mx=max(mx,p[i].x);
	sort(p+1,p+n+1,cmp);
	xs(mx);
	for(i=2;i<=mx;++i){
		int w=i,ls=0;
		while(w>1){
			if(mn[w]!=ls){
				add(i,mn[w]);
				ls=mn[w];
			}
			w/=mn[w];
		} 
	}
	for(i=2;i<=n&&ok;++i){
		if(p[i].x%p[1].x){puts("-1");return 0;}
		upd(1,i);
	}
	if(ok==0)puts("-1");
	else for(i=1;i<=n;++i)write(fa[i]),putchar(' ');
	return 0;
}

这题叫GCD TREE


NC周赛TG28题解

T1 傻逼期望题,不用想复杂,考虑相邻边对自己的贡献即可

T2 好题:

考虑把指数拆开,$A^p= (Ap-A)+(A{p-1}-A)+...+(A-1)+(1-0)$ 因为柿子中每一项都比后一项大,所以如果把每项的价值定为1,重量定位自己的话,肯定是从小往大取到某一项,等价于在所有次幂中选择一个,价值是幂。

因为wi很大,所以我们考虑用答案去拼出wi,因为要求最大,所以相当于处理刚好x个能拼出的最小数(容易发现x是$\sqrt n$ 级别的),所以考虑取前x个最小的就行了。于是就从小到大枚举$d$,看d能被加入几次,不断减掉就行了。

T3 cxy的神题

我想了一年都不会啊。

首先找到 [l1, r1] 中的最大值,设其为 x,然后在 [l2, r2] 中找有没有合法的配对,如果有,直接
return,否则说明 [l2, r2] 中没有值在 [x/2, x − 1] 中的数。
然后去找到 [l2, r2] 中小于x/2的最大值,设其为 y,然后在 [l1, r1] 里找有没有合法的配对,如果
有,return,否则说明 [l1, r1] 中没有值在 [y + 1, 2 × y − 1] 内的数。
然后再在 [l1, r1] 中找到小于 y + 1 的最大数,如此这样循环往复,仍然是 log v 次枚举后排除所有
值域。
在 [l, r] 中找值域在 [L, R] 的数中的最大值可以用主席树求解。