竞赛图
前置知识:竞赛图是一个有向图,满足任意两个不同的点间都有且仅有一条有向边相连。
首先有一个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\)线性递推,之前在考场上用过这个东西实现递推,但是这次忘了,可以发现,对于大规模集合预处理的操作,都可以利用这个实现线性递推,从而降低复杂度。