『NOIP普及:图论组题训练』

NOIP普及:图论组题训练

        • T1 重量不同的硬币
        • 解析
        • T2 超级牛游戏
        • 解析
        • T3 damage
        • 解析
        • T4 传送
        • 解析
        • T5 path
        • 解析

T1 重量不同的硬币

题目描述

Fj有N个硬币,编号为1…N。

现在有W个推断,为(A,B),表示硬币A比硬币B重。

寻找并输出一个硬币编号,要求其重量明确不同于其他硬币的个数最多。

如果有多个答案,输出字典序最小的一个。

如果给出的数据有矛盾,输出"IMPOSSIBLE"
输入格式

Line 1: 两个整数: N and W.

Lines 2..W+1: 每行两个整数: A, B

输出格式

Line 1: 重量不同于其他硬币的个数最多的硬币编号。

样例数据

input

7 6
1 6
1 5
3 6
4 3
2 4
2 5

output

2

有7个硬币,6个推断对,2的重量不同于3,4,5,6
数据规模与约定

1 <= N <= 1,000 1 <= W <= 3,000

时间限制:1s

空间限制:256MB

解析

先解决第一个问题,如何判断数据是否矛盾。我们构建图的模型,将每一个硬币视作一个节点,那么每一条推断就相当于给两个节点连了一条有向边,那么,当图出现环的时候,数据就互相矛盾了。那么我们选用 T o p s o r t Topsort Topsort判环,如果 T o p s o r t Topsort Topsort能构造该图的拓扑序,则说明该图是有向无环图,反之,该图有环。
那么,找到与其他硬币有最多明确关系的点可以用dfs实现。我们在原图上dfs一遍,可以得到一个代表比硬币小的硬币数量的值,注意,为了方便实现,我们得到数量关系即可,并不是代表具体有几个硬币比该硬币小。那么在反向图上跑一遍一模一样的dfs得到比它大的数量,相加就是不同于其他硬币的个数。比较每一个硬币的该值得到最终答案。

#include
using namespace std;
//加下划线的都是反向图的数据
struct egde{int ver,next;}e[100800]={},e_[100800]={};
int Link[30800]={},n,w,t=0,Max=-1,ans,Link_[30080]={},t_=0,degreein[30800]={},tot=0; 
int cnt[30800]={},cnt_[30800]={},vis[30080]={},vis_[30080]={};
inline void insert(int x,int y)
{
	t++;e[t].ver=y;e[t].next=Link[x];Link[x]=t;
}
inline void insert_(int x,int y)
{
	t_++;e_[t_].ver=y;e_[t_].next=Link_[x];Link_[x]=t_;
}
inline void input(void)
{
	scanf("%d%d",&n,&w);
	for(int i=1;i<=w;i++)
	{
		int u,v;
		scanf("%d%d",&u,&v);
		insert(u,v);
		insert_(v,u);
		degreein[v]++;
	}
}
inline int Topsort(void)
{
	int p=0;
	queue< int >Q;
	for(int i=1;i<=n;i++)
	{
		if(!degreein[i])
		{
			Q.push(i);
		}
	}
	while(!Q.empty())
	{
		p++;
		int temp=Q.front();Q.pop();
		for(int i=Link[temp];i;i=e[i].next)
		{
			degreein[e[i].ver]--;
			if(!degreein[e[i].ver])Q.push(e[i].ver);
		}
	}
	return p;
}
inline void Find(void)
{
	tot=Topsort();
	if(tot<n)
	{
		printf("IMPOSSIBLE\n");
		exit(0);
	}
}
inline void dfs(int k)
{
	cnt[k]=1;vis[k]=true;
	for(int i=Link[k];i;i=e[i].next)
	{
		if(!vis[e[i].ver])
		{
			dfs(e[i].ver);
			cnt[k]+=cnt[e[i].ver];
		}
	}
}
inline void dfs_(int k)
{
	cnt_[k]=1;vis_[k]=true;
	for(int i=Link_[k];i;i=e_[i].next)
	{
		if(!vis_[e_[i].ver])
		{
			dfs_(e_[i].ver);
			cnt_[k]+=cnt_[e_[i].ver];
		}
	}
}
int main(void)
{
	freopen("coin.in","r",stdin);
	freopen("coin.out","w",stdout);
	input();
	Find();
	for(int i=1;i<=n;i++)
	{
		memset(vis,0,sizeof(vis));
		memset(vis_,0,sizeof(vis_));
		dfs(i);dfs_(i);
		int temp=cnt[i]+cnt_[i];
		if(temp>Max)
		{
			Max=temp;
			ans=i;
		}
	}
	printf("%d\n",ans);
	return 0;
}

T2 超级牛游戏

题目描述

现在有N(1 <= N <= 2000)头奶牛在玩 超级牛 游戏。每头奶牛有一个唯一的ID,ID范围是 1 … 2 ^ 30-1。

超级牛比赛是淘汰赛 - 每场比赛后,输者退赛,赢者继续留在比赛,直到只剩一队游戏结束。 输赢是FJ自己决定的,或者说结果可以任意决定!

比赛的积分规则十分奇葩:积分=第一队的ID XOR 第二队的ID。 比如,12队和20队打比赛,积分是24,因为01100 XOR 10100 = 11000。

FJ认为,分越高越刺激。所以他想让总积分最高。请帮助FJ设计比赛。
输入格式

第一行包含一个整数N

以下N行包含N个队伍的ID。
输出格式

一行,一个整数,表示答案。
样例数据

input

4
3
6
9
10

output

37

输出详情:实现37的一种方法如下:

3 VS 9 ==》9胜,本场积分 3 XOR 9 = 10,目前队伍:6 9 10

6 VS 9 ==》6胜,本场积分 6 XOR 9 = 15,目前队伍:6 10

6 VS 10 ==》管他谁胜呢反正不用再比赛了,本场积分 6 XOR 10 = 12

总积分 10 + 15 + 12 = 37。

团队6和10面脱落,和队10胜。

拿下点的总数是(10)+(15)+(12)=

注:按位异或运算,由^符号通常表示,是进行逻辑异或运算的两个二进制整数的每个位置逐位操作。 规则如下 0 xor 0 = 0 0 xor 1 = 1 1 xor 0 = 1 1 xor 1 = 0 例如:

10100(十进制20)
XOR
01100(十进制12)

11000(十进制24)

数据规模与约定

保证a,b≤109a,b≤109。 时间限制:1s1s

空间限制:256MB

解析

这大概是这些题目里面最简单的一道了吧。
按照题意对每一个节点异或连边,跑一遍最大生成树就能得到答案,别忘了开longlong。

#include
using namespace std;
struct edge
{
	long long x,y,val;
	bool operator<(const edge temp)const
	{
		return this->val>temp.val;
	}
};
edge e[2080*2080]={};
long long n,a[2080]={},t=0,f[2080]={},ans=0;
inline long long Getfather(long long x){return f[x]==x?f[x]:f[x]=Getfather(f[x]);}
inline void input(void)
{
	scanf("%lld",&n);
	for(long long i=1;i<=n;i++)scanf("%lld",&a[i]);
	for(long long i=1;i<=n;i++)
	{
		for(long long j=1;j<=n;j++)
		{
			e[++t].x=i;
			e[t].y=j;
			e[t].val=a[i]^a[j];
		}
	}
}
inline void Kruskal(void)
{
	sort(e+1,e+t+1);
	for(long long i=1;i<=n;i++)f[i]=i;
	for(long long i=1;i<=t;i++)
	{
		long long fx=Getfather(e[i].x);
		long long fy=Getfather(e[i].y);
		if(fx==fy)continue;
		f[fx]=fy;
		ans+=e[i].val;
	}
}
int main(void)
{
	freopen("superbull.in","r",stdin);
	freopen("superbull.out","w",stdout);
	input();
	Kruskal();
	printf("%lld\n",ans);
	return 0;
}

T3 damage

题目描述

农夫John的农场遭受了一场地震.有一些牛棚遭到了损坏,但幸运地,所有牛棚间的路经都还能使用.

FJ的农场有P(1 <= P <= 30,000)个牛棚,编号1…P. C(1 <= C <= 100,000)条双向路经联 接这些牛棚,编号为1…C. 路经i连接牛棚a_i和b_i (1 <= a_i<= P;1 <= b_i <= P).路经 可能连接a_i到它自己,两个牛棚之间可能有多条路经.农庄在编号为1的牛棚.

N (1 <= N <= P)头在不同牛棚的牛通过手机短信report_j(2 <= report_j <= P)告诉FJ它 们的牛棚(report_j)没有损坏,但是它们无法通过路径和没有损坏的牛棚回到到农场.

当FJ接到所有短信之后,找出最小的不可能回到农庄的牛棚数目.这个数目包括损坏的牛棚.
输入格式

第1行: 三个空格分开的数: P, C, 和 N

第2..C+1行: 每行两个空格分开的数: a_i 和 b_i

第C+2..C+N+1行: 每行一个数: report_j

输出格式

第1行: 一个数,最少不能回到农庄的牛的数目(包括损坏的牛棚).

样例数据

input

4 3 1
1 2
2 3
3 4
3

output

3

数据规模与约定

时间限制:1s1s

空间限制:256MB

解析

这是一道题意理解题。每一个发短信的牛棚称自己的牛棚没有损坏,但不能返回牛庄了,这就说明和它有边相连的牛棚全部都损坏了。那我们把这些牛棚全部标记为不能访问,然后从起点bfs遍历一边,就得到了能访问的最大节点数,用总点数减去这个值就是答案。

#include
using namespace std;
#define Maxn 30000
#define Maxm 100000 
inline void read(int &k)
{
	int x=0,w=0;char ch;
	while(!isdigit(ch))w|=ch=='-',ch=getchar();
	while(isdigit(ch))x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
	k=(w?-x:x);return;
}
struct edge{int ver,next;}e[Maxm*4]; 
int n,m,k,Link[Maxm*4],Damaged[Maxn+80],t=0,vis[Maxn+80],cnt=0;
inline void Insert(int x,int y){e[++t].ver=y;e[t].next=Link[x];Link[x]=t;}
inline void Input(void)
{
	read(n),read(m),read(k);
	for(int i=1;i<=m;i++)
	{
		int u,v;
		read(u);read(v);
		Insert(u,v);Insert(v,u);
	}
	for(int i=1;i<=k;i++)
	{
		int temp;read(temp);
		Damaged[temp]=true;
		for(int j=Link[temp];j;j=e[j].next)
		{
			Damaged[e[j].ver]=true;
		}
	}
}
inline void Solve(void)
{
	queue< int >Q;
	Q.push(1);vis[1]=true;
	while(!Q.empty())
	{
		cnt++;
		int temp=Q.front();Q.pop();
		for(int i=Link[temp];i;i=e[i].next)
		{
			if(!vis[e[i].ver]&&!Damaged[e[i].ver])
			{
				vis[e[i].ver]=true;
				Q.push(e[i].ver);
			}
		}
	}
}
int main(void)
{
	freopen("damage..in","r",stdin);
	freopen("damage..out","w",stdout);
	Input();
	Solve();
	printf("%d\n",n-cnt);
	return 0;
}


T4 传送

题目描述

在遥远的星系里,有N个星球,编号为1…N,现在小x要从1号星球到N号星球。

现在我们知道,在N个星球一共有M组传送站,一组传送站包括K个星球,通过这组传送站,可以花费1天的时间从这组传送站的某个星球到任意一个这组传送站中的其他星球。

现在小x想知道,通过这些传送站,最少经过多少个星球,能从1号星球到N号星球。
输入格式

第一行三个整数N,K和M

接下来M行,每行K个整数,表示这K个星球有一组传送站。
输出格式

一个整数,表示最少的经过的星球个数。如果无解,输出-1
样例数据

input

9 3 5
1 2 3
1 4 5
3 6 7
5 6 7
6 8 9

output

4

两种可能:1-3-6-9, or 1-5-6-9.

input

15 8 4
11 12 8 14 13 6 10 7
1 5 8 12 13 6 2 4
10 15 4 5 9 8 14 12
11 12 14 3 5 6 1 13

output

3

数据规模与约定

20% N<=200

100% 1 ≤ N ≤ 100 000 (1 ≤ K ≤ 1 000) (1 ≤ M≤ 1 000)

时间限制:1s1s

空间限制:256MB

解析

这道题是noip最有可能考的一种图论题型:建图题。对图论算法没有过高要求,考在思维的转换。直接建图显然会炸,我们需要将每一传送站视为一个节点,然后将每一个传送站内的节点想传送站连一条边,这样就能解决建图问题,我们直接bfs跑最短路即可。由于传送站内的节点直接可以互相传送,而这样建图经过传送站需要经过两条边,所以最后输出的答案要除以2,又因为题目要求的是经过的点数,在加1即可。

#include
using namespace std;
int n,m,k,vis[800080]={},dis[800080]={},flag,t=0,Link[800080];
struct edge{int ver,next;}e[800080]={};
inline void insert(int x,int y){e[++t].ver=y;e[t].next=Link[x];Link[x]=t;}
inline void input(void)
{
	scanf("%d%d%d",&n,&k,&m);
	for(int i=1;i<=m;i++)
	{
		int New=n+i;
		for(int j=1;j<=k;j++)
		{
			int temp;scanf("%d",&temp);
			insert(New,temp);
			insert(temp,New);
		}
	}
}
inline void bfs(void)
{
	memset(dis,0x3f,sizeof(dis));
	memset(vis,0x00,sizeof(vis));
	flag=dis[0];
	queue< int >Q;
	Q.push(1);
	dis[1]=0;vis[1]=1;
	while(!Q.empty())
	{
		int temp=Q.front();Q.pop();
		for(int i=Link[temp];i;i=e[i].next)
		{
			if(!vis[e[i].ver])
			{
				vis[e[i].ver]=true;
				dis[e[i].ver]=dis[temp]+1;
				Q.push(e[i].ver);
			}
		}
	}
}
int main(void)
{
	freopen("hipercijevi.in","r",stdin);
	freopen("hipercijevi.out","w",stdout);
	input();
	bfs();
	if(dis[n]!=flag)printf("%d\n",dis[n]/2+1);
	else printf("-1\n");
	return 0;
}

T5 path

题目描述

Abigail在矿洞里火把突然灭了,导致她不得不摸着黑走,这也促使她一不小心失足摔到一个十分巨大的洞前.

当Abigail摔倒这个巨大洞口的门前时,她仿佛不小心触碰到了一个开关,使得她面前突然出现了一片光明,并将她面前的门照得通亮,也照亮了门旁的一块石碑.在这块石碑上写着:

勇敢的冒险者,欢迎你来到通往财富的大门.

在通往财富之前,我们需要你回答一个问题:在下面的两张图里,最长公共子路径有多长.

Abigail由于参加过OI,她肯定知道怎么用计算机求解.但是图论问题让她想起了她的 NOIP 退役赛中,由于图论问题中一个小小的错误,她最终从省一变成省二的悲惨经历…所以她决定让你来帮助她求解.

Abigail 知道你可能不是很清楚这个问题的定义,所以她跟你说了十分简明的解释:

给定你两张图,其中每张图都是有向无环图,且每一个节点都有一个点权vi.

现在让你求一条最长的序列A,使得序列A分别对应两张图上的一条路径的点权,其中一条序列的长度为两张图上的这条路径的边权和之和.

输入格式

输入第一行包括两个正整数n和m,分别表示两张图的点数和边数. 接下来包括两张图的描述,每一组描述包括第一行 n 个正整数 vi.接下来 mm 行,每行三个正整数xi,yi和ci,表示xi到yi有一条有向边连接,并且边权ci
输出格式

输出包括一行,若最长公共子路径长度大于0,则输出长度,否则输出”-1”.
数据范围

本题开启 subtask,请不要使用非完美算法. 对于 100%的数据:0

特殊形态 1:一条链,也就是说xi+1=yi.

特殊形态 2:一棵有根树,也就是说所有xi不相等.

解析

额…这是一道图上dp题,而且是双进程的dp。其实就是最长上升子序列的拓展版本,只不过需要注意几个问题:在图上dp时,需要根据有向无环图的拓扑序来划分阶段,所以我们需要先对两个图做拓扑排序,得到阶段的信息。然后我们用邻接表遍历所连向的每一个点,并进行状态转移即可。状态转移方程很简单:
f [ e [ k ] . v e r ] [ e [ l ] . v e r ] = m a x { f [ T o p i ] [ T o p j ] + e [ k ] . v a l + e [ l ] . v a l } f[e[k].ver][e[l].ver]=max\{f[Top_i][Top_j]+e[k].val+e[l].val\} f[e[k].ver][e[l].ver]=max{f[Topi][Topj]+e[k].val+e[l].val}
其中 T o p i , T o p j Top_i,Top_j Topi,Topj是两个图分别在拓扑序中的两个点, e [ k ] . v e r , e [ l ] . v e r e[k].ver,e[l].ver e[k].ver,e[l].ver是前者连出的边所指向的节点。

#include
using namespace std;
const int Maxn=1080,Maxm=7080;
struct edge{int ver,val,next;}e[4*Maxm];
int Link[Maxn][2],indegree[Maxn][2],value[Maxn][2],n,m,t=0,Top[Maxn][2],f[Maxn][Maxn],ans=-1;
inline void insert(int x,int y,int v,int num)
{
	t++;e[t].ver=y;e[t].val=v;
	e[t].next=Link[x][num];Link[x][num]=t;
}
inline void input(void)
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++)scanf("%d",&value[i][0]);
	for(int i=1;i<=m;i++)
	{
		int x,y,v;
		scanf("%d%d%d",&x,&y,&v);
		insert(x,y,v,0);
		indegree[y][0]++;
	}
	for(int i=1;i<=n;i++)scanf("%d",&value[i][1]);
	for(int i=1;i<=m;i++)
	{
		int x,y,v;
		scanf("%d%d%d",&x,&y,&v);
		insert(x,y,v,1);
		indegree[y][1]++;
	}
}
inline void Topsort(int num)
{
	int cnt=0;
	queue< int >Q;
	for(int i=1;i<=n;i++)if(!indegree[i][num])Q.push(i);
	while(!Q.empty())
	{
		int temp=Q.front();Q.pop();
		Top[++cnt][num]=temp;
		for(int i=Link[temp][num];i;i=e[i].next)
		{
			indegree[e[i].ver][num]--;
			if(!indegree[e[i].ver][num])Q.push(e[i].ver);
		}
	}
}
inline void Dp(void)
{
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=n;j++)
		{
			int Pi=Top[i][0],Pj=Top[j][1];
			if(value[Pi][0]==value[Pj][1])
			{
				ans=max(ans,f[Pi][Pj]);
				for(int k=Link[Pi][0];k;k=e[k].next)
				{
					for(int l=Link[Pj][1];l;l=e[l].next)
					{
						if(value[e[k].ver][0]==value[e[l].ver][1])
						{
							f[e[k].ver][e[l].ver]=max(f[e[k].ver][e[l].ver],f[Pi][Pj]+e[k].val+e[l].val);
						}
					}
				}
			}
		}
	}
}
int main(void)
{
	freopen("path.in","r",stdin);
	freopen("path.out","w",stdout);
	input();
	Topsort(0);
	Topsort(1);
	Dp();
	if(ans>0)printf("%d\n",ans);
	else printf("-1\n");
}

你可能感兴趣的:(『NOIP普及:图论组题训练』)