【洛谷P3349】[ZJOI2016]小星星
阿新 • • 發佈:2021-10-30
【洛谷傳送門】
借鑑了 \(\mathtt{wind\_whisper}\) 的思路,他的【部落格傳送門】(不過我的程式碼寫的比他好看多了)
題解
先不提及優化,一開始本人並沒有設計出樸素 DP。(或許有些浮躁?)
設計 DP
看到資料範圍還有題目裡面玄學的對應關係,可以想到狀壓,用一維表示已被對應的節點狀態。
考慮對於每一個轉移的父子點對 \(u,v\),由於轉移的條件就是有連邊,所以 \(u,v\) 對應的點在原圖中一定有連邊,轉移的時候把 \(u\) 當前子樹和 \(v\) 子樹的狀態合併(也就是邏輯或),就可以順利轉移。
因此,設計 \(dp(u,stat,p)\) 表示 \(u\) 節點對應的節點是 \(p\)
優化 DP
可以用容斥優化,但是比較難。(先挖坑)
- 最簡單的想法,基於 DP 本身的狀態,每個子樹對應的狀態裡 \(1\) 的個數一定等於子樹大小,預處理出來可以省去大量列舉。
- 其次,列舉子樹的時候類比樹形揹包,每次轉移完 DP 之後再
siz[u]+=siz[v]
。 - 基本就這些,還可以有一點點無聊的優化,比如在 DP 值為 \(0\) 的時候直接跳出迴圈。
吸氧能夠卡過,最大點約 \(850ms\)
程式碼
#include<bits/stdc++.h> using namespace std; #define ll long long const int INF = 0x3f3f3f3f,N = 20; inline ll read() { ll ret=0;char ch=' ',c=getchar(); while(!(c>='0'&&c<='9')) ch=c,c=getchar(); while(c>='0'&&c<='9') ret=(ret<<1)+(ret<<3)+c-'0',c=getchar(); return ch=='-'?-ret:ret; } int n,m,head[N],ecnt=-1; bool mp[N][N]; inline void init_edge(){memset(head,-1,sizeof(head)),ecnt=-1;} struct edge { int nxt,to; }a[N<<1]; inline void add_edge(int x,int y) { a[++ecnt]=(edge){head[x],y}; head[x]=ecnt; } ll dp[N][1<<18][N],siz[N]; int num[N][1<<18],cnt[N]; void dfs(int u,int fa) { siz[u]=1; for(int i=1;i<=n;i++) dp[u][1<<(i-1)][i]=1LL; //初始化,u對應任何一個點方案都為1 for(int i=head[u];~i;i=a[i].nxt) { int v=a[i].to; if(v==fa) continue; dfs(v,u); for(int j=1;j<=cnt[siz[u]];j++) for(int k=1;k<=cnt[siz[v]];k++) { int stat1=num[siz[u]][j]; int stat2=num[siz[v]][k]; if(stat1&stat2) continue; for(int p=1;p<=n;p++) if((1<<(p-1))&stat2) { if(!dp[v][stat2][p]) continue; for(int q=1;q<=n;q++) { if(mp[q][p]&& ((1<<(q-1))&stat1) ) dp[u][stat1|stat2][q]+=dp[u][stat1][q]*dp[v][stat2][p]; } } } siz[u]+=siz[v]; } } int main() { init_edge(); n=read(),m=read(); for(int i=1;i<=m;i++) { int u=read(),v=read(); mp[u][v]=mp[v][u]=1; } for(int i=1;i<n;i++) { int u=read(),v=read(); add_edge(u,v),add_edge(v,u); } for(int i=0;i<1<<n;i++) { int tcnt=0; for(int j=1;j<=n;j++) if((1<<(j-1))&i) tcnt++; num[tcnt][++cnt[tcnt]]=i; } dfs(1,-1); ll ans=0ll; for(int i=1;i<=n;i++) ans+=dp[1][(1<<n)-1][i]; printf("%lld\n",ans); return 0; }