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
0≤a[i]≤2k−1,问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,n−1]中挑出最多的数,使得它们的乘积模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 t∣w矛盾。剩余的数乘积模n的结果,打表可知要么为1,要么为 ( n − 1 ) (n-1) (n−1),于是判断下最后要不要 ( n − 1 ) (n-1) (n−1)即可。
代码:
#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∗(r−l+1)−众数个数),综上,答案为
max
{
1
,
2
∗
(
r
−
l
+
1
)
−
众
数
个
数
}
\max\{1,2*(r-l+1)-众数个数\}
max{1,2∗(r−l+1)−众数个数}。处理区间众数,可以用块状数组,笔者用的回滚莫队,时空复杂度优秀一些,缺点是不能处理强制在线的询问。
另外,随机算法在本题中也有很好的表现。对于每次询问,随机挑选其中30个数,取它们出现最多的数作为众数。当众数个数符合限制时,这种操作对答案不产生影响。而众数个数超过限制时,计算可知错误概率低于 1 0 − 9 10^{-9} 10−9。
可惜笔者太菜,比赛时没能查出回滚莫队中的错误(第一次写),还是码力太弱。
代码:
#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 个点竞赛图(给无向完全图每条边指定一个方向),你有两种询问方式:
- 询问a和b之间的边是否是a->b,不超过 9 n 9n 9n 次。
- 询问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;
}