Codeforces Round #716 Div2 个人题解

A. Perfectly Imperfect Array

题意:
给定一个长度为n的序列,是否存在一个子序列,使得这个子序列所有元素的乘积不为完全平方数。

思路:
若存在一个数不为完全平方数,则只有这个数的子序列满足条件。反之,若序列中的所有数均为完全平方数,那么所有的子序列均不满足条件,因为完全平方数*完全平方数结果仍然为完全平方数。所以问题化为检查是否序列中有非完全平方数。

代码:

#include<bits/stdc++.h>
using namespace std;
long long a[105];
int main(){
	int t;
	scanf("%d",&t);
	while(t--){
		int n;
		scanf("%d",&n);
		for(int i=1;i<=n;++i)scanf("%I64d",&a[i]);
		bool ky=false;
		for(int i=1;i<=n;++i){
			long long now=sqrt(a[i]);
			if(now*now!=a[i]){
				ky=true;
				break;
			}
		}
		if(ky)printf("YES\n");
		else printf("NO\n");
	}
	return 0;
}

B. AND 0, Sum Big

题意:
给定n和k,表示一个长度为n的序列 a [ 1.. n ] a[1..n] a[1..n],限制条件为 0 ≤ a [ i ] ≤ 2 k − 1 0\leq a[i]\leq2^{k}-1 0a[i]2k1,问And和为0且和最大的序列有多少种(即 & i = 1 n a i = 0 \And _{i=1}^{n}a_i=0 &i=1nai=0 ∑ i = 1 n a i \sum_{i=1}^{n}a_i i=1nai最大)。答案模1e9+7

思路:
首先,如果想让And和为0,对于k位二进制的n个数,每一位二进制都至少存在一个数使得该位为0,又要保证和最大,所以每一位二进制恰好有一个0,每一位都有n种方案,而位之间是相互独立的,所以最终答案为 n k % m o d n^k\%mod nk%mod,快速幂即可。

代码:

#include<bits/stdc++.h>
using namespace std;
const long long mod=1000000007;
long long quickpower(long long a,long long b,long long k){
	long long re=1,now=a;
	while(b){
		if(b&1)re=re*now%mod;
		now=now*now%mod;
		b>>=1;
	}
	return re;
}
int main(){
	int t;
	scanf("%d",&t);
	while(t--){
		long long n,k;
		cin>>n>>k;
		cout<<quickpower(n,k,mod)<<"\n";
	}
	return 0;
}

C. Product 1 Modulo N

题意:
给定一个整数n,在 [ 1 , n − 1 ] [1,n-1] [1,n1]中挑出最多的数,使得它们的乘积模n为1.

思路:
首先,容易想到,如果 g c d ( n , i ) > 1 gcd(n,i)>1 gcd(n,i)>1,那么i一定不能被选。

否则,记乘积为w,设 g c d ( n , i ) = t , n = x t gcd(n,i)=t, n=xt gcd(n,i)=t,n=xt,则 w = k n + 1 = k x t + 1 w=kn+1=kxt+1 w=kn+1=kxt+1,与 t ∣ w t|w tw矛盾。剩余的数乘积模n的结果,打表可知要么为1,要么为 ( n − 1 ) (n-1) (n1),于是判断下最后要不要 ( n − 1 ) (n-1) (n1)即可。

代码:

#include<bits/stdc++.h>
using namespace std;
int gcd(int x,int y){
	return y?gcd(y,x%y):x;
}
int ans[100005],cnt;
int main(){
	int n;
	scanf("%d",&n);
	long long tot=1;
	for(int i=1;i<n;++i){
		if(gcd(i,n)==1){
			ans[++cnt]=i;
			tot=tot*i%n;
		}
	}
	if(tot!=1)--cnt;
	printf("%d\n",cnt);
	for(int i=1;i<=cnt;++i)printf("%d ",ans[i]);
	printf("\n");
	return 0;
} 

D. Cut and Stick

题意:
给定一个序列 a [ 1.. n ] a[1..n] a[1..n],q次询问。每次询问给定l和r,询问最小的k,满足:如果把 a [ l . . r ] a[l..r] a[l..r]单独拿出来,分成k部分,该区间中的每个元素恰好属于一个部分,存在至少一种方案,满足每个部分中的每个数出现次数不能超过 ⌈ 该 部 分 总 数 / 2 ⌉ + 1 \lceil 该部分总数/2 \rceil+1 /2+1

思路:
若一个部分不符合条件,那么必然是该部分的众数超过了限制,其他数一定不会超过限制。对于 a [ l . . r ] a[l..r] a[l..r],若众数出现次数不超过限制,那么答案为1。否则,记除众数外的数有x个,那么这x个数与(x+1)个众数,可以组成一个合法的部分。剩下的数每个数单独为一部分,可以证明这是最优的方案。此时答案为 ( 2 ∗ ( r − l + 1 ) − 众 数 个 数 ) (2*(r-l+1)-众数个数) (2(rl+1)),综上,答案为 max ⁡ { 1 , 2 ∗ ( r − l + 1 ) − 众 数 个 数 } \max\{1,2*(r-l+1)-众数个数\} max{1,2(rl+1)}。处理区间众数,可以用块状数组,笔者用的回滚莫队,时空复杂度优秀一些,缺点是不能处理强制在线的询问。

另外,随机算法在本题中也有很好的表现。对于每次询问,随机挑选其中30个数,取它们出现最多的数作为众数。当众数个数符合限制时,这种操作对答案不产生影响。而众数个数超过限制时,计算可知错误概率低于 1 0 − 9 10^{-9} 109

可惜笔者太菜,比赛时没能查出回滚莫队中的错误(第一次写),还是码力太弱。

代码:

#include<bits/stdc++.h>
using namespace std;
struct query{
	int l,r,lw,rw,id,ans;
}q[300005];
bool cmp(query x,query y){
	if(x.lw!=y.lw)return x.lw<y.lw;
	return x.r<y.r;
}
bool cmpp(query x,query y){
	return x.id<y.id;
}
int a[300005],cnt[300005],tmpcnt[300005];
int main(){
	int n,m,base;
	scanf("%d%d",&n,&m);
	base=sqrt(n);
	for(int i=1;i<=n;++i)scanf("%d",&a[i]);
	for(int i=1;i<=m;++i){
		scanf("%d%d",&q[i].l,&q[i].r);
		q[i].lw=(q[i].l-1)/base+1;
		q[i].rw=(q[i].r-1)/base+1;
		q[i].id=i;
	}
	sort(q+1,q+1+m,cmp);
	int l=1,r=0,tmpmax;
	for(int i=1;i<=m;++i){
		int len=q[i].r-q[i].l+1;
		if(q[i].lw==q[i].rw){
			for(int j=q[i].l;j<=q[i].r;++j){
				++tmpcnt[a[j]];
				if(tmpcnt[a[j]]>(len-1)/2+1)q[i].ans=tmpcnt[a[j]];
			}
			for(int j=q[i].l;j<=q[i].r;++j)--tmpcnt[a[j]];
		}
		else{
			if(q[i].lw>q[i-1].lw||q[i-1].lw==q[i-1].rw){
				memset(cnt,0,sizeof(cnt));
				for(int j=q[i].l;j<=q[i].r;++j){
					++cnt[a[j]];
					if(cnt[a[j]]>(len-1)/2+1)q[i].ans=cnt[a[j]];
				}
				r=q[i].r;
				for(int j=q[i].lw*base;j>=q[i].l;--j)--cnt[a[j]];
				tmpmax=0;
				for(int j=1;j<=n;++j)tmpmax=max(tmpmax,cnt[j]);
			}
			else{
				bool ky=false;
				while(r<q[i].r){
					++r;
					++cnt[a[r]];
					if(cnt[a[r]]>tmpmax)tmpmax=cnt[a[r]];
				}
				int tmp=tmpmax;
				l=q[i].lw*base+1;
				while(l>q[i].l){
					--l;
					++cnt[a[l]];
					if(cnt[a[l]]>tmpmax)tmpmax=cnt[a[l]];
				}
				q[i].ans=tmpmax;
				tmpmax=tmp;
				while(l<=q[i].lw*base){
					--cnt[a[l]];
					++l;
				}
			}
		}
	}
	sort(q+1,q+1+m,cmpp);
	for(int i=1;i<=m;++i){
		int len=q[i].r-q[i].l+1;
		printf("%d\n",max(1,2*q[i].ans-len));
	}
	return 0;
}

E. Baby Ehab’s Hyper Apartment

题意:

交互题,现在有一个 n n n 个点竞赛图(给无向完全图每条边指定一个方向),你有两种询问方式:

  1. 询问a和b之间的边是否是a->b,不超过 9 n 9n 9n 次。
  2. 询问a到一系列点是否有边,不超过 2 n 2n 2n 次。

输出一个矩阵 f f f f [ i ] [ j ] = 1 / 0 f[i][j]=1/0 f[i][j]=1/0 代表 i i i 能/不能到达 j j j

思路:

关于竞赛图有一个结论: n n n 阶竞赛图一定有哈密尔顿路。数学归纳法易证。

如果我们能找到哈密尔顿路,那么后面的问题就简单了。假设哈密尔顿路的最后一个点 i i i 有一条连到 j j j 的边,那么对于 j j j i i i 的所有点,都可以到达 j j j,所以,我们从哈密尔顿路的最后一个点开始,用2操作找它指向的最前面的那个点即可。具体方法为:从上一个点连到的最前的点的前一个点开始,用2操作询问该点到(哈密尔顿路的起点-当前点)这个点集是否连边,有的话当前点向前一个,没有的话上一个点即为所求。当到达起点的时候结束。这样最后跑一边Floyd即可。

现在只剩下哈密尔顿路怎么找了。本题最巧妙的就在这里,如果把a->b看做a>b,那么哈密尔顿路就是一个单调递增的序列,于是只需要用这个规则重定义小于号,然后排序即可,当调用cmp方法的时候调用1操作询问即可。不过为了保险起见,可以加一个记忆化。

最后,交互题注意格式,否则就可能无端暴毙(笔者亲历)。

代码:

#include<bits/stdc++.h>
using namespace std;
int a[105],ans[1005][1005];
bool dp[105][105],dpp[105][105];
bool cmp(int x,int y){
	if(x==y)return false;
	if(ans[x][y]>-1)return ans[x][y]==1?true:false;
	cout<<'1'<<" "<<x<<" "<<y<<endl;
	fflush(stdout);
	int re;
	cin>>re;
	ans[x][y]=re;
	return ans[x][y]==1?true:false;
}
int main(){
	int t;
	cin>>t;
	while(t--){
		memset(dp,0,sizeof(dp));
		int n;
		cin>>n;
		for(int i=0;i<n;++i){
			dpp[i][i]=true;
			for(int j=0;j<n;++j){
				ans[i][j]=-1;
			}
		}
		for(int i=0;i<n;++i)a[i+1]=i;
		sort(a+1,a+1+n,cmp);
		int now=n;
		for(int i=1;i<=n;++i){
			for(int j=i;j<=n;++j)dp[a[i]][a[j]]=true;
		}
		for(int i=n;i>=1;--i){
			now=min(i,now);
			if(now==1)break;
			for(int j=now-1;j>=1;--j){
				cout<<'2'<<" "<<a[i]<<" "<<j<<" ";
				for(int k=1;k<=j;++k)cout<<a[k]<<" ";
				cout<<"\n";
				fflush(stdout);
				int tmp=0; 
				cin>>tmp;
				if(!tmp)break;
				now=j;
				dp[a[i]][a[j]]=true;
			}
		}
	    for(int k=0;k<n;++k){
			for(int i=0;i<n;++i){
				for(int j=0;j<n;++j){
					dp[i][j]|=(dp[i][k]&dp[k][j]);
				}
			}
		}
		cout<<"3"<<endl;
		for(int i=0;i<n;++i){
			for(int j=0;j<n;++j){
				if(dp[i][j])cout<<"1";
				else cout<<"0";
			}
			cout<<endl;
		}
		fflush(stdout);
		int bkx;
		cin>>bkx;
	}
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值