题目大意:
有一张n个点,m条边的有向图,有多少个子图(选定一个边集)是没有环的。答案对1e9+7取模。( n≤17 n ≤ 17 )
思路:
话说这个题目作为NOIP的模拟题有点过了,毕竟
n≤17
n
≤
17
的方法不是太好想(一定我是太菜了)。
先考虑
n≤10
n
≤
10
的方法,构造一个dag图,一个好的方法就是可以根据拓扑序来分层,一个图的拓扑序一定是唯一的,所以我们可以一层一层地添加点集进去。将一开始入度为0的点放第一层,将这些点去掉,剩下出现的新的入度为0的点为第二层…以此类推,用f[i][j]表示总共选的点集和最后一层的点集就好了。注意转移的时候要满足新一层的拓扑序要全部都在原来的后面,即一定要满足有边连向上一层的点。
/*=========================
* Author : ylsoi
* Problem : obelisk
* Algorithm : DP
* Time : 2018.5.23
* =======================*/
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<climits>
#include<vector>
using namespace std;
void File(){
freopen("obelisk.in","r",stdin);
freopen("obelisk.out","w",stdout);
}
template<typename T>bool chkmax(T &_,T __){return _<__ ? (_=__,1) : 0;}
template<typename T>bool chkmin(T &_,T __){return _>__ ? (_=__,1) : 0;}
#define REP(i,a,b) for(register int i=a;i<=b;++i)
#define DREP(i,a,b) for(register int i=a;i>=b;--i)
#define MREP(i,x) for(register int i=beg[x];i;i=E[i].last)
#define mem(a) memset(a,0,sizeof(a))
#define ll long long
#define inf INT_MAX
const int maxn=17+10;
const ll mod=1e9+7;
int n,m;
vector<int>from[maxn];
ll dp[(1<<10)+10][(1<<10)+10],all,ans;
ll qpow(ll x,int b){
ll base=x,ret=1ll;
while(b){
if(b&1)ret=ret*base%mod;
base=base*base%mod;
b>>=1;
}
return ret;
}
void add(ll &_,ll __){_=((_+__)%mod+mod)%mod;}
void mul(ll &_,ll __){_=(_*__%mod+mod)%mod;}
bool in(int x,int S){return (1<<(x-1))&S;}
void out(int x){
if(!x)return;
out(x/2);
printf("%d",x%2);
}
int main(){
File();
scanf("%d%d",&n,&m);
REP(i,1,m){
int u,v;
scanf("%d%d",&u,&v);
from[v].push_back(u);
}
all=(1<<n)-1;
REP(i,1,all)dp[i][i]=1ll;
REP(i,1,all){
for(int j=i;j;j=(j-1)&i){
if(!dp[i][j])continue;
if(i==all)add(ans,dp[i][j]);
int left=all-i;
for(int k=left;k;k=(k-1)&left){
bool flag=true;
ll mul1=1ll,mul2=1ll;
REP(v,1,n){
if(!in(v,k))continue;
bool flag1=false;
int size=from[v].size()-1,cnt1=0,cnt2=0;
REP(p,0,size){
int u=from[v][p];
if(in(u,i) && !in(u,j))++cnt1;
if(in(u,j)){flag1=true;++cnt2;}
}
if(!flag1){
flag=false;
break;
}
mul(mul1,qpow(2,cnt1));
mul(mul2,qpow(2,cnt2)-1);
}
if(!flag)continue;
add(dp[i|k][k],dp[i][j]*mul1%mod*mul2%mod);
}
}
}
printf("%lld\n",ans);
return 0;
}
好像这个方法就已经很不错了,但是并不满足我们的需要,考虑怎么把第二维给去掉。设f[i]表示选择了点集i的方案总数,自然是要一层一层地转移(要不然就会出错),但是依然按照拓扑序来分层地话就会难以实现,因为没有记录上一层状态的缘故,新添加的点不一定是拓扑序中最后面的。那么我们便按照出度为0的规则来添加点集,即出度为0的点是在新添加的这一层的。
设集合
i
i
为当前的状态,枚举一个的补集的子集
j
j
,两个集合的并集中,有前面定义的最后一层
k
k
,因为是刚刚添加进去的且只有从后面来的边,所以必有
j⊆k
j
⊆
k
。对于
i|j
i
|
j
这个集合的答案,一定是等于所有的
k
k
的状态的和,显然地,如果用这种方法计数,每一个都会被添加进去多次,就是每一个
k
k
的子集都会代表
k
k
一次,所以我们在相加的时候多乘以一个容斥系数即可,这样就可以保证每一个
k
k
都只会被计算一次。
时间复杂度,好像还是不够快,再优化我就不会了。。。
/*=========================
* Author : ylsoi
* Problem : obelisk
* Algorithm : dp
* Time : 2018.5.23
* ======================*/
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<climits>
#include<vector>
using namespace std;
void File(){
freopen("obelisk_better.in","r",stdin);
freopen("obelisk_better.out","w",stdout);
}
#define REP(i,a,b) for(register int i=a;i<=b;++i)
#define DREP(i,a,b) for(register int i=a;i>=b;--i)
#define MREP(i,x) for(register int i=beg[x];i;i=E[i].last)
#define mem(a) memset(a,0,sizeof(a))
#define ll long long
#define inf INT_MAX
const int maxn=17+5;
const ll mod=1e9+7;
int n,m,all,c[(1<<17)+10][maxn];
bool dis[maxn][maxn];
ll dp[(1<<17)+10];
void add(ll &_,ll __){_=((_+__)%mod+mod)%mod;}
void mul(ll &_,ll __){_=(_*__%mod+mod)%mod;}
ll qpow(ll x,int b){
ll base=x,ret=1ll;
while(b){
if(b&1)mul(ret,base);
mul(base,base);
b>>=1;
}
return ret;
}
bool in(int x,int S){return (1<<(x-1))&S;}
void init(){
scanf("%d%d",&n,&m);
all=(1<<n)-1;
REP(i,1,m){
int u,v;
scanf("%d%d",&u,&v);
dis[u][v]=1;
}
REP(i,1,all){
REP(v,1,n){
if(!in(v,i))continue;
REP(u,1,n)c[i][u]+=dis[u][v];
}
}
}
void work(){
dp[0]=1ll;
int siz,cnt;
REP(i,0,all){
if(!dp[i])continue;
int left=all-i;
for(register int j=left;j;j=(j-1)&left){
siz=__builtin_popcount(j),cnt=0;
REP(u,1,n)if(in(u,i))cnt+=c[j][u];
add(dp[i|j],dp[i]*qpow(2,cnt)%mod*qpow(-1,siz+1)%mod);
}
}
printf("%lld\n",dp[all]);
}
int main(){
File();
init();
work();
return 0;
}