您的位置:首页 > 其它

【清华集训2014】【BZOJ3812】主旋律

2016-04-03 14:28 267 查看
Description

响应主旋律的号召,大家决定让这个班级充满爱,现在班级里面有 n 个男生。

如果 a 爱着 b,那么就相当于 a 和 b 之间有一条 a→b 的有向边。如果这 n 个点的图是强联通的,那么就认为这个班级是充满爱的。

不幸的是,有一些不好的事情发生了,现在每一条边都可能被摧毁。我作为爱的使者,想知道有多少种摧毁的方式,使得这个班级任然充满爱呢?(说人话就是有多少边的子集删去之后整个图仍然强联通。)

Input

第一行两个数 n 和 m,表示班级里的男生数和爱的关系数。

接下来 m 行,每行两个数 a 和 b,表示男生 a 爱着男生 b。同时 a 不等于 b。

所有男生从 1 到 n 标号。

同一条边不会出现两遍,但可能出现 a 爱着 b,b 也爱着 a 的情况,这是两条不同的边。

Output

输出一行一个整数,表示对 109+7 取模后的答案。

Sample Input

5 15

4 3

4 2

2 5

2 1

1 2

5 1

3 2

4 1

1 4

5 4

3 4

5 3

2 3

1 5

3 1

Sample Output

9390

HINT

对于 100% 的数据满足: n≤15,0≤m≤n(n−1)。

Source

2015年国家集训队测试

一道很传统的题目.

首先很容易想到是状压DP.

题目所求的东西,可以转化为一个问题:”原图有多少个子图是强连通的.”

这个问题本身不太好做,但是我们可以考虑从他的对立面”多少子图是不强连通的”入手.

显然如果一个子图是不强连通的,那么他缩点后一定是一个节点数目大于等于2的DAG.

所以可以用总方案数减掉能划分出缩点后节点数目大于等于2的方案数,只要枚举有多少点点集缩点后是一个DAG,然后DP就行了.

既然选取的点集缩点后一定是个DAG,那么接下来的问题就变成了:给定一张有向图,有多少种选边方案使得选出来的子图构成一个DAG.

那么我们考虑枚举一个点集V,枚举出度为0的点集V’.然后可以容斥+DP.容斥过程中可以发现,如果V’中选的点缩完是奇数个点,对答案贡献是正的,否则是负的.然后只要算出奇数个点的方案减去偶数个点的方案就行了.

然而到这里还是只能在原题得70分…最后还要预处理一个 点集划分成奇数个没有出度的点减去偶数个没有出度的点 的方案数.

我的题解可能比较碎而且看不懂..可以看Po姐的代码里的注释,更加直接..

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#define P 1000000007
#define MAXN 16
#define GET (ch>='0'&&ch<='9')
using namespace std;
int n,m;
int bit[1<<MAXN],use[1<<MAXN],tmp[1<<MAXN];
int f[1<<MAXN],g[1<<MAXN],h[1<<MAXN];
int In[MAXN],Out[MAXN];
int Pow[MAXN*MAXN];
inline void in(int &x)
{
char ch=getchar();x=0;
while (!GET)    ch=getchar();
while (GET) x=x*10+ch-'0',ch=getchar();
}
int main()
{
in(n);in(m);Pow[0]=1;int u,v;
for (int i=1;i<=n*n;i++)    Pow[i]=(Pow[i-1]<<1)%P;
for (int i=1;i<(1<<n);i++)  bit[i]=bit[i-(i&-i)]+1;
for (int i=1;i<(1<<n);i++)  for (int j=0;j<n;j++)   if (i&(1<<j))   use[i]=j;
for (int i=1;i<=m;i++)  in(u),in(v),u--,v--,Out[u]|=1<<v,In[v]|=1<<u;
f[0]=1;g[0]=P-1;h[0]=1;
for (int i=1;i<(1<<n);i++)
{
if (bit[i]==1)  {   f[i]=g[i]=h[i]=1;continue;  }
int sum=0;//edge number
for (int j=0;j<n;j++)   if (i&(1<<j))   sum+=bit[Out[j]&i];
h[i]=f[i]=Pow[sum];g[i]=0;
int x=use[i],V=i-(1<<x);//choose a node named x, other nodes named set V
for (int j=V;;(--j)&=V)
{
int now=j|(1<<x);
if (now<i)  ((g[i]-=1ll*f[now]*g[i-now]%P)+=P)%=P;
if (j==0)   break;
}
int Sum=0;
for (int j=i;j;(--j)&=i)
{
if (j==i)   tmp[j]=0;
else
{
int x=use[i-j],now=j|(1<<x);
tmp[j]=tmp[now]-bit[Out[x]&(i-now)]+bit[In[x]&j];
}
int edgecnt=tmp[j],t=1ll*g[j]*h[i-j]%P*Pow[edgecnt]%P;
(Sum+=t)%=P;
}
((f[i]-=Sum)+=P)%=P;(g[i]+=f[i])%=P;
}
cout<<f[(1<<n)-1]<<endl;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息