zoukankan      html  css  js  c++  java
  • Codeforces Round #596 Div1 A~E题解

    我好菜啊

    A

    题意:
    定义p-二进制数为2^k-p,给出n和p,求用最小个数的p-二进制数来表示n
    1<=n<=10^9,-1000<=p<=1000

    题解:
    猜结论,答案不会很大
    n可以表示成kp+s的形式,枚举k,判断(n-kp)是否能用k个2的幂构成
    画一下图可以发现,如果可以构成,那么满足(n-kp)的位数<=k<=n-kp
    (相当于把一颗二叉树上一个点变成两个)
    证明答案不会很大:
    首先(n-kp)的位数最多为30
    ①p>=0
    显然当k超过30后,如果不满足则之后也不满足
    ②p<0
    那么n-kp显然大于等于k

    code

    #include <algorithm>
    #include <iostream>
    #include <cstdlib>
    #include <cstring>
    #include <cstdio>
    #define fo(a,b,c) for (a=b; a<=c; a++)
    #define fd(a,b,c) for (a=b; a>=c; a--)
    using namespace std;
    
    long long n,s;
    int p,i,j,k,l;
    
    bool pd(long long t)
    {
    	long long T=t;
    	int sum=0;
    	
    	while (T)
    	{
    		sum+=T&1;
    		T>>=1;
    	}
    	
    	if (s>=sum && s<=t)
    	return 1;
    	else
    	return 0;
    }
    
    int main()
    {
    //	freopen("a.in","r",stdin);
    	
    	scanf("%I64d%d",&n,&p);
    	
    	while (!pd(n))
    	{
    		n-=p;
    		++s;
    		
    		if (n<0 ||s>100)
    		break;
    	}
    	
    	if (n>=0 && pd(n))
    	printf("%I64d
    ",s);
    	else
    	printf("-1
    ");
    }
    

    B

    题意:
    给出n个数和k,求(i,j)的个数(i<j),使得ai*aj=x^k
    n<=10^5,2<=k<=100

    题解:
    能表示成x的k次方,意味着乘积质因数分解后每一位的指数%k=0
    也就是(ai中p的指数+aj中p的指数)%k=0,把i或j的指数取负,变成求ai和aj质因数指数相等的(i,j)个数
    可以排序搞,根据k讨论一下大质数

    code

    #include <algorithm>
    #include <iostream>
    #include <cstdlib>
    #include <cstring>
    #include <cstdio>
    #include <cmath>
    #define fo(a,b,c) for (a=b; a<=c; a++)
    #define fd(a,b,c) for (a=b; a>=c; a--)
    #define min(a,b) (a<b?a:b)
    using namespace std;
    
    int a[200001];
    int p[200001][66];
    int P[317];
    int c[200001];
    int N,n,K,i,j,k,l,s,len,ls,s1,s2;
    long long ans;
    bool bz;
    
    bool cmp(int a,int b)
    {
    	int i;
    	
    	fo(i,1,65)
    	if (p[a][i]<p[b][i])
    	return 1;
    	else
    	if (p[a][i]>p[b][i])
    	return 0;
    	
    	return 0;
    }
    
    bool pd(int t)
    {
    	int i;
    	
    	fo(i,1,65)
    	if (p[a[t]][i]!=p[a[t+1]][i])
    	return 0;
    	return 1;
    }
    
    bool Cmp(int x,int y)
    {
    	return p[x][0]<p[y][0];
    }
    
    int main()
    {
    //	freopen("b.in","r",stdin);
    	
    	fo(i,2,316)
    	{
    		k=1;
    		
    		fo(j,2,floor(sqrt(i)))
    		if (!(i%j))
    		{
    			k=0;
    			break;
    		}
    		
    		if (k)
    		P[++len]=i;
    	}
    	
    	scanf("%d%d",&n,&K);
    	fo(i,1,n)
    	{
    		scanf("%d",&l);
    		fo(j,1,len)
    		if (!(l%P[j]))
    		{
    			while (!(l%P[j]))
    			{
    				l/=P[j];
    				++p[i][j];
    			}
    			p[i][j]%=K;
    			p[i+n][j]=(K-p[i][j])%K;
    		}
    		
    		if (l>1)
    		{
    			p[i][0]=l;
    			p[i+n][0]=l;
    		}
    	}
    	
    	N=n+n;
    	fo(i,1,N)
    	a[i]=i;
    	
    //	fo(i,1,N)
    //	{
    //		fo(j,0,5)
    //		cout<<p[i][j]<<" ";
    //		cout<<endl;
    //	}
    	
    	sort(a+1,a+N+1,cmp);
    	
    //	fo(i,1,N)
    //	cout<<a[i]<<endl;
    //	fo(i,1,N)
    //	{
    //		fo(j,0,5)
    //		cout<<p[a[i]][j]<<" ";
    //		cout<<endl;
    //	}
    	
    	ls=1;
    	fo(i,1,N)
    	if (i==N || !pd(i))
    	{
    		l=0;
    		fo(j,ls,i)
    		c[++l]=a[j];
    		
    		sort(c+1,c+l+1,Cmp);
    		
    		if (K>=3)
    		{
    			s1=0;s2=0;
    			
    			fo(j,1,l)
    			if (!p[c[j]][0])
    			{
    				if (c[j]<=n)
    				++s1; else ++s2;
    			}
    			
    //			fo(j,1,l)
    //			if (c[j]<=n)
    //			cout<<c[j]<<" ";
    //			else
    //			cout<<-(c[j]-n)<<" ";
    //			cout<<endl;
    //			cout<<" "<<s1*s2<<" "<<s1<<" "<<s2<<endl;
    			
    			ans+=(long long)s1*s2;
    		}
    		else
    		{
    			s1=0;s2=0;
    			fo(j,1,l)
    			{
    				if (j==1 || p[c[j]][0]==p[c[j-1]][0])
    				{
    					if (c[j]<=n)
    					++s1; else ++s2;
    				}
    				else
    				{
    					ans+=(long long)s1*s2;
    					
    					s1=0;s2=0;
    					if (c[j]<=n)
    					++s1; else ++s2;
    				}
    			}
    			ans+=(long long)s1*s2;
    		}
    		
    		ls=i+1;
    	}
    	
    	if (K==2)
    	{
    		fo(i,1,n)
    		{
    			fo(j,1,65)
    			if ((p[i][j]*2%K))
    			break;
    			
    			if (j>65)
    			--ans;
    		}
    	}
    	else
    	{
    		fo(i,1,n)
    		if (!p[i][0])
    		{
    			fo(j,1,65)
    			if ((p[i][j]*2%K))
    			break;
    			
    			if (j>65)
    			--ans;
    		}
    	}
    	
    	printf("%I64d
    ",ans/2);
    }
    

    C

    题意:
    给出n*m的方格图,每个格子上有514石头或者为空
    从(1,1)开始向(n,m)移动(只能向右/下),每次移动会把一行/一列的石头向右/下推一格,不能把石头推出方格图外
    求方案数

    题解:
    差点就切了的sb题
    一个合法的路径有若干次转折,可以发现每次转折时所转到的方向上的石头都没被推过
    所以只需要考虑当前方向往后的石头个数即可转移,用前缀和优化

    code

    #include <algorithm>
    #include <iostream>
    #include <cstdlib>
    #include <cstring>
    #include <cstdio>
    #define fo(a,b,c) for (a=b; a<=c; a++)
    #define fd(a,b,c) for (a=b; a>=c; a--)
    #define mod 1000000007
    using namespace std;
    
    bool a[2002][2002];
    int s1[2002][2002];
    int s2[2002][2002];
    int f[2002][2002][2];
    int g[2002][2002][2];
    int n,m,i,j,k,l;
    char ch;
    
    int main()
    {
    //	freopen("c.in","r",stdin);
    	
    	scanf("%d%d",&n,&m);
    	fo(i,1,n)
    	{
    		fo(j,1,m)
    		{
    			ch=getchar();
    			while (ch!='.' && ch!='R')
    			ch=getchar();
    			
    			a[i][j]=ch=='R';
    		}
    	}
    	
    	if (a[n][m])
    	{
    		printf("0
    ");
    		return 0;
    	}
    	if (n==1 && m==1)
    	{
    		printf("1
    ");
    		return 0;
    	}
    	
    	fd(i,n,1)
    	{
    		fd(j,m,1)
    		{
    			s1[i][j]=s1[i][j+1]+a[i][j];
    			s2[i][j]=s2[i+1][j]+a[i][j];
    		}
    	}
    	
    	f[1][1][0]=1;
    	f[1][1][1]=1;
    	fo(i,1,n)
    	{
    		fo(j,1,m)
    		{
    			if (i>1 || j>1)
    			{
    				f[i][j][0]=g[i][j][0];
    				f[i][j][1]=g[i][j][1];
    			}
    			
    			fo(k,0,1)
    			if (f[i][j][k])
    			{
    				if (!k)
    				{
    					g[i][j][k^1]=(g[i][j][k^1]+f[i][j][k])%mod;
    					g[i][m-s1[i][j+1]+1][k^1]=(g[i][m-s1[i][j+1]+1][k^1]-f[i][j][k])%mod;
    				}
    				else
    				{
    					g[i][j][k^1]=(g[i][j][k^1]+f[i][j][k])%mod;
    					g[n-s2[i+1][j]+1][j][k^1]=(g[n-s2[i+1][j]+1][j][k^1]-f[i][j][k])%mod;
    				}
    			}
    			
    			g[i][j+1][1]=(g[i][j+1][1]+g[i][j][1])%mod;
    			g[i+1][j][0]=(g[i+1][j][0]+g[i][j][0])%mod;
    		}
    	}
    	
    	printf("%d
    ",((f[n][m][0]+f[n][m][1])%mod+mod)%mod);
    }
    

    D

    题意:
    把一条链进行若干次操作,每次操作选择一个点,把这个点的父亲设为其父亲的父亲
    求最小的操作使得能把链变成给出的一棵树,并给出链的初始编号和每次操作的点编号

    题解:
    很妙的构造题
    一开始想把每个点接到兄弟节点中深度最小的点,但是挂了
    考虑把树变成链,一次操作实质是把一个点的父亲设为其的一个兄弟节点
    由于每次操作树的深度最多+1,所以操作次数的下限为(n-深度)
    先找出树上最长链,每次把最长链上的一个点v下移到兄弟u,下移后最长链多了u
    每次深度+1,所以这样的操作次数刚好是(n-深度),即为最优

    code

    #include <algorithm>
    #include <iostream>
    #include <cstdlib>
    #include <cstring>
    #include <cstdio>
    #define fo(a,b,c) for (a=b; a<=c; a++)
    #define fd(a,b,c) for (a=b; a>=c; a--)
    #define min(a,b) (a<b?a:b)
    #define max(a,b) (a>b?a:b)
    using namespace std;
    
    int a[100001][2];
    int ls[100001];
    int fa[100001];
    int nx[100001];
    int Ans[100001];
    bool bz[100001];
    int d[100001];
    int n,i,j,k,l,len,ans,tot,tot2,mx,mx2,h,t;
    
    void New(int x,int y)
    {
    	++len;
    	a[len][0]=y;
    	a[len][1]=ls[x];
    	ls[x]=len;
    }
    
    void dfs(int t,int d)
    {
    	int i;
    	
    	if (d>mx)
    	mx=d,mx2=t;
    	
    	for (i=ls[t]; i; i=a[i][1])
    	fa[a[i][0]]=t,dfs(a[i][0],d+1);
    }
    
    int main()
    {
    //	freopen("d.in","r",stdin);
    	
    	scanf("%d",&n);
    	fo(i,2,n)
    	{
    		scanf("%d",&fa[i]);++fa[i];
    		New(fa[i],i);
    	}
    	
    	dfs(1,1);
    	
    	while (mx2)
    	{
    		bz[mx2]=1;
    		
    		nx[fa[mx2]]=mx2;
    		mx2=fa[mx2];
    	}
    	fo(j,1,n)
    	if (bz[j])
    	{
    		for (i=ls[j]; i; i=a[i][1])
    		if (!bz[a[i][0]])
    		d[++t]=a[i][0];
    	}
    	
    	while (h<t)
    	{
    		++h;
    		j=nx[fa[d[h]]];
    		
    		for (i=ls[d[h]]; i; i=a[i][1])
    		d[++t]=a[i][0];
    		
    		fa[j]=d[h];
    		nx[fa[d[h]]]=d[h];
    		nx[d[h]]=j;
    		
    		Ans[++tot]=j;
    	}
    	
    	for (i=1; i; i=nx[i])
    	printf("%d ",i-1);
    	printf("
    ");
    	printf("%d
    ",tot);
    	fd(i,tot,1)
    	printf("%d ",Ans[i]-1);
    }
    

    E

    题意:
    给出一个数k和n个数(都不能整除k),每次可以把两个数合并,合并后的值除k直到不整除为止
    求一种把n个数合并成1的方案
    n<=16,k<=2000,∑ai<=2000

    题解:
    O(3^n*2000)的做法显然过不了
    可以发现,每种合并的方案最终都可以表示为∑ai*k^bi,其中bi<0
    证明每种∑ai*k^bi=1的情况都能对应一种合法方案:
    ①n=1
    那么只有a1=1时才成立
    (不存在a1>1的情况)
    ②n>1
    设序列中最小的bi为B,那么必定存在至少两个bi=B
    证明:
    若只有一个,那么
    ∑ai*k^bi=1
    ∑ai*k^(bi-B)=k^(-B)
    当bi>B时,乘积必为k的倍数,而右侧也为k的倍数
    如果只有一个bi=B,且ai%k≠0,左右在模k意义下不等,实际也必然不等

    那么每次把bi=bj=B的ij合并,把新的数的b变为(b+合并后的数除k的次数),合并的数变为f(i+j)即可
    可以发现这样仍满足∑ai*k^bi=1,而n-1的所有情况都已经归纳证明了
    ③n=2
    由于n=1比较特殊,所以n=2也要特殊考虑
    从n>1的结论得知,b1=b2=B
    因为a1*k^B+a2*k^B=1
    a1+a2=k^(-B)
    所以f(a1+a2)=1,可以变为n=1的情况,即上文所说不存在a1>1


    那么dp就很显然了,考虑每次操作加一个a,或者对于所有的a除以k
    把一种合法的序列还原时,可以发现其中不存在小数
    所以dp时的ai和也必为整数
    用bitset优化,反着还原dp,然后正着求出每次操作
    还原dp时不需要记录上个状态(因为有bitset),每次枚举一种操作判断原状态是否存在即可

    code

    #include <algorithm>
    #include <iostream>
    #include <cstdlib>
    #include <cstring>
    #include <cstdio>
    #include <bitset>
    #define fo(a,b,c) for (a=b; a<=c; a++)
    #define fd(a,b,c) for (a=b; a>=c; a--)
    using namespace std;
    
    struct type{
    	int a,b;
    } b[17];
    int p[17];
    int a[17];
    int ans[101];
    bitset<2001> f[65536];
    int n,K,L,i,j,k,l,sum,len,s,tot;
    
    bool cmp(type a,type b)
    {
    	return a.b>b.b;
    }
    
    int main()
    {
    //	freopen("e.in","r",stdin);
    	
    	p[1]=1;
    	fo(i,2,16)
    	p[i]=p[i-1]<<1;
    	
    	scanf("%d%d",&n,&K);L=(p[n]<<1)-1;
    	fo(i,1,n)
    	scanf("%d",&a[i]),sum+=a[i];
    	sum/=K;
    	
    	f[0][0]=1;
    	fo(i,1,L)
    	{
    		fo(j,1,n)
    		if (i&p[j])
    		f[i]|=f[i^p[j]]<<a[j];
    		
    		fd(j,sum,1)
    		if (f[i][j*K])
    		f[i][j]=1;
    	}
    	
    	if (!f[L][1])
    	printf("NO
    ");
    	else
    	{
    		printf("YES
    ");
    		
    		s=L;j=1;
    		while (j)
    		{
    			if (j<=sum && f[s][j*K])
    			{
    				j*=K;
    				ans[++len]=-1;
    				continue;
    			}
    			
    			fo(i,1,n)
    			if (s&p[i] && j>=a[i] && f[s-p[i]][j-a[i]])
    			{
    				s-=p[i];
    				j-=a[i];
    				ans[++len]=i;
    				
    				break;
    			}
    		}
    		
    		j=0;
    		fo(i,1,len)
    		if (ans[i]==-1)
    		++j;
    		else
    		b[++tot]={a[ans[i]],-j};
    		
    		while (tot>1)
    		{
    			sort(b+1,b+tot+1,cmp);
    			
    			printf("%d %d
    ",b[tot-1].a,b[tot].a);
    			b[tot-1].a+=b[tot].a;
    			
    			--tot;
    			while (!(b[tot].a%K))
    			b[tot].a/=K,++b[tot].b;
    		}
    	}
    }
    
  • 相关阅读:
    codevs 2632 非常好友
    codevs 1213 解的个数
    codevs 2751 军训分批
    codevs 1519 过路费
    codevs 1503 愚蠢的宠物
    codevs 2639 约会计划
    codevs 3369 膜拜
    codevs 3135 River Hopscotch
    数论模板
    JXOJ 9.7 NOIP 放松模拟赛 总结
  • 原文地址:https://www.cnblogs.com/gmh77/p/11779812.html
Copyright © 2011-2022 走看看