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,除錯過程會極其複雜,
我先放放,因為昨天做這個的時候做懵了。。。