神秘题目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';
	}
}
posted @ 2020-10-20 19:11  zYzYzYzYz  阅读(60)  评论(0编辑  收藏  举报