zoukankan      html  css  js  c++  java
  • 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);
    	}
    }
    

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

  • 相关阅读:
    Elementary Methods in Number Theory Exercise 1.2.25
    Elementary Methods in Number Theory Exercise 1.2.14
    图解欧几里德算法
    图解欧几里德算法
    Elementary Methods in Number Theory Exercise 1.2.14
    Android中的长度单位详解(dp、sp、px、in、pt、mm)
    分享下多年积累的对JAVA程序员成长之路的总结
    android异常之都是deamon惹的祸The connection to adb is down, and a severe error has occured.
    TomatoCartv1.1.8.2部署时报错
    JavaScript浏览器对象之二Document对象
  • 原文地址:https://www.cnblogs.com/ztc03/p/csp2019.html
Copyright © 2011-2022 走看看