1. 程式人生 > 其它 >noip模擬36

noip模擬36

A. Dove 打撲克

由於每次合併都會減少一堆,所以哪怕最終每一堆個數都不一樣,最多隻有 \(\sqrt{n}\)
所以可以得出結論不同大小的堆的個數最多 \(\sqrt{n}\)

那麼把這些存進數組裡,只要保證每次操作是根號的,就可以保證在 \(m\sqrt{n}\) 的複雜度完成
統計答案時,可以用雙指標維護,配合字尾和預處理,還可以保證根號複雜度


B. Cicada 與排序

對於每一個數處理其最終在每個位置的概率,再乘位置即可算出期望
\(g[i]\) 表示這個樹到 \(i\) 位置的概率
考慮模擬歸併排序的過程進行遞迴(只遞迴當前數該去的半個區間)
這樣需要維護上一層向這一層的 \(g\)

陣列的轉移

首先維護一個 \(f\) 輔助 \(dp\) 值,\(f[i][j]\) 表示排序合併左右區間的時候左邊的選到第 \(i\) 個數,此時右邊選到第 \(j\) 個數的概率,這個很好轉移,\(f[i][j]=f[i-1][j]+f[i][j-1]\) 即可

考慮 \(g\) 通過 \(f\) 進行轉移
對於 \(g[i+j]\),如果從 \(f[i][j]\) 轉移會有一個問題:不能保證當前時刻選的是左區間的點
所以應該改為 \(f[i-1][j]/2\)

這樣還有一個問題,如果右邊已經到了盡頭,那麼其實左邊向下一個移動的概率不再是 \(\frac{1}{2}\) 而變成了 \(1\)


那麼相當於 \(\sum f[k][j-1]/2\)

這樣總複雜度是 \(n^3\)

程式碼實現
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int maxn=505;
const int mod=998244353;
int n,f[maxn][maxn],g[maxn],h[maxn],a[maxn],b[maxn],posl,posr,pos[maxn],ans[maxn],inv2;
int read(){
	int x=0,f=1;
	char ch=getchar();
	while(!isdigit(ch)){
		if(ch=='-')f=-1;
		ch=getchar();
	}
	while(isdigit(ch)){
		x=x*10+ch-48;
		ch=getchar();
	}
	return x*f;
}
int po(int a,int b=mod-2){
	int ans=1;
	while(b){
		if(b&1)ans=ans*a%mod;
		a=a*a%mod;
		b>>=1;
	}
	return ans;
}
void solve(int l,int r,int pos,int w){
	if(l==r){
		g[l]=1;
		return ;
	}
	for(int i=l;i<=r;i++)b[i]=a[i];
	sort(b+l,b+r+1);
	b[l-1]=0;
	b[r+1]=0;
	int st=0,ed=0;
	for(int i=l;i<=r;i++){
		if(b[i]!=b[i-1]&&b[i]==w)st=i;
		if(b[i]!=b[i+1]&&b[i]==w)ed=i;
	}
//	if(w==3)cout<<st<<" "<<ed<<endl;
	int mid=l+r>>1;
	if(pos<=mid){
		solve(l,mid,pos,w);
		for(int i=l;i<=mid;i++)b[i]=a[i];
		sort(b+l,b+mid+1);
		b[l-1]=b[mid+1]=0;
		for(int i=l;i<=mid;i++){
			if(b[i]!=b[i-1]&&b[i]==w)posl=i;
			if(b[i]!=b[i+1]&&b[i]==w)posr=i;
		}
		int num=0;
		for(int i=mid+1;i<=r;i++)if(a[i]==w)num++;
		
		
		for(int i=0;i<=posr-posl;i++){
			for(int j=0;j<=num-1;j++){
				h[st+i+j]=(h[st+i+j]+g[posl+i]*f[i][j]%mod*inv2)%mod;
			}
			int sum=0;
			if(!num)sum=1;
			else{
				for(int j=0;j<=i;j++)sum=(sum+f[j][num-1])%mod;
				sum=sum*inv2%mod;
			}
			h[st+i+num]=(h[st+i+num]+g[i+posl]*sum)%mod;
		}
		
		
		for(int i=l;i<=r;i++)g[i]=0;
		for(int i=st;i<=ed;i++){
			g[i]=h[i],h[i]=0;
//			if(pos==1)cout<<l<<" "<<r<<" "<<i<<" "<<g[i]<<endl;
		}
	}
	else{
		solve(mid+1,r,pos,w);
		for(int i=mid+1;i<=r;i++)b[i]=a[i];
		sort(b+mid+1,b+r+1);
		b[mid]=b[r+1]=0;
		for(int i=mid+1;i<=r;i++){
			if(b[i]!=b[i-1]&&b[i]==w)posl=i;
			if(b[i]!=b[i+1]&&b[i]==w)posr=i;
		}
		
		int num=0;
		for(int i=l;i<=mid;i++)if(a[i]==w)num++;
		
		
		for(int i=0;i<=posr-posl;i++){
			for(int j=0;j<=num-1;j++){
				h[st+i+j]=(h[st+i+j]+g[posl+i]*f[i][j]%mod*inv2)%mod;
//				if(pos==5)cout<<"hhh "<<st+i+j<<" "<<h[st+i+j]<<endl;
			}
			int sum=0;
			if(!num)sum=1;
			else{
				for(int j=0;j<=i;j++)sum=(sum+f[j][num-1])%mod;
				sum=sum*inv2%mod;
			}
			h[st+i+num]=(h[st+i+num]+g[i+posl]*sum)%mod;
//			if(pos==5)cout<<"ppp "<<st+i+num<<" "<<h[st+i+num]<<" "<<i<<" "<<sum<<endl;
		}
		
		
//		if(w==4)cout<<"ggg "<<l<<" "<<r<<" "<<st<<" "<<ed<<" "<<h[st]<<endl;
		for(int i=l;i<=r;i++)g[i]=0;
		for(int i=st;i<=ed;i++){
			g[i]=h[i],h[i]=0;
//			if(pos==5)cout<<"ggg "<<l<<" "<<r<<" "<<i<<" "<<g[i]<<endl;
		}
	}
	return ;
}
void pre(){
	f[0][0]=1;
	for(int i=1;i<=n;i++)f[i][0]=f[i-1][0]*inv2%mod,f[0][i]=f[0][i-1]*inv2%mod;
	for(int i=1;i<=n;i++){
		for(int j=1;j<=n;j++){
			f[i][j]=(f[i-1][j]+f[i][j-1])*inv2%mod;
//			cout<<i<<" "<<j<<" "<<f[i][j]<<endl;
		}
	}
	return ;
}
signed main(){
//	freopen("sort101.in","r",stdin);
//	freopen("my.out","w",stdout);
	n=read();
	for(int i=1;i<=n;i++){
		a[i]=read();
	}
	inv2=po(2);
	pre();
	for(int i=1;i<=n;i++){
		memset(g,0,sizeof g);
		memset(h,0,sizeof h);
		solve(1,n,i,a[i]);
		int ans=0;
		for(int j=1;j<=n;j++){
//			if(i==1)cout<<g[j]<<" ";
			ans=(ans+j*g[j])%mod;
		}
		cout<<ans<<" ";
	}
	return 0;
}

C. Cicada 拿衣服

非常神奇的一道題

首先注意對於單個數 \(OR-AND=XOR\),但對於多個數不是這樣的

用到一個性質,序列裡字首與和或的和最多變化 \(logn\) 次(因為每一位最多一次,不可能往回變)

再觀察當右端點固定時,當左端點往左延伸時,\(max\) 單調不減,\(min\) 單調不增