1. 程式人生 > 其它 >luogu P3349 [ZJOI2016]小星星

luogu P3349 [ZJOI2016]小星星

題面傳送門
似乎是很平凡的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]}……\)

的樣子。這樣就可以\(O(n^32^n)\)轉移了。
然後這個看上去很不可過所以考慮有什麼辦法快一點,考慮每次將列舉好的子集拿出來而不到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);
}