AtCoder Beginner Contest 392
AtCoder Beginner Contest 392
A - Shuffled Equation
给三个数,问是否存在两个数的乘积等于最后一个数。
枚举一下就行了。
#include<iostream>
#include<cstdio>
using namespace std;
int main()
{
int a,b,c;
cin>>a>>b>>c;
if(a*b==c||a*c==b||b*c==a)
puts("Yes");
else
puts("No");
}
B - Who is Missing?
给定M个数,每个数都在1到N中,且两两不同。
列出所有1到N中没有的数字。
用一个数组标记那些数字出现过即可。
#include<iostream>
using namespace std;
const int MAX=1010;
bool vis[MAX];
int n,m;
int main()
{
cin>>n>>m;
cout<<n-m<<endl;
for(int i=1;i<=m;++i)
{
int a;
cin>>a;
vis[a]=true;
}
for(int i=1;i<=n;++i)
if(!vis[i])
cout<<i<<' ';
cout<<endl;
return 0;
}
C - Bib
有n个人,第i个人身上有一个数字\(Q_i\),并且他正在看着第\(P_i\)个人。
对于每个\(i\),回答身上的数字为\(i\)的人正在看的那个人的身上的数字。
保证每个\(Q_i,P_i\)两两不同。
格外维护一个数组,表示身上数字为\(i\)的人对应的编号是多少,假设这个数组是\(id\)。
那么身上数字为\(i\)的人,看着的人的编号是\(P[id[i]]\),那么这个人身上的数字就是\(Q[P[id[i]]]\)
#include<iostream>
#include<cstdio>
using namespace std;
#define MAX 300300
int n;
int P[MAX],Q[MAX],id[MAX];
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;++i)
scanf("%d",&P[i]);
for(int i=1;i<=n;++i)
scanf("%d",&Q[i]);
for(int i=1;i<=n;++i)
id[Q[i]]=i;
for(int i=1;i<=n;++i)
printf("%d ",Q[P[id[i]]]);
puts("");
return 0;
}
D - Doubles
有\(N\)个骰子,第\(i\)个骰子有\(K_i\)面,上面的数字分别是\(A_{i,1},...,A_{i,k_i}\),投掷骰子的时候,每个面等概率出现。
选择两个骰子并投掷他们,回答这两个骰子投掷出相同数字的概率的最大值。
考虑如何计算两个骰子投出相同数字的概率,实际上就是每次两个骰子各枚举一个面,统计数字相同的数量,然后除以两个骰子的面数的乘积。
这样单次的复杂度是\(O(K_i\times K_j)\)。
如何优化这个过程呢?对于其中一个骰子,用一个桶数组维护每个数字出现的数量,这样子另一个骰子只需要扫一面,累加桶中对应数字出现的次数即可。单次的复杂度可以优化到\(O(K_i+K_j)\)。
于是就可以暴力枚举两两骰子了。
#include<iostream>
#include<cstdio>
#include<vector>
using namespace std;
#define MAX 100100
vector<int> A[MAX];
int K[MAX];
int n;
double maxP=0;
int cnt[MAX];
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;++i)
{
scanf("%d",&K[i]);
int x;
for(int j=1;j<=K[i];++j)
{
scanf("%d",&x);
A[i].push_back(x);
}
}
for(int i=1;i<=n;++i)
{
for(int j=0;j<K[i];++j)
cnt[A[i][j]]++;
for(int j=i+1;j<=n;++j)
{
long long tot=0;
for(int k=0;k<K[j];++k)
tot+=cnt[A[j][k]];
maxP=max(maxP,1.0*tot/(1.0*K[i])/(1.0*K[j]));
}
for(int j=0;j<K[i];++j)
cnt[A[i][j]]--;
}
printf("%.10lf\n",maxP);
return 0;
}
E - Cables and Servers
有\(n\)个点,\(m\)条双向边的图。
每次可以选择一个点,选择其连接的一条边,然后改变这个边连接的点。
问最少多少次操作之后图联通。
很显然先做生成树,剩下的联通块内的连边把其它联通块连上就行了。
用一个并查集维护联通块,非树边单独拎出来处理连接其它联通块即可。
#include<iostream>
#include<cstdio>
#include<vector>
#include<set>
using namespace std;
#define MAX 200200
int n,m;
vector<pair<pair<int,int>,int> > A;
set<int> S;
int f[MAX];
int getf(int x){return (x==f[x])?x:f[x]=getf(f[x]);}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;++i)f[i]=i;
for(int i=1;i<=m;++i)
{
int a,b;
scanf("%d%d",&a,&b);
if(getf(a)==getf(b))
A.push_back(make_pair(make_pair(a,b),i));
else
f[getf(a)]=getf(b);
}
int ans=-1;
for(int i=1;i<=n;++i)
if(getf(i)==i)
S.insert(i),ans+=1;
printf("%d\n",ans);
for(int i=0,l=A.size();i<l&&ans>0;++i,--ans)
{
int x=A[i].first.first,y=A[i].first.second;
int id=A[i].second;
int fa=getf(x);
S.erase(fa);
int nt=*S.begin();
printf("%d %d %d\n",id,y,nt);
f[fa]=nt;
}
return 0;
}
F - Insert
一开始有一个空的数组,执行N次操作。
第\(i\)次操作是:将\(i\)插入到\(A\)中,使其成为第\(P_i\)个元素。
输出最终的序列。
首先倒着插入,那么插入的位置其实就是从前往后空着的第\(P_i\)个位置,这个可以很容易的使用一个前缀和数据结构+二分处理。
线段树+线段树上二分可以做到\(O(nlogn)\),树状数组+二分可以做到\(O(nlog^2n)\)。
#include<iostream>
#include<cstdio>
using namespace std;
#define MAX 500500
int n,p[MAX],a[MAX];
int t[MAX];
int lb(int x){return x&(-x);}
void add(int x,int a){while(x<=n)t[x]+=a,x+=lb(x);}
int sum(int x){int ret=0;while(x)ret+=t[x],x-=lb(x);return ret;}
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;++i)scanf("%d",&p[i]);
for(int i=1;i<=n;++i)add(i,1);
for(int i=n;i;--i)
{
int l=1,r=n,ans1=0,ans2=0,ans=0;
while(l<=r)
{
int mid=(l+r)>>1;
if(sum(mid)<=p[i])ans1=mid,l=mid+1;
else r=mid-1;
}
l=1,r=n;
while(l<=r)
{
int mid=(l+r)>>1;
if(sum(mid)<p[i])l=mid+1;
else r=mid-1,ans2=mid;
}
if(!a[ans1])ans=ans1;
else ans=ans2;
a[ans]=i;
add(ans,-1);
}
for(int i=1;i<=n;++i)
printf("%d ",a[i]);
puts("");
return 0;
}
G - Fine Triplets
对于三个数\(A,B,C\),其中\(A<B<C\),如果满足\(B-A=C-B\),那么称\((A,B,C)\)为一个好的三元组。
给定\(N\)个不同的正整数,问能够构成多少个好的三元组。
换个思路,B-A=C-B,所以2B=A+C,发现值域很小,所以A+C这个东西的出现次数可以使用多项式卷积很快速的计算出来。
所以离散数字转成值域上的多项式,用NTT计算卷积即可。
#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
#define MAX 2100100
#define MOD 998244353
int fpow(int a,int b){int s=1;while(b){if(b&1)s=1ll*s*a%MOD;a=1ll*a*a%MOD;b>>=1;}return s;}
int W[MAX],r[MAX];
void NTT(int *P,int opt,int len)
{
int l=0,N;for(N=1;N<len;N<<=1)++l;
for(int i=0;i<N;++i)r[i]=(r[i>>1]>>1)|((i&1)<<(l-1));
for(int i=0;i<N;++i)if(i<r[i])swap(P[i],P[r[i]]);
for(int i=1;i<N;i<<=1)
{
int w=fpow(3,(MOD-1)/(i<<1));W[0]=1;
for(int k=1;k<i;++k)W[k]=1ll*W[k-1]*w%MOD;
for(int j=0,p=i<<1;j<N;j+=p)
for(int k=0;k<i;++k)
{
int X=P[j+k],Y=1ll*W[k]*P[i+j+k]%MOD;
P[j+k]=(X+Y)%MOD;P[i+j+k]=(X+MOD-Y)%MOD;
}
}
if(opt==-1)
{
reverse(&P[1],&P[N]);
for(int i=0,inv=fpow(N,MOD-2);i<N;++i)P[i]=1ll*P[i]*inv%MOD;
}
}
int n,a[MAX];
int b[MAX],c[MAX];
long long ans;
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;++i)
scanf("%d",&a[i]);
int L=0;
for(int i=1;i<=n;++i)
b[a[i]]=1,L=max(L,a[i]);
int N;for(N=1;N<=L+L;N<<=1);
NTT(b,1,N);
for(int i=0;i<N;++i)c[i]=1ll*b[i]*b[i]%MOD;
NTT(c,-1,N);
for(int i=1;i<=n;++i)
ans+=(c[a[i]*2]-1)/2;
cout<<ans<<endl;
return 0;
}

浙公网安备 33010602011771号