title : 2021ICPC澳门站 个人题解
date : 2022-10-6
tags : ACM,题解,练习记录
author : Linno


2020ICPC澳门站 个人题解

题目链接:https://codeforces.com/gym/103119

补题进度:8/12

A-Accelerator

式子展开为: a n s = ∏ i = 1 n a p i + ∏ i = 2 n a p i + . . . + ∏ i = n n a p i ans=\prod_{i=1}^n a_{p_i}+\prod_{i=2}^n a_{p_i}+...+\prod_{i=n}^n a_{p_i} ans=∏i=1n​api​​+∏i=2n​api​​+...+∏i=nn​api​​,对于任意一个排列期望是其其答案乘以概率,长度为i的排列概率显然为 i ! × ( n − i ) ! n ! \frac{i!×(n-i)!}{n!} n!i!×(n−i)!​。设S为从n个数中取i个的所有排列的集合,设 f i = ∑ p ∈ S ∏ j = 1 i a p j f_i=\sum_{p\in S}\prod_{j=1}^i a_{p_j} fi​=∑p∈S​∏j=1i​apj​​,答案期望为 E ( x ) = ∑ i = 1 n f i × i ! × ( n − i ) ! n ! E(x)=\frac{\sum_{i=1}^n f_i × i! × (n-i)!}{n!} E(x)=n!∑i=1n​fi​×i!×(n−i)!​

我们从 f i = ∑ p ∈ S ∏ j = 1 i a p j f_i=\sum_{p\in S}\prod_{j=1}^i a_{p_j} fi​=∑p∈S​∏j=1i​apj​​,有 f i = ∑ j = 1 i f i − j × f j f_i=\sum_{j=1}^i f_{i-j}×f_j fi​=∑j=1i​fi−j​×fj​

这是一个自己卷自己的分治FFT模板,复杂度为 O ( n l o g 2 n ) O(nlog^2n) O(nlog2n)

//#pragma GCC optimize("Ofast", "inline", "-ffast-math")
//#pragma GCC target("avx,sse2,sse3,sse4,mmx")
#include<bits/stdc++.h>
#define inf 0x3f3f3f3f
#define int long long
#define rep(i,x,y) for(int i=x;i<=y;++i)
#define repd(i,x,y) for(int i=x;i>=y;--i)
using namespace std;
const int N=1e6+7;
const int mod=998244353;int t,n,m,a[N];
int lim,L,G=3,rev[N];
int f[N<<1],g[N<<1],h[N<<1],inv[N<<1],frac[N<<1],ifrac[N<<1];int read(){    int x=0,f=1;char ch=getchar();while(ch<'0'||ch>'9'){if(ch=='-') f=f*-1;ch=getchar();}while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}return x*f;}
void write(int x){if(x>9) write(x/10);putchar(x%10+'0');}inline int fpow(int a,int b){int res=1;while(b){if(b&1) res=1ll*res*a%mod;a=1ll*a*a%mod;b>>=1;}return res;
}void NTT(int *A,int limit,int type){for(int i=0;i<limit;++i) if(i<rev[i]) swap(A[i],A[rev[i]]);for(int mid=1;mid<limit;mid<<=1){int wn=fpow(G,(mod-1)/(mid<<1));for(int pos=0;pos<limit;pos+=(mid<<1)){int w=1;for(int k=0;k<mid;k++,w=1ll*w*wn%mod){int x=A[pos+k],y=1ll*w*A[pos+mid+k]%mod;A[pos+k]=(x+y)%mod;A[pos+k+mid]=(x-y+mod)%mod; }}}if(type==1) return;for(int i=1;i<limit/2;++i) swap(A[i],A[limit-i]);int inv=fpow(limit,mod-2);for(int i=0;i<limit;++i) A[i]=1ll*A[i]*inv%mod;
}void NTT_mul(int *f,int *g,int *h,int n,int m){int lim=1,L=0;while(lim<=n+m) lim<<=1,L++;for(int i=n+1;i<lim;++i) f[i]=0;for(int i=m+1;i<lim;++i) g[i]=0;for(int i=0;i<lim;++i) rev[i]=(rev[i>>1]>>1)|((i&1)<<(L-1));NTT(f,lim,1);NTT(g,lim,1);for(int i=0;i<lim;++i) h[i]=1ll*f[i]*g[i]%mod;NTT(h,lim,-1);
}vector<int>solve(int l,int r){vector<int>res;if(l==r) return {1,a[l]};int mid=((l+r)>>1);int len1=(mid-l+1),len2=r-mid;auto res1=solve(l,mid);auto res2=solve(mid+1,r);for(int i=0;i<=len1;++i) f[i]=res1[i];for(int i=0;i<=len2;++i) g[i]=res2[i];NTT_mul(f,g,h,len1,len2);res.resize(len1+len2+1);for(int i=0;i<len1+len2+1;++i) res[i]=h[i];return res;
}signed main(){inv[0]=inv[1]=1;for(int i=2;i<N;++i) inv[i]=1ll*(mod-mod/i)*inv[mod%i]%mod;frac[0]=ifrac[0]=1;for(int i=1;i<N;++i){frac[i]=1ll*frac[i-1]*i%mod;ifrac[i]=1ll*ifrac[i-1]*inv[i]%mod;}t=read();while(t--){n=read();for(int i=1;i<=n;++i) a[i]=read();auto res=solve(1,n);int ans=0;for(int i=1;i<=n;++i){ans+=1ll*res[i]*frac[i]%mod*frac[n-i]%mod;if(ans>=mod) ans-=mod; }ans=1ll*ans*ifrac[n]%mod;write(ans);putchar('\n');}return 0;
}

C-Club Assignment

从高位到低位,将0和1分到不同的集合,如果有超过3个是相同的数位,就对答案没有贡献,直接分治到下一层。对于长度为4以下的区间可以直接暴力枚举两边异或最小值最大的方案,然后向上合并即可。

#include<bits/stdc++.h>
#define mk make_pair
#define pii pair<int,int>
#define F first
#define S second
#define inf 0x3f3f3f3f3f3f3f3f
using namespace std;
typedef long long ll;
const int N=3e5+7;pii a[N];
int t,n,ans[N];ll solve(int l,int r,int pos){ //求从pos位向下分[l,r]区间可得到的答案 if(r-l+1==1){ //只有一个数的集合直接返回ans[a[l].S]=1;return inf;}else if(r-l+1<=4){ //如果区间长度在4以内直接枚举 if(r-l+1==2){ans[a[l].S]=1;ans[a[r].S]=2;return inf;}vector<pii>s1,s2;ll mx=0;for(int i=l;i<=r;++i){for(int j=i+1;j<=r;++j){vector<pii>A,B;A.push_back(a[i]);A.push_back(a[j]);for(int k=l;k<=r;++k){ //将a[i],a[j]放在A集合,其他放在B集合 if(k==i||k==j) continue;B.push_back(a[k]);}ll va=inf,vb=inf;va=A.front().F^A.back().F;if(B.size()>1) vb=B.front().F^B.back().F;if(min(va,vb)>=mx){ //替换原来的答案 s1=move(A);s2=move(B);mx=min(va,vb);}}}for(auto p:s1) ans[p.S]=1;for(auto p:s2) ans[p.S]=2;return mx;}if(pos<0){if(r-l+1>=3){for(int i=l;i<=r;++i) ans[a[i].S]=1;return 0;}if(r-l+1==2){ans[a[l].S]=1;ans[a[r].S]=2;}else ans[a[l].S]=1;return inf;}int p=l;while(p<=r){//如果有两个以上在这一位上是1,那么就等于失效了,找下一位,否则将左右分到不同集合 if((a[p].F&(1<<pos))) break;++p;}ll mi=inf;if(p>l) mi=min(mi,solve(l,p-1,pos-1));if(p<=r) mi=min(mi,solve(p,r,pos-1));return mi;
}signed main(){ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);cin>>t;while(t--){cin>>n;for(int i=1,x;i<=n;++i){cin>>x;a[i]=mk(x,i);}sort(a+1,a+1+n);cout<<solve(1,n,30)<<"\n"; for(int i=1;i<=n;++i) cout<<ans[i];cout<<"\n";}return 0;
}

D-Artifacts

签到题,队友getline写完的,用Python写了一遍发现真的好写。

import sys
import mathatk=0.0
ar=0.0
cr=5.0
cdr=50.0
for i in range(5):for j in range(5):str=input()if(str[0]=='A'):if(str[4]=='R'):tmp=float(str.split('+')[-1].split('%')[0])ar+=tmpelse:tmp=float(str.split('+')[-1])atk+=tmpelif(str[0]=='C'):if(str[5]=='R'):tmp=float(str.split('+')[-1].split('%')[0])cr+=tmpelse:tmp=float(str.split('+')[-1].split('%')[0])cdr+=tmp
if(cr>100):cr=100
atk=(1500.0)*(1+ar/100.0)+atk
ans=atk*(1.0-cr/100.0)+atk*(1.0+cdr/100.0)*(cr/100.0)
print(ans)

F-Fixing Networks

这题很烦,特判条件很多。直接的结论是 d + 1 d+1 d+1个点组成一个完全图是最合理的,那么剩下的点要满足条件肯定也是完全对称的。可以想象连一个环,先前连 l e n 2 \frac{len}{2} 2len​长度的点,向后相同,其中len为环的长度。如果 d d d是奇数时,如果环不对称显然无解,因此可以环长为偶数时,向对面再连一个点,就完全对称了。一些需要的特判:

① n < c ∗ ( d + 1 ) n<c*(d+1) n<c∗(d+1),即上述方式抽不出c个团。

② d 是奇数且 n 是奇数 d是奇数且n是奇数 d是奇数且n是奇数,那么最后会剩出一个奇环,环上不可能每个点不能连奇数个点。

③ d = 0 d=0 d=0,团数=点数,没有边。

④ d = 1 d=1 d=1,偶数个点两两相连。

#include<bits/stdc++.h>
using namespace std;
const int N=1e5+7;
int n,d,c;
vector<int>ans[N];signed main(){ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);cin>>n>>d>>c;if(n<(d+1)*c||((d&1)&&(n&1))){cout<<"No\n";return 0;}else if(d==0){if(c==n) cout<<"Yes\n";else cout<<"No\n";return 0;}else if(d==1){if(2*c==n){cout<<"Yes\n";for(int i=1;i<=n;++i) cout<<(i+((i&1)?1:-1))<<"\n";}else cout<<"No\n";}else{int L=1;for(int p=1;p<=c-1;p++,L+=d+1){ //c-1团 int R=L+d;for(int i=L;i<=R;++i){for(int j=L;j<=R;++j){if(i!=j) ans[i].emplace_back(j);        }}}if(L<=n){ //剩下[L~n]的点int len=n-L+1; //这里有一个环if((len&1)&&(d&1)){cout<<"No\n";return 0;}for(int i=L;i<=n;++i){for(int j=1;j<=d/2;++j){ans[i].emplace_back(L+(i-L+j+len)%len);}for(int j=1;j<=d/2;++j){ans[i].emplace_back(L+(i-L-j+len)%len);}if(d&1){int tmp=i+len/2;if(tmp>n) tmp-=len;ans[i].emplace_back(tmp);}}}cout<<"Yes\n";for(int i=1;i<=n;++i){stable_sort(ans[i].begin(),ans[i].end());for(auto j:ans[i]) cout<<j<<" ";cout<<"\n";\}}return 0;
}

G-Game on Sequence

容易发现所有位置上的胜负状态都是可以由最后一个位置出现的该数的胜负状态表示的。记录好每个数字最后出现的位置,对于操作1就直接加入,然后重新扫一遍所有数字的胜负态,由最后出现的数字向前开始搜SG数组。对于操作2分类讨论:①如果这个数不是最后出现的那一个,肯定是必胜的(可以转移到最后一次出现的相同数字或者其能转移的最后一个数);②否则我们已经提前处理过该数字的胜负了,直接输出。

#include<bits/stdc++.h>
using namespace std;
const int N=4e5+7,M=300;
int n,m,a[N],lst[M],sg[M];bool cmp(int A,int B){return lst[A]>lst[B];
}inline void work(){memset(sg,1,sizeof(sg));vector<int>tmp;for(int i=0;i<M;++i) if(lst[i]) tmp.emplace_back(i);sort(tmp.begin(),tmp.end(),cmp);for(auto num:tmp){bool flag=false;for(int i=0;i<8;++i){if(!sg[num^(1<<i)]){flag=1;break;}}if(flag) sg[num]=1;else sg[num]=0;}
}signed main(){ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);cin>>n>>m;for(int i=1;i<=n;++i) cin>>a[i],lst[a[i]]=i;work();for(int i=1,op,x;i<=m;++i){cin>>op>>x;if(op==1){a[++n]=x;lst[a[n]]=n;work();}else{if(lst[a[x]]==x){if(sg[a[x]]) cout<<"Grammy\n";else cout<<"Alice\n";}else cout<<"Grammy\n";}}return 0;
}

I-Nim Cheater

对于所有操作,可以构成一棵树,我们在树上dp可以得到每个操作的答案。答案转移就是简单的dp式子 d p [ i ⊕ v a l [ x ] ] = m i n ( d p [ i ] + c o s t [ p ] ) dp[i\oplus val[x]]=min(dp[i]+cost[p]) dp[i⊕val[x]]=min(dp[i]+cost[p])

#include<bits/stdc++.h>
#define inf 0x3f3f3f3f
using namespace std;
const int N=20001;int ans[N],fa[N],val[N],cost[N];
vector<int>G[N];
int sz;
bool hson[N];int dfs(int p){  //重链剖分 int cnt=1,mx=0,son=-1;for(int to:G[p]){int res=dfs(to);if(res>mx){mx=res;son=to;}cnt+=res;}if(son>=0) hson[son]=1;return cnt;
}int res[N];
void solve(int p,int tot){  //树上dp,先遍历轻儿子,然后遍历重儿子 int *bk;if(!hson[p]){bk = new int[N];for(int i=0;i<N;++i) bk[i]=res[i];}for(int i=0;i<N;++i){res[i^val[p]]=min(res[i^val[p]],res[i]+cost[p]);}ans[p]=res[tot^val[p]];int son=-1;for(int to:G[p]){if(hson[to]){son=to;continue;}solve(to,tot^val[p]);}if(son>=0) solve(son,tot^val[p]);if(!hson[p]){for(int i=0;i<N;++i) res[i]=bk[i];delete [] bk;}
}int num;
void printans(int p){if(!num) return;if(p){cout<<ans[p]<<"\n";num--;}for(int to:G[p]){if(!num) return;printans(to);if(!num) return;cout<<ans[p]<<"\n";num--;}
}signed main(){ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);int n,cur=0;cin>>n;string op;for(int i=1;i<=n;++i){  //所有操作可以构成一棵树 cin>>op;if(op=="ADD"){++sz;cin>>val[sz]>>cost[sz];G[cur].emplace_back(sz);fa[sz]=cur;cur=sz;}else{cur=fa[cur];}}memset(res,inf,sizeof(res));res[0]=0;dfs(0);solve(0,0);num=n;printans(0);return 0;
}

J-Jewel Grab

#include<bits/stdc++.h>
#define inf 1e12
using namespace std;
typedef long long ll;
const int N=2e5+7;int n,m,c[N],pre[N],nxt[N],lst[N];
ll v[N],mvc[N];
set<int>pos[N]; struct Nod{  //线段树上结点记录区间的权值和以及最右边的颜色前驱 int l,r,mx;ll s;
}tr[N<<2];
#define ls (p<<1)
#define rs (p<<1|1)
#define mid ((tr[p].l+tr[p].r)>>1)void pushup(int p){tr[p].mx=max(tr[ls].mx,tr[rs].mx);tr[p].s=tr[ls].s+tr[rs].s;
}void build(int p,int l,int r){tr[p].l=l;tr[p].r=r;if(l==r){tr[p].mx=pre[l];tr[p].s=v[l];return;}build(ls,l,mid);build(rs,mid+1,r);pushup(p);
}void modify(int p,int pos){if(tr[p].l==tr[p].r){tr[p].mx=pre[tr[p].l];tr[p].s=v[tr[p].l];return;}if(pos<=mid) modify(ls,pos);else modify(rs,pos);pushup(p);
}ll query(int p,int ql,int qr){ if(ql<=tr[p].l&&tr[p].r<=qr) return tr[p].s;ll res=0;if(ql<=mid) res=query(ls,ql,qr);if(qr>mid) res+=query(rs,ql,qr);return res;
}int find(int p,int st,int k){ //找第一个前驱在st之后的结点if(tr[p].l==tr[p].r) return tr[p].l;int pos=-1;if(tr[ls].mx>=st&&k<=mid) pos=find(ls,st,k);if(pos!=-1) return pos;if(tr[rs].mx>=st) pos=find(rs,st,k);return pos;
}signed main(){ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);cin>>n>>m;for(int i=1;i<=n;++i){cin>>c[i]>>v[i];pos[c[i]].insert(i);pre[i]=lst[c[i]];lst[c[i]]=i;}for(int i=n;i>=1;--i) if(pre[i]) nxt[pre[i]]=i;build(1,1,n);for(int i=1,op,x,y,k;i<=m;++i){cin>>op;if(op==1){  //对于修改操作而言,就是在链表和线段树上单点修改 cin>>x>>y>>k;pos[c[x]].erase(x);if(lst[c[x]]==x) lst[c[x]]=pre[x];c[x]=y;v[x]=k;if(nxt[x]){if(pre[x]){nxt[pre[x]]=nxt[x];pre[nxt[x]]=pre[x];modify(1,nxt[x]);}else{pre[nxt[x]]=0;modify(1,nxt[x]);}}else{if(pre[x]) nxt[pre[x]]=0; }int p;auto it=pos[y].lower_bound(x);if(it==pos[y].end()){nxt[x]=-1;pre[x]=lst[y];if(pre[x]!=-1) nxt[pre[x]]=x;modify(1,x);}else{p=*it;if(pre[p]){nxt[pre[p]]=x;pre[x]=pre[p];modify(1,x);}else{pre[x]=0;modify(1,x);}nxt[x]=p;pre[p]=x;modify(1,p);}pos[y].insert(x);lst[y]=max(lst[y],x);}else{vector<int>col;col.clear();cin>>x>>k;int cur=x;ll ans=0;for(int j=0;j<=k&&cur<=n;++j){ //筛选k个前缀在s之前的结点 int p=find(1,x,cur);if(p==-1){ans+=query(1,cur,n); //如果没有颜色重复 break;}else{col.emplace_back(c[p]);if(j<k){if(mvc[c[p]]==0) mvc[c[p]]=v[pre[p]];if(v[p]>mvc[c[p]]){ //记录该颜色经过筛选后的最大权值 ans-=mvc[c[p]];ans+=v[p];mvc[c[p]]=v[p];}}ans+=query(1,cur,p-1);cur=p+1;for(auto u:col) mvc[u]=0;}}cout<<ans<<"\n";}}return 0;
}

L-Random Permutation

推出来的式子是 n ! ∗ n ! n n \frac{n!*n!}{n^n} nnn!∗n!​

import sys
import mathn=int(input())
ans=math.factorial(n)*math.factorial(n)/(n**n)
print(ans)

【超好懂的比赛题解】2020ICPC澳门站 个人题解相关推荐

  1. 【超好懂的比赛题解】HNCPC Multi-university Training Round3 比赛题解

    title : HNCPC Multi-university Training Round3 date : 2022-7-29 tags :ACM,练习记录 author : LINNO HNCPC ...

  2. 【超好懂的比赛题解】第十八届同济大学程序设计竞赛暨高校网络友谊赛

    title : 第十八届同济大学程序设计竞赛暨高校网络友谊赛 date : 2022-5-30 tags : ACM,题解,练习记录 author : Linno 第十八届同济大学程序设计竞赛暨高校网 ...

  3. 【超好懂的比赛题解】“山大地纬杯”第十二届山东省ICPC大学生程序设计竞赛(正式赛)

    title : "山大地纬杯"第十二届山东省ICPC大学生程序设计竞赛(正式赛) date : 2022-5-30 tags : ACM,题解,练习记录 author : Linn ...

  4. 2021ICPC澳门站部分题解

    链接 A 构造一个生成函数就完了,正好有一个板子,贴一下完事.社会主义重拳出击,没想到区域赛会出这种题.澳门的前六个题是签到.模拟.最小异或生成树.分治ntt.构造.dag上sg函数.跪了.和大陆的区 ...

  5. 马走日-深搜回溯-超好懂题解

    马在中国象棋以日字形规则移动. 请编写一段程序,给定n*m大小的棋盘,以及马的初始位置(x,y),要求不能重复经过棋盘上的同一个点,计算马可以有多少途径遍历棋盘上的所有点. Input 第一行为整数T ...

  6. 【ICPC 2022 澳门站】A题 So I‘ll Max Out My Constructive Algor... 题解

    题目大意 给定一个 n ∗ n n*n n∗n的数字矩阵,代表每个点的高度,每个数各不相同,求一条遍历所有的点的路径,要求只能上下左右移动,且高度下降的次数不小于高度上升次数. 题目链接 思路 事实上 ...

  7. REVA世界巡回交流会——亚太峰会 澳门站拉开序幕

    数字金融界最近有不少大事发生,世界知名数字平台REVA举办世界巡回交流会,其中亚太地区第一站选在澳门,2023年5月8日至10日期间,将盛大举行此次高峰论坛,REVA管理高层悉数到场,行业内外精英齐聚 ...

  8. 2020ICPC沈阳站后记

    沈阳站金的很意外,去的时候我们三个人根本就没有抱着打金的心态,就像hl学长赛后说的那样,之前心里想着冲金拼的时候总也是银首,今天压根就没想,结果反而金了. 略详细得回顾一下这次沈阳之行赛中赛外各种细节 ...

  9. 第45届ICPC沈阳站部分题解(D、F、G、H、I、J、K)

    文章目录 D-前缀和思想+dfs F-贪心 G H-双指针+dp 题意 思路 代码 I-追及问题+裴蜀定理 J-可持久化线段树 K-前缀和+积分的定义 题意 思路 参考链接 传送门 本文CSDN 本文 ...

最新文章

  1. 其他算法-高斯白噪声
  2. - 运算符(C# 参考)
  3. css3实现烟花效果,CSS3 带颤动效果的简易烟花动效
  4. BootStrap之前奏响应式布局
  5. python实训内容_Python实验课:Python元组数据及其运算
  6. TensorFlow和Keras解决大数据量内存溢出问题
  7. 查询所有_学会DSUM函数,轻松搞定所有的数据查询与数据求和
  8. 怎么把手机录音转换成mp3?
  9. WIN10打印机显示服务器脱机,网络打印机脱机,教你win10网络打印机脱机无法打印的应对办法...
  10. cisco思科交换机恢复出厂设置清除配置的方法
  11. PPT画图如何保存600dpi以及消除白边问题
  12. 高中计算机二级考试试题,高中二年级全国计算机等级考试试题.doc
  13. 十五天学会Autodesk Inventor,看完这一系列就够了(一),前言—介绍及区别
  14. 关于寻找海王id的算法伪代码分析思路
  15. 1. CUDA安装失败解决方法
  16. BCGSoft BCGControlBar for .NET托管代码工具包
  17. OpenCV 之视频文件的处理
  18. 多个目标优化的帕累托前沿面如何可视化
  19. OR青年导师访谈特辑 | 香港理工大学助理教授 马玮:一次拉长的面试 一个交流的平台
  20. c语言浮点数值显示两位,C语言中,输出浮点数时保留两位小数应该怎么搞?

热门文章

  1. 常见硬件面试题(含答案)盘点,硬件工程师学习笔记
  2. ggplot2设置坐标轴范围_ggplot2|详解八大基本绘图要素
  3. ios 按钮文字下划线_iOS 文字下划线
  4. welsh-powell
  5. linux根文件系统目录结构
  6. 程序员自我提升的5个方法
  7. 手机充值了还是显示无服务器,手机显示已联网,但却不能用,怎么办?
  8. 自我管理类书籍推荐以及如何进行自我管理
  9. TADF材料的机制原理;TADF的机理;热活化延迟荧光如何产生?
  10. dsf5.0 有确认按钮的弹框