衝刺 CSP 聯訓模擬2

Pursuing_OIer發表於2024-10-05

題面

溫馨提示

程式碼中的變數名可能與題意、題解不同。
程式碼不刪預設源,可以直接拿來對拍。

T1 擠壓

Solution

眾所周知,異或是一種按位運算,不好進行十進位制的數間合併。我們考慮將每個數拆分為二進位制的形式進行處理。
此時,對於每一種情況,假設表示 \(2^i\) 二進位制位的值為 \(b_i\),我們的答案(平方)的形式應為:

\[\left( \sum_{i=0}^{i<30}{2^ib_i}\right)^2=\sum_{i=0}^{i<30}{\sum_{j=i+1}^{j<30}{2^{i+j+1}b_ib_j}}+\sum_{i=0}^{i<30}{2^{2i}b_i} \]

這相當於對於第 \(i\) 位與第 \(j\) 位(\(i\leq{j}\)),對答案產生貢獻的應為這兩位是否同時為 \(1\)。對於本題,允許實現 \(\log^2\) 級別的演算法,所以考慮對於每個數,列舉每兩個數位,根據該數這兩位的情況確定這兩個數位在選擇是否異或該數後分別為 \(00\)\(01\)\(10\)\(11\) 的機率(等於期望)並帶入上面式子。

Optimise

考慮上面這個實現分討的量很大,我們考慮將第 \(i\)\(j\) 位是否為 \(1\) 的情況轉為 \(2b_i+b_j\),即將 \(00\) 的情況記作 \(0\)\(01\) 的情況記作 \(1\),以此類推。考慮將列舉到的數的這兩位的情況記作 \(t\),將已經得到的記作 \(i\) 的機率記為 \(dp_i\),則新的 \(dp_i\) 包含兩種情況:一種是根本沒有選到,從 \(dp_i\) 轉移,另一種是取到該數,從 \(dp_x\) 轉移,其中 \(x \operatorname{xor} t=i\)。根據異或的自反律,可得:

\[dp_i=dp_i(1-p)+dp_{i \operatorname{xor} t}p \]

其中 \(p\) 表示當前的數被選中的機率。

Code

程式碼($793$ ms $2.8$ Mib)
#include<bits/stdc++.h>
using namespace std;
const long long p=1000000007;
inline long long qpow(long long x,long long y){
	long long rtr=1;
	for(;y;y>>=1){
		if(y&1)
		rtr=rtr*x%p;
		x=x*x%p;
	}
	return rtr;
}
long long n,m,a[100100],tinv,ps[200100],ne[200100],dp[8][64][64],ans;
int main(){
	freopen("a.in","r",stdin);
	freopen("a.out","w",stdout);
	scanf("%lld",&n);
	tinv=qpow(1000000000,p-2);
	for(int i=1;i<=n;++i){
		scanf("%lld",&a[i]);
	}
	for(int i=1;i<=n;++i){
		scanf("%lld",&ps[i]);
		ps[i]=ps[i]*tinv%p;
		ne[i]=((1-ps[i])%p+p)%p;
	}
	for(int i=0;i<30;++i){
		for(int j=0;j<30;++j){
			dp[0][i][j]=1;
		}
	}
	int tb;
	long long tem[8];
	for(int i=1;i<=n;++i){
		for(int j=0;j<30;++j){
			for(int k=j;k<30;++k){
				tem[0]=dp[0][j][k];
				tem[1]=dp[1][j][k];
				tem[2]=dp[2][j][k];
				tem[3]=dp[3][j][k];
				tb=(((a[i]>>j)&1)<<1)|((a[i]>>k)&1);
				for(int l=0;l<4;++l){
					dp[l][j][k]=(tem[l]*ne[i]%p+tem[l^tb]*ps[i]%p)%p;
				}
			}
		}
	}
	for(int i=0;i<30;++i){
		for(int j=0;j<30;++j){
			if(i^j)
			ans=(ans+(((1ll<<(i+j))%p*dp[3][i][j]%p)<<1)%p)%p;
			else
			ans=(ans+(1ll<<(i+j))%p*dp[3][i][j]%p)%p;
		}
	}
	printf("%lld",ans);
	return 0;
}

T2 工地難題

Solution

假設不考慮 \(k\),相當於將 \(n-m\)\(0\) 插進 \(m\)\(1\),方案數為 \(\dbinom{n}{n-m}\)。考慮取出 \(1\)\(k+1\) 個點再加進去,也就是說欽定 \(1\) 組大於 \(k\),方案數為 \(\dbinom{n-k-1}{n-m}\dbinom{1}{n-m+1}\),即現將剩下的數插板再將欽定的放入 \(n-m\)\(0\) 之間的空中。然而我們發現,有其中一組在 \(k\)\(2k\) 之間的可以透過在全在剩下的中取或取一部分剩下的再加上欽定的被算重兩次,我們再欽定 \(2k+2\) 減去貢獻。以此類推進行容斥,最終最大連續段的長度小於等於\(k\) 的值為:

\[\sum_{i=0}^{i(k+1)\leq{m}}{(-1)^i \dbinom{n-i(k+1)}{n-m} \dbinom{n-m+1}{k}} \]

進行差分即可得到每個 \(k\) 的答案。
對於 \(1\leq{k}\leq{m}\),每一個 \(k\) 至多進行 \(O(\frac{n}{k})\) 級別的計算,總的複雜度為調和級數的,即 \(O(n\log{m})\)

證明

考慮將 \(1+\frac{1}{2}+\frac{1}{3}+\frac{1}{4}+\cdots+\frac{1}{m}\) 拆成 \(1+\frac{1}{2}+(\frac{1}{3}+\frac{1}{4})+(\frac{1}{5}+\frac{1}{6}+\frac{1}{7}+\frac{1}{8})+\cdots\),之後每一組的元素個數為上一組的兩倍,發現每一組的和都小於 \(1\),有對數級別的組數,乘上分子 \(n\) 得到 \(O(n\log{m})\)

Code

程式碼($45$ ms $7.7$ Mib)
#include<bits/stdc++.h>
using namespace std;
const long long p=1000000007;
long long n,m,t[400100],invt[400100],lans;
inline long long qpow(long long x,long long y){
	long long rtr=1;
	for(;y;y>>=1){
		if(y&1){
			rtr=rtr*x%p;
		}
		x=x*x%p;
	}
	return rtr;
}
inline long long c(long long x,long long y){
	if(x>y)
	return 0;
	return t[y]*invt[x]%p*invt[y-x]%p;
}
int main(){
	freopen("a.in","r",stdin);
	freopen("a.out","w",stdout);
	scanf("%lld%lld",&n,&m);
	t[0]=1;
	invt[0]=1;
	for(int i=1;i<=(n<<1);++i){
		t[i]=i*t[i-1]%p;
	}
	invt[n<<1]=qpow(t[n<<1],p-2);
	for(int i=(n<<1)-1;i;--i){
		invt[i]=(i+1)*invt[i+1]%p;
	}
	for(int i=1;i<=m;++i){
		long long ans=c(n-m,n);
		for(int j=m-i-1,k=1;j>=0;j-=i+1,++k){
			if(k&1)
			ans=((ans-c(n-m,j+n-m)*c(k,n-m+1)%p)%p+p)%p;
			else
			ans=(ans+c(n-m,j+n-m)*c(k,n-m+1)%p)%p;
		}
		printf("%lld ",((ans-lans)%p+p)%p);
		lans=ans;
	}
	return 0;
}

T3 星空遺蹟

Solution

我們先考慮對“最終勝者”操作進行化簡,可以發現以下性質:

  • 對於 \(AAAA\) ,只有前一個和後一個影響比較結果,如果後面大了前面所有的 \(A\) 也跟著變,所以 \(AAAA\) 等價於 \(A\)
  • 對於 \(ABA\) ,其中 \(A\) 能贏 \(B\),在第一局必定為 \(AAX\)\(X\) 表不確定。我們發現這個字串只要最後一個字母是 \(A\) 就不會影響他與後面比較的結果,即 \(X\) 不變,所以 \(ABA\) 等價於只有一個 \(A\)

至此,我們將某個字母前面是該字母或它能贏的字母的情況進行了化簡,得到一種穩定情況:某個字母前面是他贏不了的一個字母,反映到本題,則穩定的串一定為 \(RSPRSPRSPRSP\cdots\) 的一個子串。考慮維護一個單調棧,在棧內維護穩定結構,即噹噹前字母為 \(A\)\(A\)\(B\)\(C\)\(A\) 時:

  • 若棧為空,則將 \(A\) 入棧。
  • 若棧頂為 \(C\),加入 \(A\) 後依舊穩定,直接入棧。
  • 若棧頂為 \(A\),因為棧是穩定結構要麼只有 \(A\) 要麼 \(A\) 前面有 \(C\),只需彈出原來的 \(A\) 再入棧就穩定了。
  • 若棧頂為 \(B\),因為棧是穩定結構要麼只有 \(B\) 要麼 \(B\) 前面有 \(A\),只有 \(B\) 則清棧加 \(A\),否則按照上文第二個性質去掉 \(AB\) 並將 \(A\) 入棧。

容易發現,由於穩定結構每個字元都無法更新前一個字元,所以加入每個字元後“最終勝者”為此時棧頂字母。
以上操作放到本題複雜度為 \(O(nq)\),考慮將整個字串入棧,對某個區間查詢有以下情況:

  • 存在棧的大小為 \(1\),則最後一次出現這種情況的時候進棧的元素即為棧底。
  • 不存在這種情況,考慮忽略前面字元,最後一次棧最小時入棧元素要麼是第一個進來沒被消除,要麼就是清空了棧,所以他就是棧底。

現在考慮與上文相對應的,\(A\) 入棧是如何統計棧的大小。設上一次大小為 \(f_{i-1}\),此次為 \(f_i\)

  • 若棧為空,\(f_i=1\)
  • 若棧頂為 \(C\),直接入棧,\(f_i=f_{i-1}+1\)
  • 若棧頂為 \(A\),彈出 \(A\) 再入棧,\(f_i=f_{i-1}\)
  • 若棧頂為 \(B\),彈出 \(AB\) 再入棧,\(f_i=\max(f_{i-1}-1,1)\)

考慮對於 \(1\)\(\max\) 很麻煩,我們考慮在 \(f_{i-1}\leq{1}\) 時仍然減 \(1\),考慮如果出現連續減 \(1\),說明後加的優於前面的,取後面,也就是 \(f\) 較小的。而且可以發現,如果存在多個最小值,說明當前棧頂無法在被更新。問題轉化為:求區間內任意一個 \(f\) 最小的點的字元,即線段樹區間求最小值位置。
對於修改,我們考慮每次修改 \(i\) 位置只會影響 \(f_i-f_{i-1}\)\(f_{i+1}-f{i}\),這樣會影響後面所有的 \(f\),記錄每個 \(f_i-f_{i-1}\) 並據此區間修改 \(f\) 即可。

Code

程式碼($180$ ms $8.3$ Mib)
#include<bits/stdc++.h>
using namespace std;
struct node{
	int data[800100],pos[800100],tag[800100];
	inline void pushdown(int now){
		if(tag[now]){
			data[now<<1]+=tag[now];
			tag[now<<1]+=tag[now];
			data[now<<1|1]+=tag[now];
			tag[now<<1|1]+=tag[now];
			tag[now]=0;
		}
	}
	void build(int now,int lft,int rgt,int* dt){
		if(lft==rgt){
			data[now]=dt[lft];
			pos[now]=lft;
			return;
		}
		pushdown(now);
		int mid=(lft+rgt)>>1;
		build(now<<1,lft,mid,dt);
		build(now<<1|1,mid+1,rgt,dt);
		if(data[now<<1]<data[now<<1|1]){
			data[now]=data[now<<1];
			pos[now]=pos[now<<1];
		}else{
			data[now]=data[now<<1|1];
			pos[now]=pos[now<<1|1];
		}
	}
	void add(int now,int lft,int rgt,int ll,int rr,int dt){
		if(ll<=lft&&rgt<=rr){
			data[now]+=dt;
			tag[now]+=dt;
			return;
		}
		pushdown(now);
		int mid=(lft+rgt)>>1;
		if(ll<=mid)
		add(now<<1,lft,mid,ll,rr,dt);
		if(rr>mid)
		add(now<<1|1,mid+1,rgt,ll,rr,dt);
		if(data[now<<1]<data[now<<1|1]){
			data[now]=data[now<<1];
			pos[now]=pos[now<<1];
		}else{
			data[now]=data[now<<1|1];
			pos[now]=pos[now<<1|1];
		}
	}
	pair<int,int> query(int now,int lft,int rgt,int ll,int rr){
		if(ll<=lft&&rgt<=rr){
			return {data[now],pos[now]};
		}
		pushdown(now);
		bool vis=0;
		int mid=(lft+rgt)>>1;
		pair<int,int> rtr,tem;
		if(ll<=mid){
			rtr=query(now<<1,lft,mid,ll,rr);
			vis=1;
		}
		if(rr>mid){
			tem=query(now<<1|1,mid+1,rgt,ll,rr);
			if(vis){
				if(rtr.first>tem.first)
				rtr=tem;
			}else{
				rtr=tem;
			}
		}
		return rtr;
	}
}tree;
char a[200100];
int n,q,f[200100],s[200100];
inline int getv(char x){
	if(x=='R')
	return 2;
	if(x=='S')
	return 1;
	return 0;
}
inline int cmp(char x,char y){
	if(x==y)
	return 0;
	x=getv(x);
	y=getv(y);
	if((!x)&&y==2)
	return 1;
	if(x==2&&(!y))
	return -1;
	if(x>y)
	return 1;
	return -1;
}
int main(){
	freopen("a.in","r",stdin);
	freopen("a.out","w",stdout);
	scanf("%d%d %s ",&n,&q,a+1);
	f[1]=1;
	s[1]=1;
	for(int i=2;i<=n;++i){
		f[i]=cmp(a[i-1],a[i]);
		s[i]=f[i]+s[i-1];
	}
	tree.build(1,1,n,s);
	char tc;
	int ta,tb,td,tem;
	for(;q;--q){
		scanf("%d",&ta);
		if(ta==1){
			scanf("%d %c ",&tb,&tc);
			a[tb]=tc;
			if(tb^1){
				tem=cmp(a[tb-1],a[tb]);
				tree.add(1,1,n,tb,n,tem-f[tb]);
				f[tb]=tem;
			}
			if(tb^n){
				tem=cmp(a[tb],a[tb+1]);
				tree.add(1,1,n,tb+1,n,tem-f[tb+1]);
				f[tb+1]=tem;
			}
		}else{
			scanf("%d%d",&tb,&td);
			printf("%c\n",a[tree.query(1,1,n,tb,td).second]);
		}
	}
	return 0;
}

T4 紐帶

待更。

相關文章