1. 程式人生 > 實用技巧 >Codeforces Global Round 10(CF1392)

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]\) 的變換,就抵消了初始串的所有變化。
單這樣仍然不好做,因為我們不能像做字首和碼洋快速得到變化後相同位數。
於是在初始串做完區間 \([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\)