luogu P3349 [ZJOI2016]小星星
阿新 • • 發佈:2021-08-05
題面傳送門
似乎是很平凡的idea了啊,而且這道題也不卡常啊。
容易想到一個暴力dp,就是設\(dp_{i,j,k}\)表示\(i\)點,有了\(j\)的狀態,當前點為\(k\)的方案數,這樣子是列舉子集要\(O(n^33^n)\)的過不掉。
然後接下來這個idea大概出自[SHOI2016]黑暗前的幻想鄉(也不能說出自因為是同一年考的),就是列舉一個集合,這個集合內的數隨便取,最後答案是\(\sum\limits_{i=1}^{2^n}{f_{i}[|i|=n]}-\sum\limits_{i=1}^{2^n}{f_{i}[|i|=n-1]}+\sum\limits_{i=1}^{2^n}{f_{i}[|i|=n-2]}……\)
然後這個看上去很不可過所以考慮有什麼辦法快一點,考慮每次將列舉好的子集拿出來而不到dp的時候再判斷這樣可以將\(2^n\)變成\(2^{n-2}\)然後就可以過了。
code:
#include<bits/stdc++.h> #define I inline #define max(a,b) ((a)>(b)?(a):(b)) #define min(a,b) ((a)<(b)?(a):(b)) #define abs(x) ((x)>0?(x):-(x)) #define re register #define ll long long #define db double #define N 17 #define mod 998244353 #define eps (1e-5) #define U unsigned int #define it iterator #define Gc() getchar() #define Me(x,y) memset(x,y,sizeof(x)) #define d(x,y) (n*(x-1)+(y)) using namespace std; int n,m,k,x,y,A[N+5][N+5],B[N+5],Bh,Pus;ll Ans[N+5],dp[N+5][N+5],ToT; struct yyy{int to,z;}; struct ljb{int head,h[N+5];yyy f[N+5<<1];I void add(int x,int y){f[++head]=(yyy){y,h[x]};h[x]=head;}}s; I void dfs(int x,int last){ yyy tmp;re int i,j,h;for(i=1;i<=Bh;i++)dp[x][i]=1;for(i=s.h[x];i;i=tmp.z){ tmp=s.f[i];if(tmp.to==last) continue;dfs(tmp.to,x);for(j=1;j<=Bh;j++){ for(ToT=0,h=1;h<=Bh;h++) A[B[j]][B[h]]&&(ToT+=dp[tmp.to][h]);dp[x][j]*=ToT; } } } int main(){ freopen("1.in","r",stdin); re int i,j;scanf("%d%d",&n,&m);for(i=1;i<=m;i++) scanf("%d%d",&x,&y),A[x][y]=A[y][x]=1;for(i=1;i<n;i++) scanf("%d%d",&x,&y),s.add(x,y),s.add(y,x);for(i=0;i<(1<<n);i++){ for(Bh=0,j=1;j<=n;j++)(i>>j-1)&1&&(B[++Bh]=j);Me(dp,0);dfs(1,0);for(j=1;j<=Bh;j++) Ans[Bh]+=dp[1][j];//printf("%d %lld %d\n",Bh,Ans[Bh],i); }for(Pus=1,ToT=0,i=n;i;i--,Pus*=-1)ToT+=Pus*Ans[i];printf("%lld\n",ToT); }