Description
现在有n个人要排成一列,编号为1->n 。但由于一些不明原因的关系,人与人之间可能存在一些矛盾关系,具体有m条矛盾关系(u,v),表示编号为u的人想要排在编号为v的人前面。要使得队伍和谐,最多不能违背k条矛盾关系(即不能有超过k条矛盾关系(u,v),满足最后v排在了u前面)。问有多少合法的排列。答案对10^9+7取模。
--------------------------------------
有1~n个小学生排队抢宵夜,由于一些f**k
原因,m条矛盾关系(u,v),表示编号为u的小学生想要排在编号为v的人前面否则打架,不能使k队小学生打架,求合法序列的方案数mod 1e9+7
input&output
第一行包括三个整数n,m,p。
接下来m行,每行两个整数u,v,描述一个矛盾关系(u,v)。
保证不存在两对矛盾关系(u,v),(x,y),使得u=x且v=y 。
输出包括一行表示合法的排列数。
数据范围&提示
对于30%的数据,n<=10
对于60%的数据,n<=15
对应100%的数据,n,k<=20,m<=n*(n-1),保证矛盾关系不重复。
样例
in
10 12 3
2 6
6 10
1 7
4 1
6 1
2 4
7 6
1 4
10 4
10 9
5 9
8 10
out
123120
solution
30%
暴力dfs,全排列然后计算方案数
60%
暴力加剪枝
100%
状压dp,即状态压缩动态规划
FAQ:
从何看出?
对应100%的数据,n,k<=20,m<=n*(n-1),保证矛盾关系不重复。
所以我们可以通过枚举
2
n
2^n
2n 次来压缩1~n个小学生选还是不选的情况
通俗地讲,设
1
≤
i
≤
2
n
1\leq i\leq 2^n
1≤i≤2n ,那么i转换二进制后就是
10011010100100…
1表示选,0表示不选,还是不会就->_->位运算——百度百科
所以设
F
i
,
j
F_{i,j}
Fi,j 为
i
i
i这个状态下,已经有
j
j
j对小学生打架的方案数总和
那么就有 f[i][j] 转移到f[i or
2
k
−
1
2^{k-1}
2k−1][j+tot](前提是k这个人并不在i这个状态内并且j+tot
≤
\leq
≤p)
k表示这时候如果加进来一个人k,tot表示加进来k后会多几个小学生打架
那么就有转移方程
dp完了
答案就是
上代码
tips:in C++,__builtin_popcount(x)
可快速计算x的二进制下有多少个1
有关C++__builtin_popcount(x)的用法戳
兄dei,不是c++的快转吧,**s*al会错的
#include <cstdio>
#define ll long long
#define cnt(x) __builtin_popcount(x)//定义cnt(x)等价于__builtin_popcount(x)
#define mod (int)(1e9+7)
#define open(x) freopen(x".in","r",stdin);freopen(x".out","w",stdout);
using namespace std;
ll n,m,K,f[1<<20][30],g[1<<20][30],fal[50],cnt[1<<20],ans=0,max=0;
int main()
{
//open("count");
freopen("data4.in","r",stdin);
scanf("%lld%lld%lld",&n,&m,&K);
for(int i=1;i<=m;++i)
{
ll x,y;scanf("%lld%lld",&x,&y);
fal[x]|=(1<<(y-1));//和i同样的方法,把和u号小学生打架的人压缩成状态存进fal[u]
}
f[0][0]=1;max=(1<<n);
for(int i=0;i<max;++i)
for(int j=0;j<=K;++j)
if(f[i][j])
for(int k=1;k<=n;++k)
{
ll sum=(1<<k-1);
if((i&sum)==0)
{
ll tot=cnt(fal[k]&i);//fal[k] and i表示状态i中有多少个和k打架的小学生
//cnt函数快速统计and后有多少个1
if(j+tot<=K)(f[i|sum][j+tot]+=f[i][j])%=mod;
}
}
for(int i=0;i<=K;++i)(ans+=f[max-1][i])%=mod;
printf("%lld",ans);
return 0;
}