神秘题目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 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<<'
';
	}
}
原文地址:https://www.cnblogs.com/zYzYzYzYz/p/13848319.html