队伍统计

Posted on 2019-02-03 09:47  Mocking_Jimmy  阅读(121)  评论(0编辑  收藏  举报

1月27日

Description

现在有n个人要排成一列,编号为1->n 。但由于一些不明原因的关系,人与人之间可能存在一些矛盾关系,具体有m条矛盾关系(u,v),表示编号为u的人想要排在编号为v的人前面。要使得队伍和谐,最多不能违背k条矛盾关系(即不能有超过k条矛盾关系(u,v),满足最后v排在了u前面)。问有多少合法的排列。答案对 \(10^9+7\) 取模。

Input

输入文件名为count. in。
第一行包括三个整数n,m,k。
接下来m行,每行两个整数u,v,描述一个矛盾关系(u,v)。
保证不存在两对矛盾关系(u,v),(x,y),使得u=x且v=y 。

Output

输出文件名为count.out。
输出包括一行表示合法的排列数。

Sample Input

输入1:

4 2 1
1 3
4 2

输入2:

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

Sample Output

输出1:

18

输出2:

123120

Data Constraint

对于30%的数据,\(n\leq 10\)

对于60%的数据,\(n\leq 15\)

对应100%的数据,n, \(k\leq 20\) ,\(m\leq n \times (n-1)\),保证矛盾关系不重复。

Solution

  • 【我的解法】

无脑暴力,30分,可以欣赏一下

#include<cstdio>
#pragma GCC optimize(3)//咳咳,别管这是什么
using namespace std;
const int mod=1000000007;

int n,m,k,tot;
int g[21][21];
int a[21];
bool vis[21];

void dfs(int x)
{
	if(x>n)
	{
		int cnt=0;
		for(int i=1;i<n;++i)
			for(int j=i+1;j<=n;++j)
				cnt+=g[a[i]][a[j]];
		if(cnt<=k)
		{
			tot++;
			tot%=mod;
		}
		return;
	}
	for(int i=1;i<=n;i++)
	{
		if(vis[i]) continue;
		a[x]=i;
		vis[i]=true;
		dfs(x+1);
		vis[i]=false;
	}
}

int main()
{
	freopen("count.in","r",stdin);
	freopen("count.out","w",stdout);
	scanf("%d%d%d",&n,&m,&k);
	for(int i=1,u,v;i<=m;i++)
	{
		scanf("%d%d",&u,&v);
		g[v][u]=1;
	}
	dfs(1);
	printf("%d",tot);
	fclose(stdin);
	fclose(stdout);
	return 0;
}
  • 【大佬正解】

首先把状态压缩一下,把队伍压缩成一个二进制,由于n最大只有20,所以 \(2^{20}\) 是不会MLE的。
然后从前往后递推。f(s,i)表示当前选了s集合这些人,i表示违反了多少条矛盾关系。
然后如果暴力转移的话,是会炸的。 \(O(2^nn^2k)\)。所以要进行一些非(sang)常(xin)基(bing)础(kuang)的优化。
因为矛盾是没有重复的,所以把要在x前面的连个链式前向星,然后往左位移再&一下就可以了。

(其实有更优方法,但我不会调。。。只要维护一个fal[x]表示要排在x后面的人的集合,那么每次只要求出s & fal[x]的1的个数就能快速更新𝑖了。)

#include<cstdio>
//#pragma GCC optimize(3)//不敢了不敢了再也不敢吸臭氧了
#define mod 1000000007
using namespace std;

int n,m,k,tot,ans;
int f[2097153][21];
int head[21],to[401],next[401],dive;

void add(int x,int y)
{
	++dive;
	next[dive]=head[x];
	to[dive]=y;
	head[x]=dive;
}
inline int read()//不吸臭氧就只能快读了QAQ
{
	int x=0; char c=getchar();
	while (c<'0' || c>'9') c=getchar();
	while (c>='0' && c<='9')
	{
		x=(x<<1)+(x<<3)+(c^48);
		c=getchar();
	}
	return x;
}

int main()
{
	freopen("count.in","r",stdin);
	freopen("count.out","w",stdout);
	n=read();
	m=read();
	k=read();
	for(int i=1,u,v;i<=m;i++)
	{
		u=read();
		v=read();
		add(v,u);
	}
	f[0][0]=1;
	tot=(1<<n)-1;
	for(int s=0;s<=tot;s++)
		for(int i=0;i<=k;i++)
		{
			if(!f[s][i]) continue;
			for(int x=1;x<=n;x++)
			{
				if(s&(1<<x-1)) continue;
				int c=0;
				for(int p=head[x];p;p=next[p])
					if(!(s&(1<<to[p]-1))) c++;
				if(i+c>k) continue;
				f[s|(1<<x-1)][i+c]+=f[s][i];
				f[s|(1<<x-1)][i+c]%=mod;
			}
		}
	for(int i=0;i<=k;i++)
	{
		ans+=f[tot][i];
		ans%=mod;
	}
	printf("%d\n",ans);
	fclose(stdin);
	fclose(stdout);
	return 0;
}