题目
问题描述
给定一个 (n) 个点的竞赛图,求有多少个点的子集 (S) 满足 (S) 的诱导子图是强连通的。
竞赛图是一个有向图,满足任意两个不同的点之间均存在且仅存在一条有向边相连。
一个图在点集 (S) 的诱导子图的定义为:点集为 (S),边集为所有两端的点都在 (S) 中的边的子图。
一个图是强连通的,当且仅当该图中任意两个点 (x),(y),都满足以 (x) 为起点存在一条路径到达 (y)。
注意空集也是一个子集,且我们定义空集的诱导子图也是强连通的。
输入格式
输入第一行有一个正整数 (T),表示测试点组数。
对于每一个测试点,第一行输入一个数 (n),表示点的个数。
接下来 n 行,每行 n 个数,设其为 (a_{i,j}),如果 (a_{i,j}) ,则表示从 (i) 到 (j) 有一条有向边,否则表示没有。保证 (a_{i,i}) 且 (a_{i,j}hat{} a_{i,j}=1(i ot=j))
输出格式
输出有 (T) 行,对于每一个测试点输出其答案。
样例输入1
2
3
0 1 0
0 0 1
1 0 0
5
0 0 0 1 1
1 0 1 0 1
1 0 0 1 0
0 1 0 0 1
0 0 1 0 0
样例输出1
5
14
数据范围与约定
对于 (20\%) 的数据,满足 (n≤10)。
对于 (40\%) 的数据,满足 (n≤15)。
对于 (60\%) 的数据,满足 (n≤19)。
对于 (100\%) 的数据,满足 (1≤n≤24,1≤T≤10)。
时间限制:2s
空间限制:512MB
题解
20分暴力
怎么做都行。
40分暴力
考虑状压,枚举 (n) 个点的状态,之后用 (Floyed) 判断即可。
60分暴力
考虑优化上一个做法,状压感觉是没法优化了,但是我们可以优化 (Floyed) 。
不用对 (n) 个点都跑一边,只需要跑状态中出现过的点。
其实也可以去掉 (Floyed) ,设 (f[i]) 表示 (n) 个点状态是 (i) 时的合法情况。
对于每一个状态 (i) 考虑其去掉一个点 (x) 的合法状态 (i') (就是代码中的 (state))。
如果 (i') 里面的点有指向 (x) 的出边,并且 (x) 有指向 (i') 中任意一点的出边,那么 (i) 是一个合法状态。
注意特判三个点的情况。
#include<bits/stdc++.h>
using namespace std;
int T,n,dis[30][30],ans,road[30],_road[30],Log[16778000],id[30];
bool f[16778000];
inline int read()
{
int x=0,w=0;char ch=0;
while(!isdigit(ch)){w|=ch=='-';ch=getchar();}
while(isdigit(ch)){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
return w?-x:x;
}
bool check(int state,int x)
{
if(!state)return 1;
id[0]=0;
for(int i=state;i;i-=i&-i)
id[++id[0]]=Log[i&-i];
if(id[0]==1)return 0;
if(id[0]>2){
if(!f[state])return 0;
return (state&road[x])&&(state&_road[x]);
}
return (dis[id[1]][id[2]]&&dis[id[2]][x]&&dis[x][id[1]])||(dis[id[2]][id[1]]&&dis[x][id[2]]&&dis[id[1]][x]);
}
int main()
{
T=read();
for(int i=1;i<=24;i++)Log[1<<(i-1)]=i;
while(T--){
n=read();
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
dis[i][j]=read();
for(int i=1;i<=n;i++){
road[i]=_road[i]=0;
for(int j=1;j<=n;j++){
if(dis[i][j])road[i]+=1<<(j-1);
if(dis[j][i])_road[i]+=1<<(j-1);
}
}
for(int i=0,End=1<<n;i<End;i++)f[i]=0;
ans=f[0]=1;
for(int i=1,End=1<<n;i<End;i++){
for(int j=i;j;j-=j&-j)
if(check(i^(j&-j),Log[j&-j]))
{ans++;f[i]=1;break;}
}
cout<<ans<<'
';
}
}
满分做法
满分做法需要用到竞赛图的一个性质:(a_{i,j}hat{} a_{i,j}=1(i ot=j))。
先维护一个 (g[i]) 表示 (i) 里面的点的出边指向的点的交集,设 (g[0]=(111..111)_2)。
设 (j) 为集合 (g[i]) 的子集,对于每一个合法状态 (i) ,将 (i|j) 标记为不合法。
因为那个性质:若有一条 (x) 指向 (y) 的边,则没有 (y) 指向 (x) 的边。
所以 (j) 中的点都没有指向 (i) 中的点的边,那么 (i|j) 这个图就肯定不连通。
#include<bits/stdc++.h>
using namespace std;
int T,n,dis[30][30],ans,road[30],g[16778000],Log[16778000];
bool f[16778000];
inline int read()
{
int x=0,w=0;char ch=0;
while(!isdigit(ch)){w|=ch=='-';ch=getchar();}
while(isdigit(ch)){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
return w?-x:x;
}
int main()
{
T=read();
for(int i=1;i<=24;i++)Log[1<<(i-1)]=i;
while(T--){
n=read();
for(int i=0;i<=n;road[i++]=0);
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
road[i]|=read()<<(j-1);
g[0]=-(ans=1);
for(int i=0,End=1<<n;i<End;f[i++]=0);
for(int i=1,End=1<<n;i<End;i++){
g[i]=g[i^(i&-i)]&road[Log[i&-i]];
if(f[i])continue;
ans++;
for(int j=g[i];j;j=(j-1)&g[i])f[i|j]=1;
}
cout<<ans<<'
';
}
}