Codeforces Global Round 10(CF1392)
Codeforces Global Round 10
前言
我:我想上紅。。。
我的實力和網速:不,你不想。
A to E 略
F Omkar and Landslide
題意
給一個長度為 \(n\) 的遞增的陣列 \(h_i\) ,每當存在 \(h_j + 2 \leq h_{j+1}\) ,則使 \(h_j\) 增加 \(1\) ,同時使 \(h_{j+1}\) 減小 \(1\) 。輸出最終的陣列。\(0\leq h_i \leq {10}^{12},1\leq n\leq {10}^{6}\) 。
題解
沒有看這題的題解,說一下我自己的做法吧。。顯然初始和最終的陣列都可以由若干個逐漸上升 \(1\)
取每個塊的最後的數放在一個佇列中,我們就可以反推出原來的陣列。於是我們得到了表示陣列的方法。
觀察變化的方式可知,當我們在一個數組末尾增加一個數,只會對最後一個塊產生變化,直到最後一個塊能與前一個塊合併。
逐個將數放入佇列中,每次增加的塊數是 \(O(1)\) 的,減少的塊數的總和是 \(O(n)\) 的,這題就做完了。
\(Code\)
#include <bits/stdc++.h> #define LL long long using namespace std; const int N=1e6+10; const int inf=1e9+10; int read(){ int x=0,f=1;char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();} while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();} return x*f; } void print(LL x){ if(x>9) print(x/10); putchar(x%10+'0'); } int n,d,mx; LL h[N]; int q[N]; int main(){ scanf("%d",&n); for(int i=1;i<=n;++i) scanf("%I64d",&h[i]); int tp=1;q[1]=1; LL nd,ud; int x; for(int i=2;i<=n;++i){ while(1){ if(h[i]==h[q[tp]]){ q[++tp]=i; break; } if(h[i]==h[q[tp]]+1){ q[tp]=i; break; } if(tp>=2){ nd=q[tp]-q[tp-1]; if(h[q[tp]]+1<=h[i]-nd){ h[i]-=nd; h[q[tp]]++; q[tp-1]=q[tp]; --tp; } else{ ud=h[i]-(h[q[tp]]+1); q[tp-1]+=ud; q[tp]=i; h[i]=h[i]-ud; break; } } else { nd=q[tp]; if(h[q[tp]]+1<=h[i]-nd){ ud=(h[i]-h[q[tp]])/(LL)(nd+1); h[q[tp]]+=ud; h[i]-=ud*nd; } else{ ud=h[i]-(h[q[tp]]+1); q[tp]=ud; q[tp+1]=i; ++tp; h[i]=h[i]-ud; break; } } } } --tp; for(int i=n-1;i>=1;--i){ h[i]=h[i+1]-1; if(tp>0&&q[tp]==i){ ++h[i]; --tp; } } for(int i=1;i<=n;++i){ print(h[i]);putchar(' '); } puts(""); return 0; }
G Omkar and Pies
題意
給兩個長度為 \(k\) 的01串,一個是初始串,一個是目標串。現在有 \(n\) 個操作,每個操作有兩個引數 \(a_i\) 和 \(b_i\),表示交換當前串第 \(a_i\) 和第 \(b_i\) 位置上的數。現在要求按順序使用一個區間的所有操作,使得初始串經變化後與目標串相同的位儘量多。要求使用的區間長度不小於 \(m\) 。問最多相同幾位,以及在此情況下選擇的區間。\(2\leq k \leq 20,1\leq n,m\leq {10}^{6} , 1\leq a_i,b_i \leq k\) 。
題解
我當時要是做出這題就紅了QAQ。。我當時一直在確定使用區間後怎麼快速得到變化後的串。。然後就崩了。
題解的方法很巧妙,首先有個正確性顯然的結論:將初始串和目標串同時進行一樣的操作,相同的位數不變。
這樣的話在初始串做完區間 \([l,r]\)
單這樣仍然不好做,因為我們不能像做字首和碼洋快速得到變化後相同位數。
於是在初始串做完區間 \([l,r]\) 的 變換之後,兩串同時倒著做 \([1,r]\) 的變換。
相當於初始串只倒著做了 \([1,l-1]\) 的變換,而目標串倒做了 \([1,r]\) 的變換。
此時我們列舉 \(l,r\) 就能快速得到要算最多相同位數的兩個串了。但現在答案還是不好統計。
設初始串中1的數量為 \(u\) , 目標串中1的個數為 \(v\) ,兩串變化後相同的1的個數為 \(w\) , 兩串變化後相同位數 \(x\).
於是有 \(x=k-u-v+w*2\) ,由於 \(u,v,k\) 都是已知的常數,所以相同的1的個數越大,則相同位數越大。
而快速得到最大的相同1的個數的區間可以使用狀壓dp在 \(O(k2^{k})\) 做完。
\(Code\)
#include <bits/stdc++.h>
#define LL long long
using namespace std;
const int N=3e5+10;
const int inf=1e8;
int read(){
int x=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
return x*f;
}
void print(LL x){
if(x>9) print(x/10);
putchar(x%10+'0');
}
int n,m,K;
char s[30];
int a[30],b[30],p[30],bin[30];
int aa[30],bb[30];
int cnt[(1<<20)+10];
int dp[2][(1<<20)+10];
int getnum(int *c){
int re=0;
for(int i=K;i>=1;--i) re=re<<1|c[i];
return re;
}
void umin(int &x,int y){if(x>y)x=y;}
void umax(int &x,int y){if(x<y)x=y;}
int main(){
for(int i=0;i<=(1<<19);++i){
cnt[i<<1]=cnt[i];
cnt[i<<1|1]=cnt[i]+1;
}
scanf("%d%d%d",&n,&m,&K);
scanf("%s",s+1);
for(int i=1;i<=K;++i) a[i]=s[i]-'0';
scanf("%s",s+1);
for(int i=1;i<=K;++i) b[i]=s[i]-'0';
for(int i=0;i<=(1<<20);++i) dp[0][i]=inf;
for(int i=0;i<=(1<<20);++i) dp[1][i]=-inf;
umin(dp[0][getnum(a)],0);
umax(dp[1][getnum(b)],0);
for(int i=1;i<=K;++i) p[i]=i;
int l,r;
for(int i=1;i<=n;++i){
scanf("%d%d",&l,&r);
swap(p[l],p[r]);
for(int j=1;j<=K;++j){
aa[p[j]]=a[j];
bb[p[j]]=b[j];
}
//for(int j=1;j<=K;++j) cout<<aa[j]<<" ";puts("");
//for(int j=1;j<=K;++j) cout<<bb[j]<<" ";puts("");
umin(dp[0][getnum(aa)],i);
umax(dp[1][getnum(bb)],i);
}
for(int i=(1<<K)-1;i>0;--i){
for(int j=0;j<K;++j){
if(i&(1<<j)){
umin(dp[0][i-(1<<j)],dp[0][i]);
umax(dp[1][i-(1<<j)],dp[1][i]);
}
}
}
int ans=-1;
for(int i=0;i<(1<<K);++i){
if(cnt[i]>ans&&dp[1][i]-dp[0][i]>=m){
ans=cnt[i];
l=dp[0][i]+1;
r=dp[1][i];
}
}
ans=K+ans+ans;
for(int i=1;i<=K;++i) ans=ans-a[i]-b[i];
cout<<ans<<endl<<l<<" "<<r<<endl;
return 0;
}
H ZS Shuffles Cards
題意
有 \(n+m\) 張牌,其中有 \(m\) 張鬼牌,剩餘 \(n\) 張牌有編號 \(1\) 到 \(n\) 。
每次洗牌之後,在牌堆從上到下按順序取出牌,若牌不是鬼牌,把這張牌的編號放入一個集合 \(S\) 裡;若牌是鬼牌,則考慮 \(S\) 中是否已經包含 \(1\) 到 \(n\) 所有數字,若已經包含,則停止遊戲,否則重新洗牌開始新的一輪。每次取出一張牌所需時間1秒,其他操作不消耗時間。問結束時期望需要多少秒。答案對 \(998244353\) 取模。\(1\leq n,m\leq 2 \cdot {10}^{6}\) 。
題解
首先設 \(f(x)\) 為已經還剩 \(x\) 個不同的數沒獲得,期望再過 \(f(x)\) 後結束。
列舉下一輪得到新的 \(l\) 個數,且下一輪一共翻了 \(i\) 張數字牌。得到式子:
\(f(x)\) \(=\) \(\sum_{l=0}^{x}{\sum_{i=0}^{n}{ {x \choose l} {n-x \choose i-l} \cdot i! \cdot m \cdot \frac{(n+m-i-1)!}{(n+m)!} \cdot (f(x-l)+i+1)}}\)
為了方便計算,將式子分成兩部分分別計算。。
\(A(x)\) \(=\) \(\sum_{l=0}^{x}{\sum_{i=0}^{n}{ {x \choose l} {n-x \choose i-l} \cdot i! \cdot m \cdot \frac{(n+m-i-1)!}{(n+m)!} \cdot (i+1)}}\)
\(B(x)\) \(=\) \(\sum_{l=0}^{x}{\sum_{i=0}^{n}{ {x \choose l} {n-x \choose i-l} \cdot i! \cdot m \cdot \frac{(n+m-i-1)!}{(n+m)!} \cdot f(x-l)}}\)
\(f(x)\) \(=\) \(A(x)+B(x)\)
然後就是推式子。
\(A(x)\) \(=\) \(\frac{m \cdot x! \cdot (n-x)!}{(n+m)!} \cdot \sum_{l=0}^{x}{ \frac{(m-1+x-l)! \cdot (l+1)}{(x-l)!} \cdot \sum_{i=0}^{n}{\frac{(i+1)!}{(l+1)! \cdot (i-l)!} \cdot \frac{(n+m-i-1)!}{(n-x-i+l)! \cdot (m-1+x-l)!} }}\)
\(A(x)\) \(=\) \(\frac{m \cdot x! \cdot (n-x)!}{(n+m)!} \cdot \sum_{l=0}^{x}{ \frac{(m-1+x-l)! \cdot (l+1)}{(x-l)!} \cdot \sum_{i=0}^{n}{ {i+1 \choose l+1} \cdot {n+m-i-1 \choose m-1+x-l} }}\)
\(A(x)\) \(=\) \(\frac{m \cdot x! \cdot (n-x)!}{(n+m)!} \cdot \sum_{l=0}^{x}{ \frac{(m-1+x-l)! \cdot (l+1)}{(x-l)!} \cdot {n+m+1 \choose m+x+1} }\) 這裡用到了 \(\sum_{i}{i \choose k}{n-i \choose m-k}\) \(=\) \({n+1 \choose m+1}\) 這條公式,詳見本部落格的數學的專題總結。。
\(A(x)\) \(=\) \(\frac{m \cdot x! \cdot (n-x)!}{(n+m)!} \cdot {n+m+1 \choose m+x+1} \cdot \sum_{l=0}^{x}{ \frac{(m+l-1)! \cdot (x-l+1)}{l!} }\)
後面的 \(\sum_{l=0}^{x}{ \frac{(m+l-1)! \cdot (x-l+1)}{l!} }\) 可以另外dp,轉移可以做到 \(O(1)\)
用類似的方法推 \(B(x)\)
\(B(x)\) \(=\) \(=\) \(\frac{m \cdot x! \cdot (n-x)!}{(n+m)!} \cdot \sum_{l=0}^{x}{ \frac{(m-1+x-l)! \cdot f(x-l)}{(x-l)!} \cdot \sum_{i=0}^{n}{\frac{i!}{l! \cdot (i-l)!} \cdot \frac{(n+m-i-1)!}{(n-x-i+l)! \cdot (m-1+x-l)!} }}\)
\(B(x)\) \(=\) \(\frac{m \cdot x! \cdot (n-x)!}{(n+m)!} \cdot {n+m \choose m+x} \cdot \sum_{l=0}^{x}{ \frac{(m+l-1)! \cdot f(l)}{l!} }\)
\(B(x)\) \(=\) \(\frac{m \cdot x!}{(m+x)!} \cdot \sum_{l=0}^{x}{ \frac{(m+l-1)! \cdot f(l)}{l!} }\)
類似BZOJ1426收集郵票,這個 \(B(x)\) 的dp式中會有 \(f(x)\) ,因為有可能一輪過去一個新數都沒有。
可以取出 \(B(x)\) 中係數有 \(f(x)\) 的項,移到等式左邊,然後再除回右邊,這裡會用到快速冪。所以總的效率為 \(O(nlgP)\)
\(Code\)
#include <bits/stdc++.h>
#define LL long long
using namespace std;
const int N=2e6+10;
const int inf=1e8;
const LL P=998244353;
int read(){
int x=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
return x*f;
}
void print(LL x){
if(x>9) print(x/10);
putchar(x%10+'0');
}
LL n,m;
LL jc[N*3],inv[N*3];
LL f[N],A[N],B[N];
LL T[N],G[N],H[N];
void add(LL &x,LL y){
x+=y;if(x>=P)x-=P;
}
LL qpow(LL x,LL y){
LL re=1;
while(y){
if(y&1) re=re*x%P;
x=x*x%P;y>>=1;
}
return re;
}
int main(){
jc[0]=jc[1]=inv[0]=inv[1]=1;
for(LL i=2;i<=N*2;++i){
jc[i]=jc[i-1]*i%P;
inv[i]=(P-P/i)*inv[P%i]%P;
}
for(LL i=2;i<=N*2;++i){
inv[i]=inv[i-1]*inv[i]%P;
}
cin>>n>>m;
T[0]=jc[m-1];G[0]=jc[m-1];
for(LL i=0;i<n;++i){
G[i+1]=G[i];add(G[i+1],jc[m+i]*inv[i+1]%P);
T[i+1]=T[i];add(T[i+1],G[i+1]);
}
for(LL i=1;i<=n;++i){
A[i]=m*jc[i]%P*(n+m+1)%P*inv[m+i+1]%P*T[i]%P;
B[i]=m*jc[i]%P*inv[m+i]%P*H[i-1]%P;
f[i]=(A[i]+B[i])%P*qpow((P+1-m*inv[m+i]%P*jc[m-1+i]%P)%P,P-2)%P;
H[i]=H[i-1];add(H[i],f[i]*inv[i]%P*jc[m+i-1]%P);
}
cout<<f[n]<<endl;
return 0;
}
I Kevin and Grid
題意
題解
\(Code\)