[题解]AtCoder Beginner Contest 392(ABC392) A~G

A - Shuffled Equation

显然只有最大值可能被相乘得到,所以对\(a\)从小到大排序,判断\(a[0]\times a[1]=a[2]\)是否成立即可。

时间复杂度\(O(1)\)

点击查看代码
#include<bits/stdc++.h>
using namespace std;
int a[3];
signed main(){
cin>>a[0]>>a[1]>>a[2];
sort(a,a+3);
cout<<(a[0]*a[1]==a[2]?"Yes\n":"No\n");
return 0;
}

B - Who is Missing?

用桶记录每个数的选取情况,枚举\(1\sim n\)所有数,输出所有未被选择的数即可。

时间复杂度\(O(n)\)

点击查看代码
#include<bits/stdc++.h>
#define N 1010
using namespace std;
int n,m,cnt;
bitset<N> vis;
signed main(){
cin>>n>>m;
for(int i=1,a;i<=m;i++) cin>>a,vis[a]=1;
for(int i=1;i<=n;i++) if(!vis[i]) cnt++;
cout<<cnt<<"\n";
for(int i=1;i<=n;i++) if(!vis[i]) cout<<i<<" ";
return 0;
}

C - Bib

题目是让我们对于每个\(i\),求穿\(i\)号围兜的人盯着的人穿几号围兜。

\(b[i]\)表示穿\(i\)号围兜的人,我们在输入的过程中处理出来。则根据上面的分析,第\(i\)个答案为\(q[p[b[i]]]\)

点击查看代码
#include<bits/stdc++.h>
#define N 300010
using namespace std;
int n,p[N],q[N],b[N];
signed main(){
cin>>n;
for(int i=1;i<=n;i++) cin>>p[i];
for(int i=1;i<=n;i++) cin>>q[i],b[q[i]]=i;
for(int i=1;i<=n;i++) cout<<q[p[b[i]]]<<" ";
return 0;
}

D - Doubles

暴力枚举是\(O(n^2 V)\)的,其中\(V=10^5\)表示值域。考虑优化。

实际上对于我们枚举的\((i,j)\)这对骰子,仅需枚举其中一个骰子可能的点数即可,并不需要把\(1\sim 10^5\)的点数都枚举一遍。

分析一下时间复杂度:\(O(\sum\limits_{i,j}k[i])=O(\sum\limits_j S)=O(nS)\),其中\(S=\sum k=10^5\)

点击查看代码
#include<bits/stdc++.h>
#define N 105
#define S 100010
using namespace std;
int n;
unordered_set<int> se[N];
double p[N][S],maxx;
signed main(){
cin>>n;
for(int i=1,m,x;i<=n;i++){
cin>>m;
double tmp=1.0/m;
for(int j=1;j<=m;j++)
cin>>x,p[i][x]+=tmp,se[i].insert(x);
}
for(int i=1;i<n;i++){
for(int j=i+1;j<=n;j++){
double ans=0;
for(int k:se[i])
ans+=p[i][k]*p[j][k];
maxx=max(maxx,ans);
}
}
cout<<fixed<<setprecision(18)<<maxx;
return 0;
}

悄悄告诉你:由于此题玄学的数据,\(O(n^2 V)\)的暴力也可以过(Link)。

E - Cables and Servers

容易发现,最优操作下,每操作一次连通块就减少\(1\)。所以最少操作次数就是整张图的连通块个数\(-1\)

再考虑如何输出方案。显然最优操作下,每个操作都必须合并\(2\)个连通块。换句话说,不能因为我们动用某条边,就使得原来的一整个连通块断成两半。

所以我们不妨规定,只动用原图生成树之外的边。

这样做法就出来了,我们对原图跑生成树,遍历生成树外的边,每条边选取其中一个端点,连向其他连通块中的任意一个即可。

时间复杂度\(O(n\alpha(n))\)

点击查看代码
#include<bits/stdc++.h>
#define N 200010
#define M 200010
using namespace std;
int n,m,fa[N],to[M];
struct edge{int to,num;};
bitset<M> flg;
int find(int x){return x==fa[x]?x:fa[x]=find(fa[x]);}
unordered_set<int> se;
signed main(){
cin>>n>>m;
for(int i=1;i<=n;i++) fa[i]=i;
for(int i=1,u,v;i<=m;i++){
cin>>u>>v,to[i]=u;//to[i]=v 也可以
u=find(u),v=find(v);
if(u!=v) fa[u]=v,flg[i]=1;
}
for(int i=1;i<=n;i++) se.insert(find(i));
cout<<se.size()-1<<"\n";
for(int i=1;i<=m;i++){
if(se.size()==1) break;
if(flg[i]) continue;
auto it=se.begin();
if((*it)==find(to[i])) it++;//避免自己连向自己
fa[*it]=fa[to[i]];
cout<<i<<" "<<to[i]<<" "<<(*it)<<"\n";
se.erase(it);
}
return 0;
}
另一种实现(更简洁)
#include<bits/stdc++.h>
#define N 200010
#define M 200010
using namespace std;
int n,m,fa[N],to[M],ans;
struct edge{int to,num;};
bitset<M> flg;
int find(int x){return x==fa[x]?x:fa[x]=find(fa[x]);}
signed main(){
cin>>n>>m,ans=n-1;
for(int i=1;i<=n;i++) fa[i]=i;
for(int i=1,u,v;i<=m;i++){
cin>>u>>v,to[i]=u;
u=find(u),v=find(v);
if(u!=v) fa[u]=v,flg[i]=1,ans--;
}
cout<<ans<<"\n";
for(int i=1,k=1;i<=m;i++){
if(flg[i]) continue;
while(k<=n&&find(k)==find(to[i])) k++;
if(k>n) break;
fa[fa[k]]=fa[to[i]];
cout<<i<<" "<<to[i]<<" "<<k<<"\n";
}
return 0;
}

F - Insert

看到题面首先想到平衡树之类的结构来模拟,又不想实现平衡树了,就想到用rope来维护序列,算了一下时间在\(O(n\sqrt n)\approx 3.5\times 10^8\),交上去搏一把,很不幸\(\tt TLE\times 4\)Link)。

ropepb_ds库的一个分支,底层实现是块状链表,可以支持\(O(\sqrt n)\)的插入、删除等操作,从c++11开始受支持,具体用法可以自行搜索)

考虑除平衡树外的解法。

我们反过来考虑每个操作,初始定义\(A'=(1,2,\dots,n)\),对于\(i=n,n-1,\dots,1\),依次从\(A'\)中删除\(A'[P[i]]\),并记录\(B[i]\)为此次删除的数。

不难发现\(B[i]\)的另一个含义就是:第\(i\)个加入的元素最终的位置。

因此,对于\(i\in[1,n]\),令\(A[B[i]]=i\),即可得到答案\(A\)


如何快速取到\(A'\)的第\(P[i]\)个元素?

我们可以定义\(T\)数组,初始全为\(1\)。删除某个元素\(x\),相当于令\(T[x]=0\)

找第\(x\)个元素的实际下标,就相当于找使得\((\sum\limits_{i=1}^p T[i])\ge x\)的最小\(p\),可以使用树状数组上倍增来解决。

时间复杂度\(O(n\log n)\)

点击查看代码
#include<bits/stdc++.h>
#define N 500010
using namespace std;
int n,p[N],sum[N],a[N];
inline int lowbit(int x){return x&-x;}
void add(int x,int k){for(;x<=n;x+=lowbit(x)) sum[x]+=k;}
int solve(int x){
int p=0,s=0;
for(int i=20;i>=0;i--)
if(p+(1<<i)<=n&&s+sum[p+(1<<i)]<x)
p+=(1<<i),s+=sum[p];
return p+1;
}
signed main(){
cin>>n;
for(int i=1;i<=n;i++) cin>>p[i],add(i,1);
for(int i=n;i>=1;i--){
int ps=solve(p[i]);
a[ps]=i,add(ps,-1);
}
for(int i=1;i<=n;i++) cout<<a[i]<<" ";
return 0;
}

G - Fine Triplets

对于\(1,2,\dots,n\)这样的数据,答案数量将达到\(n^2\)级别,无法逐个计数。

考虑\(O(n)\)来枚举三元组\((a,b,c)\)的中间值\(b\),由于\(c-b=b-a\iff a+c=2b\),所以它对答案的贡献即为和为\(2b\)的二元组(无序)个数。

如何快速判断和为\(k\)的二元组个数,我们可以想到使用生成函数。

比如对于集合\(S=\{1,2,3,5\}\),考虑构造如下式子:

\[(x^1+x^2+x^3+x^5)(x^1+x^2+x^3+x^5)=x^2+2x^3+3x^4+2x^5+3x^6+2x^7+2x^8+x^{10} \]

不难发现我们构造的式子的\(k\)次项系数就代表和为\(k\)的二元组(有序)个数。比如\(2x^7=x^2x^5+x^5x^2\),意味着我们可以从左边选取\(2\),右边选取\(5\)来组成\(7\);或者左边选\(5\),右边选\(2\)来组成\(7\)。共有这\(2\)种选法。

上式的展开过程需要使用多项式乘法,可以使用FFT/NTT做到\(O(m\log m)\)的时间复杂度,其中\(m\)表示最高次项的次数,这里为值域\(V\)

在这之后,我们就可以\(O(1)\)查询和为\(2b\)的二元组(有序)个数\(s\)了,对答案的贡献为\(\lfloor \frac{s-1}{2}\rfloor\),之所以要减去\(1\),是因为我们要除去两边同时取\(b\)的选法;除以\(2\)是因为是无序的。

总时间复杂度\(O(V\log V+n)\)

点击查看代码(FFT)
#include<bits/stdc++.h>
#define N 1048586
#define Pi 3.1415926535897932384626
using namespace std;
struct complx{
double x,y;
complx(double xx=0,double yy=0){x=xx,y=yy;}
}a[N<<1];
int n,l,r[N<<1],s[N],limit=1;
long long ans;
inline complx operator+(complx a,complx b){return complx(a.x+b.x,a.y+b.y);}
inline complx operator-(complx a,complx b){return complx(a.x-b.x,a.y-b.y);}
inline complx operator*(complx a,complx b){return complx(a.x*b.x-a.y*b.y,a.x*b.y+a.y*b.x);}
void fft(complx *a,int type){
for(int i=0;i<limit;i++) if(i<r[i]) swap(a[i],a[r[i]]);
for(int mid=1;mid<limit;mid<<=1){
complx Wn(cos(Pi/mid),type*sin(Pi/mid));
for(int R=mid<<1,j=0;j<limit;j+=R){
complx w(1,0);
for(int k=0;k<mid;k++,w=w*Wn){
complx x=a[j+k],y=w*a[j+mid+k];
a[j+k]=x+y;
a[j+mid+k]=x-y;
}
}
}
}
int main(){
ios::sync_with_stdio(false);
cin.tie(0);
cin>>n;
for(int i=1;i<=n;i++) cin>>s[i],a[s[i]].x++;
while(limit<=2e6) limit<<=1,l++;
for(int i=0;i<limit;i++) r[i]=(r[i>>1]>>1)|((i&1)<<(l-1));
fft(a,1);
for(int i=0;i<limit;i++) a[i]=a[i]*a[i];
fft(a,-1);
for(int i=1;i<=n;i++) ans+=((long long)(a[s[i]*2].x/limit+0.5)-1)/2;
cout<<ans<<"\n";
return 0;
}
posted @   Sinktank  阅读(366)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· NetPad:一个.NET开源、跨平台的C#编辑器
· PowerShell开发游戏 · 打蜜蜂
· 凌晨三点救火实录:Java内存泄漏的七个神坑,你至少踩过三个!
2025-2-27 8:11:29 TOP-BOTTOM-THEME
Enable/Disable Transition
Copyright © 2023 ~ 2024 Sinktank - 1328312655@qq.com
Illustration from 稲葉曇『リレイアウター/Relayouter/中继输出者』,by ぬくぬくにぎりめし.
点击右上角即可分享
微信分享提示