[题解]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)。
(rope
是pb_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\}\),考虑构造如下式子:
不难发现我们构造的式子的\(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; }
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· NetPad:一个.NET开源、跨平台的C#编辑器
· PowerShell开发游戏 · 打蜜蜂
· 凌晨三点救火实录:Java内存泄漏的七个神坑,你至少踩过三个!