您的位置:首页 > 其它

【BZOJ4455】小星星,容斥原理+树形DP

2017-01-08 20:21 423 查看
传送门

思路:

我一开始想的是考虑i个点不合法来容斥

显然不科学

因为一个点合不合法是取决于其它点的位置的……

答案实际上求的是“在[1,n]中,没有重复编号的合法方案数”(感觉这步转化很好啊)

所以应该考虑对某些点不限制重复编号,然后进行容斥

按例考虑放宽限制

总的合法方案即“没有点不能编号的合法方案”-“有一个点({1}{2}{3}{4}..{n})不能编号的合法方案数”+有两个点({1,2}{1,3}..)不能编号的合法方案数”….

后面这一坨是2^n的

定义f(S)是用集合S中元素对[1,n]编号,对每个元素的使用次数不加限制;g(S)是用集合S中元素对[1,n]编号,但每个元素至少用一次(无上限)

那么有f(S)=∑T⊆Sg(T)

由容斥原理的一般形式可知

g(S)=∑T⊆S(−1)|S|−|T|f(T)

对于这个式子我们让S是全集U就可以得到答案了

枚举子集T然后暴力DP计算,f[i][j]表示i编号为j,往子树转移时枚举子树和本身的编号,比较简单就不细说了

复杂度O(2nn3)

需要卡一下常(本机跑完全图1.1s->0.8s)

总结一下,尽可能从容斥原理的一般化考虑问题,感性理解,大胆猜想,公式求证;

对问题的转化能力太弱,应当转化为数学模型,从简单明了的问题入手,发现限制或条件不能处理时多变换思路,别走死胡同,少绕弯子

代码:

#include<cstdio>
#include<vector>
#define LL long long
using namespace std;
int n,m,id[20];
vector<int>e[20];
bool ok[20][20],vis[20];
LL f[20][20],tot,ans;
void dp(int x,int fa)
{
LL sum;
for (int i=1;i<=id[0];++i)
if (!vis[id[i]]) f[x][id[i]]=1;
for (int v,i=0;i<e[x].size();++i)
{
v=e[x][i];
if (v==fa) continue;
dp(v,x);
for (int j=1;j<=id[0];++j)
if (!vis[id[j]]&&f[x][id[j]])
{
sum=0;
for (int k=1;k<=id[0];++k)
if (!vis[id[k]]&&ok[id[j]][id[k]])
sum+=f[v][id[k]];
f[x][id[j]]*=sum;
}
}
}
void dfs(int x,int sum)
{
if (x==n+1)
{
dp(1,0);
tot=0;
for (int i=1;i<=id[0];++i)
tot+=f[1][id[i]];
if (sum&1) ans-=tot;
else ans+=tot;
return;
}
id[++id[0]]=x;
dfs(x+1,sum);
--id[0];
vis[x]=1;
dfs(x+1,sum+1);
vis[x]=0;
}
main()
{
scanf("%d%d",&n,&m);
for (int u,v,i=1;i<=m;++i)
scanf("%d%d",&u,&v),
ok[u][v]=ok[v][u]=1;
for (int u,v,i=1;i<n;++i)
scanf("%d%d",&u,&v),
e[u].push_back(v),
e[v].push_back(u);
dfs(1,0);
printf("%lld",ans);
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: