神秘题目1
题目
问题描述
给定一个 \(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\not=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<<'\n';
}
}
满分做法
满分做法需要用到竞赛图的一个性质:\(a_{i,j}\hat{} a_{i,j}=1(i\not=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<<'\n';
}
}