【ZJOI2016】小星星

题面

https://www.luogu.org/problem/P3349

题解

很好听的题目名字,很优美的思路。

设$f[x][y][S]$为树上的$x$点对应图上的$y$点,$x$的子树对应了图上$S$的子集。枚举子集转移即可。

但是这样大概是$O(3^nn^3)$的,是跑不过去的。

$mbox{Gloid}$爷说如果状态记录了集合,转移就必须是$O(3^n)$了,考虑优化,

设$f[x][y]$为树上的$x$点对应图上的$y$点,但是这个状态图上的点和树上的点并不一一对应。一些点没有标号,一些标号对应了很多点。

可以容斥一下,只考虑$S$集合的点的标号,$+/-$交替即可。

写起来出奇好写,可是感觉不是那么好理解呢。

#include<vector>
#include<cstdio>
#include<cstring>
#include<iostream>
#define ri register int
#define N 18
#define LL long long
using namespace std;

int n,m;
int has[N][N];
vector<int> to[N];

inline int read() {
  int ret=0,f=0; char ch=getchar();
  while (ch<'0' || ch>'9') f|=(ch=='-'),ch=getchar();
  while (ch>='0' && ch<='9') ret*=10,ret+=ch-'0',ch=getchar();
  return f?-ret:ret;
}

int v[1<<(N-1)];
LL f[N][N],ans=0;
void dfs(int s,int x,int ff) {
  for (ri i=1;i<=n;i++) if (s&(1<<(i-1))) f[x][i]=1;
  for (ri i=0;i<to[x].size();i++) {
    int y=to[x][i]; if (y==ff) continue;
    dfs(s,y,x);
    for (ri j=1;j<=n;j++) if (s&(1<<(j-1))) {
      LL sum=0;
      for (ri j2=1;j2<=n;j2++) if (has[j][j2]) sum+=f[y][j2];
      f[x][j]*=sum;
    }
  }
}

LL calc(int s) {
  memset(f,0,sizeof(f));
  dfs(s,1,1);
  LL ret=0; 
  for (ri i=1;i<=n;i++) ret+=f[1][i];
  return ret;
}

int main() {
  n=read(); m=read();
  for (ri i=1;i<=m;i++) {
    int u=read(),v=read();
    has[u][v]=1,has[v][u]=1;
  }
  for (ri i=1;i<n;i++) {
    int u=read(),v=read();
    to[u].push_back(v);
    to[v].push_back(u);
  }
  v[0]=0;
  for (ri i=1;i<(1<<n);i++) {
    v[i]=v[i-(i&(-i))]+1;
    if (v[i]%2==n%2) ans+=calc(i); else ans-=calc(i);
  }
  cout<<ans<<endl;
}
原文地址:https://www.cnblogs.com/shxnb666/p/11805358.html