【习题讲解】 图论类习题

文章目录

    • 重量不同的硬币
    • 超级牛游戏
    • damage

重量不同的硬币

题目描述
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

首先,我们知道,如果重量的关系出现了矛盾,一定出现了环,我们可以选择用拓扑排序进行判环:
拓扑排序中的点,要想进入拓扑序列,入度必然为0;但是有环,入度必然不为0.因此,我们统计拓扑排序有多少个点进入拓扑序列,如果有点数sum 对于统计大小,我们进行dfs遍历,用cnt[]表示连向这个点的关系,注意每一次的初始化要为1,否则无法进行大小统计。再在反图上做一遍DFS即可。

#include
using namespace std;
#define maxn 1200
int n,w;
int ans=0,d=0;
int q[maxn]={};
int v[maxn]={};
int V[maxn]={};
int In[maxn]={};
int cnt[maxn]={};
int CNT[maxn]={};
vector <int> a[maxn];
vector <int> A[maxn];

inline void read(int &readnum)
{
	int s=0,w=1;char c=getchar();
	while (c<'0' || c>'9') {if (c=='-') w=-1; c=getchar();}
	while (c>='0' && c<='9') s=s*10+c-48,c=getchar();
	readnum=s*w;return;
}

inline int Topsort(void)
{
	int sum=0;
	int h=1,t=0;
	for (int i=1;i<=n;++i) 
	    if (!In[i]) q[++t]=i;
	for (;h<=t;++h)
	{
		int p=q[h];sum++;
		for (int i=0;i<a[p].size();++i)
		{
			int np=a[p][i];
			In[np]--;
			if (!In[np]) q[++t]=np; 
		}
	}
	return sum;
}

void dfs(int x)
{
	int sum=0;
	cnt[x]=1,v[x]=1;
	for (int i=0;i<a[x].size();++i)
	{
		int np=a[x][i];
		if (v[np]) continue;
		dfs(np);
		sum+=cnt[np];
	}
	cnt[x]+=sum;
}

void DFS(int X)
{
	int SUM=0;
	CNT[X]=1,V[X]=1;
	for (int I=0;I<A[X].size();++I)
	{
		int NP=A[X][I];
		if (V[NP]) continue;
		DFS(NP);
		SUM+=CNT[NP];
	}
	CNT[X]+=SUM;
}

int main()
{
	freopen("coin.in","r",stdin);
	freopen("coin.out","w",stdout);
	read(n),read(w);
	for (int i=0;i<maxn-10;++i) a[i].clear();
	for (int i=0;i<maxn-10;++i) a[i].clear();
	for (int i=1;i<=w;++i)
	{
		int x,y;
		read(x),read(y);
		In[y]++;
		a[x].push_back(y);
		A[y].push_back(x);
	}
	int stp=Topsort();
	if (stp<n)
	{
		cout<<"IMPOSSIBLE\n";
		return 0;
	}
	for (int i=1;i<=n;++i)
	{
		memset(v,0,sizeof(v));
		memset(V,0,sizeof(V));
		dfs(i),DFS(i);
		int tmp=cnt[i]+CNT[i];
		if (tmp>ans) ans=tmp,d=i;
	}
	cout<<d<<endl;
	fclose(stdin);
	fclose(stdin);
	return 0;
}

超级牛游戏

题目描述
现在有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

做法:亮亮连边,组成一个完全图,跑一遍最大生成树(或者发把左右边转成负数跑一遍最小生成树)即可。

#include
using namespace std;
#define LL long long

struct edge {
	int x,y,v;
}e[5000000]={};
LL sum=0;
int tot=0,n;
int fa[2200]={};
int Id[2200]={};
 
inline void read(int &readnum)
{
	int s=0,w=1;char c=getchar();
	while (c<'0' || c>'9') {if (c=='-') w=-1;c=getchar();}
	while (c>='0' && c<='9') s=s*10+c-48,c=getchar();
	readnum=s*w;
}

inline int get(int x)
{
	if (fa[x]==x) return x;
	return fa[x]=get(fa[x]);
}

inline bool cmp(edge a,edge b) {
	return a.v<b.v;
}

int main()
{
	freopen("superbull.in","r",stdin);
	freopen("superbull.out","w",stdout);
	read(n);
	for (int i=1;i<=n;++i) fa[i]=i;
	for (int i=1;i<=n;++i) read(Id[i]);
	for (int i=1;i<=n;++i) 
	    for (int j=i+1;j<=n;++j)
	        e[++tot]=edge{i,j,-(Id[i]^Id[j])},
	        e[++tot]=edge{j,i,-(Id[j]^Id[i])};
	sort(e+1,e+tot+1,cmp); 
	for (int i=1;i<=tot;++i)
	{
		int fa1=get(e[i].x);
		int fa2=get(e[i].y);
		if (fa1==fa2) continue;
		fa[fa1]=fa2;
		sum+=(LL)e[i].v; 
	}
	cout<<-sum<<endl;
	fclose(stdin);
	fclose(stdout);
	return 0;
}

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

我们知道对于每一个损坏的点,跑不出去,周围的一个点一定是损坏的。
因此我们只需要把每一个损坏了的点的边上的v数组标记为1,再把自己标记为1,直接用bfs跑一遍遍历即可。

#include
using namespace std;

int p,c,n,ans=0;
int d[100000];
int v[100000]={};
int q[200000]={};
vector<int> a[100000]={};

inline void read(int &readnum)
{
	int s=0,w=1;char c=getchar();
	while (c<'0' || c>'9') {if (c=='-') w=-1; c=getchar();}
	while (c>='0' && c<='9') s=s*10+c-48,c=getchar();
	readnum=s*w;return;
}

int main()
{
	freopen("damage..in","r",stdin);
	freopen("damage..out","w",stdout);
	read(p),read(c),read(n);
	for (int i=1;i<=c;++i)
	{
		int x,y;
		read(x),read(y);
		a[x].push_back(y);
		a[y].push_back(x);
	}
	for (int i=1;i<=n;++i)
	{
		int k;read(k);
		for (int i=0;i<a[k].size();++i) v[a[k][i]]=1;
	}
	int h,t;t=h=1;
	memset(d,100,sizeof(d));
	d[1]=0,v[1]=1,q[1]=1;
	while (h<=t)
	{
		int p=q[h++];
		for (int i=0;i<a[p].size();++i)
		{
			int np=a[p][i];
			if (v[np]) continue;
			d[np]=d[p]+1,v[np]=1,q[++t]=np;
		}
	}
	for (int i=1;i<=p;++i)  
	    if (d[i]>1e6) ans++;
	cout<<ans<<endl; 
	return 0;
}

你可能感兴趣的:(图论)