竞赛图

  前置知识:竞赛图是一个有向图,满足任意两个不同的点间都有且仅有一条有向边相连。
  首先有一个40分做法,就是枚举每一个点集用\(tarjan\)判一下是不是强联通即可。
  然后考虑将判强联通改为判割边,可以免去弹栈,常数小一点,得分60。
  但这就是\(tarjan\)的极限了,正解是\(O(2^n)\)
  考虑状压,可以发现一个不联通的图一定存在一个联通的子图,这个子图缩点后只有向外的出边。
  因此可以发现,如果我们处理出一个联通子图\(S\)所有点出边的交集\(T\),那么对于\(T\)的任意子集\(R\),都有\(S\)\(R\)是不强联通的。
  对于处理\(T\),可以枚举\(S\)利用\(lowbit\)实现线性递推。

Code
#include<bits/stdc++.h>
using namespace std;
namespace STD
{
	#define rr register
	typedef long long ll;
	const int M=604;
	const int N=26;
	int n,t;
	int to[N],reco[1<<24],reach[1<<24];
	bool c[1<<24];
	int read()
	{
		rr int x_read=0,y_read=1;
		rr char c_read=getchar();
		while(c_read<'0'||c_read>'9')
		{
			if(c_read=='-') y_read=-1;
			c_read=getchar();
		}
		while(c_read<='9'&&c_read>='0')
		{
			x_read=(x_read<<3)+(x_read<<1)+(c_read^48);
			c_read=getchar();
		}
		return x_read*y_read;
	}
};
using namespace STD;
int main()
{
	t=read();
	for(rr int i=1;i<=24;i++)
		reco[1<<(i-1)]=i;
	while(t--)
	{
		memset(c,1,sizeof c);
		memset(to,0,sizeof to);
		n=read();
		for(rr int i=1;i<=n;i++)
			for(rr int j=1;j<=n;j++)
			{
				int a=read();
				if(a) to[i]|=1<<(j-1);
			}
		for(rr int i=1;i<=n;i++)
			reach[1<<(i-1)]=to[i];
		for(rr int i=3;i<(1<<n);i++)
			if(i!=(i&(-i)))
				reach[i]=reach[i-(i&(-i))]&reach[i&(-i)];
		int ans=0;
		for(rr int i=1;i<(1<<n);i++)
		{
			if(!c[i]) continue;
			for(rr int j=reach[i];j;j=(j-1)&reach[i])
				c[i|j]=0;
			ans++;
		}
		printf("%d\n",ans+1);
	}
}
/*
	只有一个点的子图是强联通的。
	可以作为起点更新其他子集
*/

  一开始这道题被卡成了\(TLE80\)原因是没有用\(lowbit\)线性递推,之前在考场上用过这个东西实现递推,但是这次忘了,可以发现,对于大规模集合预处理的操作,都可以利用这个实现线性递推,从而降低复杂度。

posted @ 2021-08-15 06:11  Geek_kay  阅读(204)  评论(0编辑  收藏  举报