CSP2019题解

T1 格雷码

认证前几天某odgd还说不考格雷码

然后就考了

考虑从高到低处理每一位,设当前处理到了第\(i\)位,待处理数字是\(j\)的状态为\(f(i,j)\)

  • \(j<2^{i-1}\),则输出0,处理\(f(i-1,j)\)

  • \(j\geqslant 2^{i-1}\),则输出1,将\(j\)的后\(i\)位取反,处理\(f(i-1,j)\)

另外,要注意1ull<<64unsigned long long的问题。

code:

#include<stdio.h>
int n;
char c=0;
unsigned long long val=0;
void dfs(int p,unsigned long long q){
	if(q<1ull<<p-1){
		printf("0");
	}else{
		printf("1");
		q-=1ull<<p-1;
		q=(1ull<<p-1)-q-1;
	}if(p-1>0)dfs(p-1,q);
}
int main(){
	scanf("%d",&n);
	while(c<'!')c=getchar();
	while(c>='!')val=val*10+(c-'0'),c=getchar();
	dfs(n,val);
}

T2 括号树

一道树上dp的好题不会栈的做法啊啊啊

\(t[i],s[i]\)

  • 若第\(i\)个括号为\()\),且在根结点到\(i\)号结点的简单路径上,存在节点\(j\),使得从\(j\)\(i\)按结点经过顺序依次排列组成的字符串为合法括号串,则\(t[i]\)为满足条件的\(j\)中深度最小的点,\(s[i]\)为满足条件的\(j\)的数量。

  • 若第\(i\)个括号为\()\),且在根结点到\(i\)号结点的简单路径上,不存在节点\(j\),使得从\(j\)\(i\)按结点经过顺序依次排列组成的字符串为合法括号串,则\(t[i]=s[i]=0\)

  • 若第\(i\)个括号为\((\),则\(t[i]=i,s[i]=0\)

转移:设当前讨论到了\(p\)号节点,需要讨论它的所有儿子

更新信息:若第\(i\)个括号为\()\),则\(t[p]\)需要一直往上跳到\(q\),使得\(q\)为满足从\(q\)\(p\)按结点经过顺序依次排列组成的字符串为合法括号串的\(q\)中深度最小的点,否则\(t[p]=p\)

  • 当儿子为\((\),则令\(t[son]=son,s[son]=0\)
  • 当儿子为\()\)
    • 当该节点为\((\),则\(t[son]=f[p]\)\(s[son]=s[f[p]]+1\)
    • 当该节点为\()\)
      • \(t[p]==1\)\(t[p]==0\),说明没有与儿子配对的\((\),则\(t[son]=s[son]=0\)
      • 否则说明有与儿子配对的\((\),则\(t[son]=f[p]\)\(s[son]=s[f[f[p]]]+1\)

code:

#include<cstdio>
int Last[500002],Next[500002],val[500002],f[500002];
int t[500002],g[500002],n,tp=0;
long long s[500002],ans=0;
char c;
void dfs(int p){
	if(!val[p])t[p]=p;
	else{
		if(!s[p])t[p]=0;
		else{
			if(val[f[t[p]]])t[p]=t[f[t[p]]];
		}
	}
	for(int i=Last[p];i;i=Next[i]){
		if(val[i]){
			if(!val[p])t[i]=p,s[i]=s[f[p]]+1;
			else{
				if(t[p]==0||t[p]==1)s[i]=0,t[i]=0;
				else t[i]=f[t[p]],s[i]=s[f[t[i]]]+1;
			}
		}dfs(i);
	}
}
void dfs1(int p){
	ans^=s[p]*p;
	for(int i=Last[p];i;i=Next[i]){
		s[i]+=s[p];
		dfs1(i);
	}
}
int main(){
	scanf("%d",&n);
	c=getchar();
	while(c<'!')c=getchar();
	while(c>='!')val[++tp]=c==')',c=getchar();
	for(int i=2;i<=n;i++){
		scanf("%d",&f[i]);
		Next[i]=Last[f[i]];Last[f[i]]=i;
	}dfs(1);dfs1(1);printf("%lld",ans);
}

T3 树上的数

就是乱搞贪*害我投了150分钟进去

思考一下每一个数字在树上移动的轨迹,设\(i\)移动轨迹为\(e_0,p_{i,0},e_{i,1},p_{i,2},e_{i,2},\dots,p_{i,n_i-1},e_{i,n_i-1},p_{i,n_i},e_0\)(前后补上\(e_0\)),容易发现以下几条规律:

  1. 对于每个节点\(u\),它在这些轨迹中出现次数为其度数\(+1\)
  2. 对于每一条边\(e_i(u,v)\),它在这些轨迹中出现次数为2,且一次为\(u,e_i,v\),一次为\(v,e_i,u\)
  3. 对于每个节点\(u\),不存在任意一个数字集合\(S\),使\(S\)中每一个数字的移动轨迹中与\(u\)相邻的边都出现过两次,且不同边的数量不为\(u\)的度数\(+1\)。(感性理解)

然后就有一个很暴力的思路:对每一个点\(u\)维护一个并查集,记录哪些边在已讨论过的路径中出现过\(e_i,u,e_j\)的情况,然后贪心,从\(1\text{~}n\)枚举每一个节点结束时可能的最小数字。

\(O(Tn^2\log n)\),相信\(\text{CCF}\)少爷机。

code:

#include<cstdio>
int Last[2002],Next[4002],End[4002],Len[4002],val[2002],mk[2002],rc[2002],T,n,f[2002],mp[2002][2002];
int ff[2002][2002],cntt[2002],ts[2002][2002],tb[2002][2002],ans[2002];
inline int gf(int p,int q){return ff[p][q]==q?q:(ff[p][q]=gf(p,ff[p][q]));}
void dfs(int p){
	int F=f[p],r=gf(p,F),k=gf(p,0);
	if(r!=k||ts[p][k]==cntt[p]||ts[p][k]>tb[p][k]+1)rc[p]=1;
	for(int i=Last[p];i;i=Next[i])if(End[i]!=f[p]&&!Len[i]){
		int s=gf(p,End[i]);f[End[i]]=p;
		if(s!=r||ts[p][s]==cntt[p]||ts[p][s]>tb[p][s]+1)dfs(End[i]);
	}
}
int main(){
	scanf("%d",&T);
	while(T--){
		scanf("%d",&n);
		for(int i=1;i<=n;i++){
			Last[i]=mk[i]=ans[i]=0;
			scanf("%d",&val[i]);cntt[i]=1;
		}
		for(int i=1;i<=n;i++){
			for(int j=0;j<=n;j++){
				ff[i][j]=j;
				ts[i][j]=1;
				tb[i][j]=0;
				mp[i][j]=0;
			}
		}
		for(int i=2;i<=n+n-2;i+=2){
			scanf("%d%d",&End[i+1],&End[i]);Len[i]=Len[i+1]=0;
			mp[End[i+1]][End[i]]=i;
			mp[End[i]][End[i+1]]=i+1;
			cntt[End[i]]++;cntt[End[i+1]]++;
			Next[i]=Last[End[i+1]];Last[End[i+1]]=i;
			Next[i+1]=Last[End[i]];Last[End[i]]=i+1;
		}for(int i=1;i<=n;i++){
			for(int j=1;j<=n;j++)rc[j]=0,f[j]=0;
			dfs(val[i]);
			for(int j=1;j<=n;j++){
				if(!mk[j]&&rc[j]&&val[i]!=j){
					ans[i]=j;mk[j]=1;
					int p=j,tmp=0,tmp2=0;
					while(1){
						if(tmp){
							int s=gf(tmp,p),t=gf(tmp,tmp2);
							ff[tmp][s]=t;
							ts[tmp][t]+=ts[tmp][s];
							tb[tmp][t]+=tb[tmp][s]+1;
						}
						Len[mp[f[p]][p]]=1;tmp2=tmp;tmp=p;p=f[p];
						if(!tmp)break;
					}break;
				}
			}
		}for(int i=1;i<=n;i++)printf("%d ",ans[i]);puts("");
	}
}

T4 Emiya 家今天的饭

看到Yazid就想起这到题,还都是dp

首先,很容易就能想到用合法的方案数减去非法的方案数。

\(f[t][i][j][k]\)表示当前讨论到第\(t\)种物品,第\(i\)种方法,一共用了\(j\)种方法,其中有\(k\)种方法用的是物品\(t\)

然后可以滚掉\(t\)这一维,得到\(f[i][j][k]\)\(O(n^3m)\)

还可以合并\(j,k\)两维,设\(f[i][j]\)表示当前讨论到第\(i\)种方法,用的方法数\(-\)用的物品\(t\)\(\times 2=j\)

\(f[i][j]=f[i-1][j]+f[i-1][j-1]*(sum[i]-a[i][t])+f[i-1][j+1]*a[i][t]\)

code:

#include<cstdio>
#include<memory.h>
#define inf 998244353
int a[102][2002],n,m,ans=1;
int dp[102][222],s[102];
int main(){
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++){
		s[i]=0;
		for(int j=1;j<=m;j++){
			scanf("%d",&a[i][j]);
			s[i]+=a[i][j];
			if(s[i]>=inf)s[i]-=inf;
		}ans=1ll*ans*(s[i]+1)%inf;
	}ans+=inf-1;
	if(ans>=inf)ans-=inf;
	for(int t=1;t<=m;t++){
		memset(dp,0,sizeof(dp));
		dp[0][n+1]=1;//n+1+a-b*2
		for(int i=1;i<=n;i++){
			for(int j=n+1-i;j<=n+1+i;j++){
				dp[i][j]=(1ll*dp[i-1][j-1]*(s[i]+inf-a[i][t])+1ll*dp[i-1][j+1]*a[i][t]+dp[i-1][j])%inf;
			}
		}for(int i=n;i>=1;i--){
			ans+=inf-dp[n][i];
			while(ans>=inf)ans-=inf;
		}
	}printf("%d ",ans);
}

T5 划分

盲猜结论题

先前缀和一遍,再设\(t[i]\)为前\(i\)个数的最优划分中倒数第二段的最后一个位置。

维护一个单调栈,保证里面的元素\(p\)满足\(s[p]\times 2-s[t[p]]\)单调不降,再在上面维护一个指针,指针只往右移。

每次找到栈里最大的满足\(s[i]>=s[p]\times 2-s[t[p]]\)的数,即为\(t[i]\)。(我也很迷)

再插入\(i\),总时间\(O(n)\)

吐槽高精慢死了

code:

#include<cstdio>
#include<deque>
#include<bits/stdc++.h>
__int128 ans=0,e=1;
int t[40000002],n,T;
long long s[40000002];
int S[40000002],g[40000002],tp=0,pos=0;
int main(){
	scanf("%d%d",&n,&T);
	if(!T)for(int i=1;i<=n;i++)scanf("%lld",&s[i]),s[i]+=s[i-1];
	else{
		long long x,y,z,m,lj=0;
		scanf("%lld%lld%lld%lld%lld%lld",&x,&y,&z,&s[1],&s[2],&m);
		for(int i=3;i<=n;i++)s[i]=(x*s[i-1]+y*s[i-2]+z)&1073741823ll;
		while(m--){
			scanf("%lld%lld%lld",&x,&y,&z);
			for(int i=lj+1;i<=x;i++)s[i]=s[i]%(z-y+1)+y;
			lj=x;
		}for(int i=1;i<=n;i++)s[i]+=s[i-1];
	}S[++tp]=0;
	for(int i=1;i<=n;i++){
		while(pos<tp&&s[i]-s[S[pos+1]]>=s[S[pos+1]]-s[t[S[pos+1]]])pos++;
		t[i]=S[pos];g[i]=g[t[i]]+1;
		while(tp&&s[S[tp]]+s[S[tp]]-s[t[S[tp]]]>=s[i]+s[i]-s[t[i]])tp--;
		S[++tp]=i;
	}for(int i=n;i;i=t[i]){
		ans+=e*(s[i]-s[t[i]])*(s[i]-s[t[i]]);
	}if(ans<1e18){
		long long k=ans;
		printf("%lld",k);
	}else{
		long long k1=ans/1000000000000000000,k2=ans%1000000000000000000;
		printf("%lld%18lld",k1,k2);
	}
}

T6 树的重心

CCF非专业级植树能力认证

先以1号点为根把树提起来,然后对每个节点,统计它对答案的贡献。

\(i\)的最大儿子为\(s[i][0]\),次大为\(s[i][1]\),以\(i\)为根的子树大小为\(siz[i]\)

设切掉的边两端更低的一个点为\(u\),当前讨论到节点\(p\)

\(u\)\(p\)的祖先(含\(p\)),则\(p\)为重心当且仅当\(2\times siz[s[p][0]]\leqslant s[u]\leqslant 2\times s[p]\)

\(u\)\(p\)的最大儿子的子树中,则\(p\)为重心当且仅当\(\max(siz[s[p][1]],siz[s[p][0]]-siz[u],siz[1]-siz[p])\times 2\leqslant siz[1]-siz[u]\)

\(u\)\(p\)的其它儿子的子树中,则\(p\)为重心当且仅当\(\max(siz[s[p][0]],siz[1]-siz[p])\times 2\leqslant siz[1]-siz[u]\)

否则,\(p\)为重心当且仅当\(max(siz[s[p][0]],siz[1]-siz[p]-siz[u])\times 2\leqslant siz[1]-siz[u]\) *这种情况可以用非子树减去祖先来求

化简一下,用主席树维护子树,用倍增维护一下祖先。

code:

#include<cstdio>
int Last[300002],Next[600002],End[600002],f[300002][22];
int t[300002],h[300002],s[300002],ss[300002][2],tot,T,n;
long long d[300002],ans;
inline int Max(int a,int b){return a>b?a:b;}
struct node{
	node *ls,*rs;
	int cnt;
}tr[8000002],*null=tr,*top,*rt[300002];
void init(){
	null->ls=null->rs=null;
	null->cnt=0;rt[0]=null;
}
node *addnode(){
	node *p=++top;
	p->ls=p->rs=null;
	p->cnt=0;
	return p;
}
void add(int p,int l,int r,node *t,node *lt){
	t->ls=lt->ls;t->rs=lt->rs;t->cnt=lt->cnt+1;
	if(l==r)return;
	if(l+r>>1>=p)add(p,l,l+r>>1,t->ls=addnode(),lt->ls);
	else add(p,l+r+2>>1,r,t->rs=addnode(),lt->rs);
}
int get(int L,int R,int l,int r,node *lt,node *t){
	if(L<=l&&r<=R)return t->cnt-lt->cnt;
	if(L>R||lt==t)return 0;
	if(L<1)L=1;if(R>n)R=n;
	long long ans=0;
	if(l+r>>1>=L)ans+=get(L,R,l,l+r>>1,lt->ls,t->ls);
	if(l+r>>1<R)ans+=get(L,R,l+r+2>>1,r,lt->rs,t->rs);
	return ans;
}
void dfs1(int p,int F){
	d[p]=d[F]+1;
	f[p][0]=F;
	s[p]=1;h[p]=0;
	ss[p][0]=ss[p][1]=0;
	for(int i=1;i<=19;i++)f[p][i]=f[f[p][i-1]][i-1];
	for(int i=Last[p];i;i=Next[i])if(End[i]!=F){
		dfs1(End[i],p);s[p]+=s[End[i]];
		if(s[End[i]]>s[h[p]])h[p]=End[i];
		if(s[End[i]]>ss[p][1])ss[p][1]=s[End[i]];
		if(ss[p][1]>ss[p][0])ss[p][1]^=ss[p][0]^=ss[p][1]^=ss[p][0];
	}
}
void dfs2(int p){
	t[p]=++tot;
	add(s[p],1,n,rt[t[p]]=addnode(),rt[t[p]-1]);
	if(h[p])dfs2(h[p]);
	for(int i=Last[p];i;i=Next[i])if(End[i]!=f[p][0]&&End[i]!=h[p])dfs2(End[i]);
	if(p!=1){
		int l=p,r=p,lb=2*s[p],rb=2*ss[p][0];
		for(int i=19;i>=0;i--){
			if(f[l][i]&&s[f[l][i]]<=lb)l=f[l][i];
			if(f[r][i]&&s[f[r][i]]<rb)r=f[r][i];
		}if(s[r]<rb&&r!=1)r=f[r][0];
		if(s[l]<=lb&&l!=1)l=f[l][0];
		ans+=(d[r]-d[l])*p;
	}
}
void dfs3(int p){
	for(int i=Last[p];i;i=Next[i])if(End[i]!=f[p][0])dfs3(End[i]);
	if(h[p]){
		ans+=1ll*p*get(1,s[1]-2*Max(ss[p][0],s[1]-s[p]),1,n,rt[t[h[p]]+s[h[p]]-1],rt[t[p]+s[p]-1]);
		ans+=1ll*p*get(2*ss[p][0]-s[1],s[1]-2*Max(ss[p][1],s[1]-s[p]),1,n,rt[t[p]],rt[t[h[p]]+s[h[p]]-1]);
	}if(p!=1){
		ans+=1ll*p*get(s[1]-2*s[p],s[1]-2*ss[p][0],1,n,rt[0],rt[t[p]-1]);
		ans+=1ll*p*get(s[1]-2*s[p],s[1]-2*ss[p][0],1,n,rt[t[p]+s[p]-1],rt[n]);
		int l=f[p][0],r=f[p][0],lb=s[1]-2*ss[p][0],rb=s[1]-2*s[p];
		for(int i=19;i>=0;i--){
			if(f[l][i]&&s[f[l][i]]<=lb)l=f[l][i];
			if(f[r][i]&&s[f[r][i]]<rb)r=f[r][i];
		}if(s[r]<rb)r=f[r][0];
		if(s[l]<=lb)l=f[l][0];
		ans-=(d[r]-d[l])*p;
	}
}
int main(){
	init();
	scanf("%d",&T);
	while(T--){
		scanf("%d",&n);top=tr;d[0]=0;ans=0;tot=0;
		for(int i=1;i<=n;i++)Last[i]=0;
		for(int i=1;i<n+n-2;i+=2){
			scanf("%d%d",&End[i+1],&End[i]);
			Next[i]=Last[End[i+1]];Last[End[i+1]]=i;
			Next[i+1]=Last[End[i]];Last[End[i]]=i+1;
		}dfs1(1,0);dfs2(1);dfs3(1);printf("%lld\n",ans);
	}
}

题出的好!难度适中,覆盖知识点广,题目又着切合实际的背景,解法比较自然。给出题人点赞 !

posted @ 2019-11-18 22:11  ztc…  阅读(321)  评论(0编辑  收藏  举报

Please contact lydsy2012@163.com!