1. 程式人生 > 其它 >noip模擬20[玩具·y·z]

noip模擬20[玩具·y·z]

\(noip模擬20\;solutions\)

這次考了105分,主要是第一題之前我做過但是沒調出來,掛掉40pts,就挺傷心的,

最重要的是最後一題我現在還沒調出來,aaa

以後一定有時間就調他,一定要調出來,補好這個坑!!!

·

\(T1\;玩具\)

就這個題我一眼就知道是一棵樹隨意連邊,求期望深度

第二眼就想起來我之前做過

第三眼就是我忘記咋做了

然後就苦思冥想,想也想不到哦啊。。。

最後還是有了一點點思路,可是有一個邊界卡錯了,最後還是隻拿了暴力分

其實那也不是正解,只能拿到70pts;

\(O(n^4)\) 1節點原本就在那裡,2節點一定會接在1節點上,所以我們所有的節點不是在1子樹上就是在2子樹上

我們分類討論,先討論最大深度在1這個子樹上,在討論在2子樹上的時候

注意有可能1,2子樹的最大深度相等,這種情況只能算進其中一種裡,如果兩種都算了這個那就多了

這是統計方案數,從之前的深度轉移過來,乘上組合數

\(f[i][j]\)表示i個點組成的樹最大深度為j的方案數,轉移就是:

最大深度在1子樹:

\(\huge f[i][j]=\sum\limits^{i-1}_{k=1}\sum\limits^{min(k-1,j-2)}_{x=1}f[k][j]×f[i-k][x]×C^{k-1}_{i-2}\)

為啥是這樣,我將深度相等的情況放在下一種中處理,所以我們這個時候的最大值一定在1子樹,所以f[k][j];

2子樹的深度必須小於j-1,所以x從1迴圈到j-2,組合數代表從i-2個數中選k-1個因為1,2已經固定了

最大深度在2子樹,1子樹的最大深度可以等於2子樹的:

\(\huge f[i][j]=\sum\limits^{i-1}_{k=1}\sum\limits^{min(k-1,j)}_{x=1}f[k][j-1]×f[i-k][x]×C^{k-1}_{i-2}\)

這個和上面是一樣的。。。

70pts
#include<bits/stdc++.h>
using namespace std;
#define re register int
#define ll long long
const int N=205;
ll jc[N];
ll yh[N][N];
ll n,mod;
ll f[N][N];
ll ksm(ll x,ll y){
	ll ret=1;
	while(y){
		if(y&1)ret=ret*x%mod;
		x=x*x%mod;
		y>>=1;
	}
	return ret;
}
signed main(){
	scanf("%lld%lld",&n,&mod);
	jc[1]=1;for(re i=2;i<n;i++)jc[i]=jc[i-1]*i%mod;
	for(re i=1;i<=n;i++){
		yh[i][0]=1;
		yh[i][i]=1;
		for(re j=1;j<i;j++){
			yh[i][j]=(yh[i-1][j-1]+yh[i-1][j])%mod;
		}
	}
	f[1][0]=1;f[2][1]=1;
	for(re i=3;i<=n;i++){
		for(re j=1;j<i;j++){
			for(re k=1;k<=i-1;k++){//1
				for(re x=0;x<=min(k-1,j-2);x++){
					f[i][j]=(f[i][j]+f[k][j]*f[i-k][x]%mod*yh[i-2][k-1]%mod)%mod;
				}
			}
			for(re k=1;k<=i-1;k++){//2
				for(re x=0;x<=j;x++){
					f[i][j]=(f[i][j]+f[k][j-1]*f[i-k][x]%mod*yh[i-2][k-1]%mod)%mod;
				}
			}
		}
	}
	ll ans=0;
	for(re i=1;i<n;i++){
		ans=(ans+1ll*i*f[n][i]%mod)%mod;
	}
	ans=ans*ksm(jc[n-1],mod-2)%mod;
	printf("%lld",ans);
}

·

下面的才是正解,\(O(n^3)\),它這個思路極其的叼鑽,極其的不好想,但是好理解

那麼這個正解是如何得到的呢?

首先我們將這個問題轉化為求每一種深度的概率,而這個概率可以用\(n^3\)來求

我們要找到一棵樹深度為i的概率,想一想我們找不到所有樹的形態,但是所有形態其實就是一個森林

當我們把所有的邊都連上,他就變成了一顆樹,而且這個過程中可以轉移dp,就是正解的思路

這個讓我明白了,整個不好求就拆開求

程式碼有細節,注意當深度大於節點數的時候要賦值為1

AC_code
#include<bits/stdc++.h>
using namespace std;
#define re register int
#define ll long long
const int N=205;
ll n,mod,jc=1,ans;
ll inv[N];
ll dp[N][N],f[N][N],g[N][N];
ll ksm(ll x,ll y){
	ll ret=1;
	while(y){
		if(y&1)ret=ret*x%mod;
		x=x*x%mod;
		y>>=1;
	}
	return ret;
}
signed main(){
	scanf("%lld%lld",&n,&mod);
	inv[1]=1;for(re i=2;i<=n;i++){
		inv[i]=ksm(i,mod-2);
	}
	dp[1][1]=1;
	for(re i=2;i<=n;i++){
		//cout<<i<<"    ";
		for(re j=1;j<=i;j++){
			dp[i][j]=(dp[i-1][j-1]*(j-1)%mod*inv[i]%mod+dp[i-1][j]*(i-j)%mod*inv[i]%mod)%mod;
			//cout<<dp[i][j]<<" ";
		}
		//cout<<endl;
	}
	for(re i=0;i<=n;i++)f[1][i]=1;
	for(re i=0;i<=n;i++)g[0][i]=1;
	for(re i=1;i<=n;i++){
		for(re j=0;j<i;j++){
			for(re k=1;k<=i;k++){
				g[i][j]=(g[i][j]+f[k][j]*g[i-k][j]%mod*dp[i][k]%mod)%mod;
			}
			f[i+1][j+1]=g[i][j];
			for(re k=j+1;k<=n;k++)f[i+1][k]=f[i+1][j+1];
			if(j==i-1){
				for(re k=i;k<=n;k++)g[i][k]=g[i][j];
			}
			//f[i+1][j+1]=g[i][j];
		}
	}
	//cout<<f[1][1]<<" "<<f[2][1]<<endl;
	for(re i=1;i<n;i++){
		ans=(ans+i*(f[n][i]+mod-f[n][i-1])%mod)%mod;
		//cout<<f[n][i]<<endl;
	}
	//cout<<inv[jc]<<endl;
	printf("%lld",ans);
}

·

\(T2\;y\)

這個題我搞到了60pts,如何搞到的,用trie樹

我用trie樹記錄每一位後面目前有多少種,就是它的siz

還是利用dfs每掃到一條邊,我們就把它加入到trie樹中相應的位置,如果此時這個位置已經是一顆滿二叉樹了,就可以break

複雜度沒法算,但是比一般的暴力快。。。

60pts
#include<bits/stdc++.h>
using namespace std;
#define re register int
const int N=95;
int n,m,d;
int to[N*N*2],nxt[N*N*2],val[N*N*2],head[N],rp;
int sz[N];
void add_edg(int x,int y,int z){
	to[++rp]=y;
	val[rp]=z;
	nxt[rp]=head[x];
	head[x]=rp;
}
struct TRIE{
	int siz[1<<21],son[1<<21][2],fa[1<<21];
	int seg;
	TRIE(){seg=1;}
	void pushup(int x){
		if(!x)return ;
		siz[x]++;
		pushup(fa[x]);
	}
	int ins(int x,int v,int dep){
		if(siz[son[x][v]]==sz[dep])return 0;
		//cout<<"ins "<<x<<" "<<v<<" "<<dep<<endl;
		if(!son[x][v]){
			son[x][v]=++seg;
			fa[seg]=x;
		}
		if(dep==d){
			//cout<<"ok"<<" "<<x<<endl;
			siz[son[x][v]]=1;
			pushup(x);
		}
		return son[x][v];
	}
}t;
void dfs(int x,int f,int rt,int dep){
	if(dep>d)return ;
	//cout<<x<<" "<<f<<" "<<rt<<" "<<dep<<endl;
	for(re i=head[x];i;i=nxt[i]){
		int y=to[i];
		int pd=t.ins(rt,val[i],dep);
		if(!pd)continue;
		dfs(y,x,pd,dep+1);
	}
}
signed main(){
	scanf("%d%d%d",&n,&m,&d);
	int flag=0;
	for(re i=1,x,y,z;i<=m;i++){
		scanf("%d%d%d",&x,&y,&z);
		add_edg(x,y,z);
		add_edg(y,x,z);
		if(z!=0)flag=1;
	}
	if(m==0){
		printf("0");
		return 0;
	}
	for(re i=1;i<=d;i++){
		sz[i]=1<<(d-i);
	}
	//cout<<t.seg<<endl;
	dfs(1,0,1,1);
	printf("%d",t.siz[1]);
}

·

正解竟然是---深搜,沒錯就是dfs

設f[i][j][s]表示有一條從i出發,從j結束的,狀態為s的路徑,

我知道你會告訴我,第一維根本沒有用,直接從1開始搜不就完事了,確實可以,可是你只有21pts

MEET IN THE MIDDLE這是個好思想,整個做不了就直接給它拆掉,我們重新定義一下陣列,根據cty的思想

設f[i][j][s]表示走了i步,走到了j,狀態為s

因為我們的起點是1,而終點沒有要求,我們只需要列舉到\(\frac{d}{2}\)就好了,

具體是先給f[0][1][0]賦值為真,\(O(2^{\frac{d}{2}}nm)\)找到第\(\frac{d}{2}\)步的終點,

因為終點是沒有要求的,第二次我們從後往前找,所有的f[0][i][0]都賦值為真,

所以最後我們三層迴圈\(O(2^dn)\)複雜度遍歷一下就好了

不要用深搜,因為這是圖,深搜的最劣複雜度為\(O(n^d)\),直接爆炸

為什麼迴圈的複雜度較小??,因為有許多重複的狀態都放到一起了

AC_code
#include<bits/stdc++.h>
using namespace std;
#define re register int 
const int N=95;
int n,m,d;
int to[N*N*2],nxt[N*N*2],val[N*N*2],head[N],rp;
void add_edg(int x,int y,int z){
	to[++rp]=y;
	val[rp]=z;
	nxt[rp]=head[x];
	head[x]=rp;
}
int f[25][N][1<<12],f1[N][1<<12],f2[N][1<<12];
signed main(){
	scanf("%d%d%d",&n,&m,&d);
	for(re i=1,x,y,z;i<=m;i++){
		scanf("%d%d%d",&x,&y,&z);
		add_edg(x,y,z);
		add_edg(y,x,z);
	}
	int ans=0,nd=d+1>>1,vd=d>>1;
	//memset(f,-1,sizeof(f));
	f[0][1][0]=true;
	for(re i=0;i<nd;i++){
		for(re x=1;x<=n;x++){
			for(re j=head[x];j;j=nxt[j]){
				int y=to[j];
				for(re s=0;s<(1<<i);s++){
					f[i+1][y][(s<<1)|val[j]]|=f[i][x][s];
				}
			}
		}
	}
	for(re i=1;i<=n;i++){
		for(re s=0;s<(1<<nd);s++){
			f1[i][s]=f[nd][i][s];
			//if(f1[i][s])cout<<i<<" "<<s<<endl;
		}
	}
	memset(f,false,sizeof(f));
	for(re i=1;i<=n;i++)f[0][i][0]=true;
	for(re i=0;i<vd;i++){
		for(re x=1;x<=n;x++){
			for(re j=head[x];j;j=nxt[j]){
				int y=to[j];
				for(re s=0;s<(1<<i);s++){
					f[i+1][y][(s<<1)|val[j]]|=f[i][x][s];
				}
			}
		}
	}
	for(re i=1;i<=n;i++){
		for(re s=0;s<(1<<vd);s++){
			f2[i][s]=f[vd][i][s];
			//if(f2[i][s])cout<<i<<" "<<s<<endl;
		}
	}
	for(re i=0;i<(1<<nd);i++){
		for(re j=0;j<(1<<vd);j++){
			for(re k=1;k<=n;k++){
				if(f1[k][i]&&f2[k][j]){
					ans++;break;
				}
			}
		}
	}
	printf("%d",ans);
}

·

\(T3\;z\)

這個我到如今還沒有做,只會一個小暴力

大體思路我會了,但是這個是需要判斷起始位置和終止位置的

這個判斷極其的麻煩,

要用到map+priority_queue,除錯過程會極其複雜,

我先放放,因為昨天做這個的時候做懵了。。。