• CSP2019题解


    T1 格雷码

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

    然后就考了

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

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

    • (jgeqslant 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 ext{~}n)枚举每一个节点结束时可能的最小数字。

    (O(Tn^2log n)),相信( ext{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)( imes 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] imes 2-s[t[p]])单调不降,再在上面维护一个指针,指针只往右移。

    每次找到栈里最大的满足(s[i]>=s[p] imes 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 imes siz[s[p][0]]leqslant s[u]leqslant 2 imes s[p])

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

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

    否则,(p)为重心当且仅当(max(siz[s[p][0]],siz[1]-siz[p]-siz[u]) imes 2leqslant 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
    ",ans);
    	}
    }
    

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

  • 相关阅读:
    java图片裁剪原理
    代码整洁之道
    vue.js devtools安装
    Convert DateTime To Varchar with multiple Styles
    Sql Server为数值变量添加删除前导后缀(翻译)
    asp.net ckeditor 3.6.2 + ckfinder 2.1 上传图片
    转摘 IE6 动态创建 iframe 无法显示的 bug
    sql server output parameter
    windows7 无法删除文件夹 提示需要SYSTEM权限
    Kooboo 全文索引研究
  • 原文地址:https://www.cnblogs.com/ztc03/p/csp2019.html
Copyright © 2020-2023  润新知