XMUT 简单图论专场(拓扑排序、最小生成树、贪心)

181 篇文章 0 订阅
95 篇文章 0 订阅


A - 小Y上学记——修学分


Problem Description

小Y终于如愿以偿地通过高考来到了魂牵梦萦的大学校园——ACdream大学。来到校园的第一件事就是选课。

由于每一门课都有1个学分~而且有一些课需要先学完别的课程(例如必须先学会高等数学,才能学会量子力学,必须先学会走,才能学会跑)

ACdream大学需要学生修够若干学分才允许毕业。

请按顺序输出小Y的一种方案(若不止一种答案,请输出字典序最小的一种方案)

Input

多组数据,每组数据首先是两个整数n,m,k,分别表示学科总数,学科之间的关系数,以及毕业所需的最少学分。

(2<=n<=100, 0<=m<=500,1<=k<=n)

接下来是m行,每行是两个整数a,b表示学科a是学科b的前置学科。

Output

对于每组数据,若小Y不存在任何方案选课,请输出-1.

否则在一行输出m个整数,表示小Y按顺序修的学科编号。

Sample Input
2 1 2
0 1
2 2 2
1 0
0 1
3 2 1
1 0
0 1
3 0 3
Sample Output
0 1
-1
2
0 1 2
Hint

对于第一组数据,先修完第0学科,获得1学分,再修以第0学科为前置学科的第1学科,获得1学分,即可满足毕业条件:2学分。

对于第二组数据,由于第0学科的前置学科为第1学科,而第1学科的前置学科为第2学科,因此小Y无论如何也无法满足毕业条件。

对于第三组数据,由于多了第2学科,而且第2学科不需要前置学科,因此只需要修完第2学科即可满足毕业条件了。

对于第四组数据,没有任何的先后限制,因此6种全排列方案均符合题意,字典序最小的方案为0,1,2


这里有一道我做过的类似题目可供参考:http://blog.csdn.net/enjoying_science/article/details/47380099


编程思想:根据题意,可抽象成图的拓扑排序。这里采用map和链式前向星两种方法来实现拓扑排序算法。


AC  code1(map方式):

#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<queue>
#include<math.h>
#include<vector>
#include<map>
#include<set>
#include<cmath>
#include<string>
#include<algorithm>
#include<iostream>
#define LL long long 
#define exp 1e-10
#define MAXN 1000010
using namespace std;
const int INF=0x3f3f3f3f;
const int N = 100005;
const int mod = 1000000007;
int ma[555][555];
int ind[555];
int ans[555];
int main()
{
	//freopen("D:\in.txt","r",stdin);
	int n,m,k,i,j,cnt;
	while(scanf("%d%d%d",&n,&m,&k)!=EOF)
	{
		memset(ma,0,sizeof(ma));
		memset(ind,0,sizeof(ind));
		for(i=1;i<=m;i++)
		{
			int a,b;
			scanf("%d%d",&a,&b);
			if(!ma[a][b])
			{
				ma[a][b]=1;
				ind[b]++;
			}
		}
		int node;
		cnt=0;
		for(i=0;i<n;i++)//i为成功拓扑排序的点的个数 
		{
			for(j=0;j<n;j++)//点的编号是从0开始的 
			{
				if(ind[j]==0)
				{
					node=j;
					ind[node]--;
					ans[i]=node;
					for(int jj=0;jj<n;jj++)
					{
						if(ma[node][jj])
						{
							ind[jj]--;
						}
					}
					break;
				}
			}
			if(j==n)//点的编号是从0开始的,到n-1都遍历完,直接跳出
				break;
		}
		if(i<k)//i为成功拓扑排序的点的个数 
		{
			printf("-1\n");
		}
		else
		{
			printf("%d",ans[0]);
			for(i=1;i<k;i++)
			{
				printf(" %d",ans[i]);
			}
			printf("\n");
		}	
	}
	return 0;
}


AC code2(链式前向星):

#include <iostream>
#include <stdio.h>
#include <string.h>
#include <map>
#define MAX 505
using namespace std;
int InD[505];/*InD[i]记录点i的入度*/
int First[MAX];/*First[i]头结点的第一条边的编号*/
struct edge
{
    int TO;/*点*/
    int Next;/*下一条边的编号*/
}ID[3*MAX];
int SIGN;
void Add_E(int x,int y)/*添加点操作*/
{
    ID[SIGN].TO=y;
    InD[y]++;
    ID[SIGN].Next=First[x];
    First[x]=SIGN++;
}
int Jude(int x,int y)/*查找与X是否与Y相连*/
{
    int i;
    for(i=First[x];i!=0;i=ID[i].Next)   //查找与该点相关的点
    {
       if(ID[i].TO==y)return 0;
    }
    return 1;
}
int ToPoSort(int N,int Num[],int K)/*拓扑排序,邻接表*/
{
    int i,j,k;
    for(j=0;j<N;j++)
    {
        for(i=1;i<=N;i++)
        {
            if(InD[i]==0)
            {
                InD[i]--;
                Num[j]=i;
                for(k=First[i];k!=0;k=ID[k].Next)
                {
                    InD[ID[k].TO]--;
                }
                break;
            }
        }
        if(i>N)break;
    }
    if(j>=K)return 1;
    else return 0;
}
int main()
{
    int M,N,K,i;
    int a,b;
    int Num[MAX];
    while(scanf("%d%d%d",&N,&M,&K)!=EOF)
    {
        for(i=1;i<=N;i++){First[i]=0;InD[i]=0;}
        for(i=0,SIGN=1;i<M;i++)
        {
            scanf("%d%d",&a,&b);a+=1;b+=1;
            Add_E(a,b);
        }
        if(ToPoSort(N,Num,K))/*拓扑排序*/
        {
            for(i=0;i<K;i++)
            {
                if(i!=0)putchar(32);
                printf("%d",Num[i]-1);
            }putchar(10);
        }
        else printf("-1\n");
    }
    return 0;
}


B - 小Y上学记——小Y的玩偶

Problem Description

小Y最喜欢拆拆拆了~尽管他不一定能装回去。

小Y有一个很可爱的积木玩偶,是由一块一块积木拼接而成,现在小Y想把这个积木玩偶拆拆拆。

每一块积木玩偶都有一个耐久值,想把一块积木拆出来,小Y需要付出的能量就是和它直接拼接的所有积木的耐久值之和。

小Y很懒的~他想知道把这个玩偶全部拆好,最少需要付出多少能量?

Input

多组数据,每组数据首先是一个整数n表示积木块数。(0<n<=1000)

接下来一行包含n个整数,表示每块积木的耐久值a[i](0<=a[i]<=100000)。

接下来是n行,第i行代表第i块积木的连接情况。

每一行首先是一个整数k,表示这块积木与k块积木相连,接下来是k个整数,代表与这块积木相连的积木标号(标号从0开始)

保证连接情况合法。

Output
对于每组数据,输出一个整数,表示小Y需要的最少能量。
Sample Input
4
10 20 30 40
2 1 3
2 0 2
1 1
1 0
4
100 100 100 100
1 1
3 0 2 3
2 1 3
2 1 2
7
40 10 20 10 20 80 40
4 2 3 4 5
1 4
2 0 3
5 0 2 4 5 6
4 0 1 3 6
2 0 3
2 3 4
Sample Output
40
400
160
Hint

对于第一组数据,首先拆掉第2块积木,需要20能量,然后拆掉第1块积木,需要10能量,接着拆掉第3块积木,需要10能量,最后只剩下第0块了,不需要能量了。总共需要40点能量。

对于第二组数据,无论怎么拆除,都是需要400点能量。


编程思想:贪心+邻接表。每次都拆耐久值最大的点,然后更新与其连接的点的信息,直到图中剩下一个点,这时所得的能量和最小。下面证明这种贪心策略是正确的。每次选择耐久度最大的点,设这个点的耐久度为K,有m个点与其连接,这m个点的耐久度为K1~Km,若不取走这个耐久度最大的点,那么要拆了连接它的m条边的点,则需要消耗K*m能量,如果直接把这个点拆除了,则需要K1+K2+...+Km能量。因为K*m>=K1+K2+...+Km,所以拆掉连接的这相同的m条边时,后者消耗的能量更少。因此,每次拆除耐久度最大的点,然后再去更新连接图,这样可以保证消耗的能量最小。


AC code:

#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<queue>
#include<math.h>
#include<vector>
#include<map>
#include<set>
#include<cmath>
#include<string>
#include<algorithm>
#include<iostream>
#define LL long long 
#define exp 1e-10
#define MAXN 1000010
using namespace std;
const int INF=0x3f3f3f3f;
const int N = 100005;
const int mod = 1000000007;
int n,k,m,mi;
int p[MAXN]; 
struct node{
	int v;
	int id;
}a[MAXN];
int b[MAXN];
bool cmp(node a,node b)
{
	return a.v>b.v;
}
vector<int>vec[MAXN];
bool vis[MAXN];
LL ans,mm;
int main()
{
	//freopen("D:\in.txt","r",stdin);
	int i,j,t;
	while(scanf("%d",&n)!=EOF)
	{
		for(i=0;i<n;i++)
		{
			scanf("%d",&a[i].v);
			a[i].id=i;
		}
		sort(a,a+n,cmp);
		for(i=0;i<n;i++)
			b[a[i].id]=i;
		memset(p,0,sizeof(p));
		memset(vis,false,sizeof(vis));
		for(i=0;i<n;i++)
			vec[i].clear();
		mm=-INF;
		mi=0;
		for(i=0;i<n;i++)
		{
			scanf("%d",&k);
			while(k--)
			{
				scanf("%d",&t);
				vec[i].push_back(t);
				p[i]+=a[b[t]].v;
			}
		}
		ans=0;
		for(j=0;j<n-1;j++)
		{
			mi=a[j].id;
			ans+=p[mi];
			for(i=0;i<vec[mi].size();i++)//更新连接的点
			{
				p[vec[mi][i]]-=a[b[mi]].v;
			}
			a[b[mi]].v=0;//0表示已删除,不再对与它连接的点有关系,即“惰性删除”
		}
		printf("%lld\n",ans);
	}
	return 0;
}





C - 小Y上学记——认识新同学

Problem Description

小Y来到了一个新的班级,新的集体,第一件事肯定是要去认识新同学。

然而想认识全班同学,所需要的代价是很大的,所以小Y想让代价尽可能小。

认识新同学有两种办法,一种是直接去认识,另一种是通过已经认识的同学去认识新同学。

小Y想知道最小代价是多少呢?

Input

多组数据,每组数据首先是一个N,表示新的班级的同学数(包括小Y,2<=N<=1000)

接下来是N-1个整数,表示小Y直接认识第i名同学所需要的代价。

接下来是一个N-1阶矩阵,其中第i行第j列表示通过第i名同学认识第j名同学的代价。

其中保证对角线为0,且保证对称.

所有代价均为不超过1000的正整数。

Output
对于每组数据,输出一个整数,表示最少代价。
Sample Input
3
2 4
0 1
1 0
4
2 4 3
0 1 2
1 0 1
2 1 0
Sample Output
3
4

编程思想:根据题意,可抽象成求一颗最小生成树。这里采用利用并查集构造最小生成树的克鲁斯卡尔算法实现。


AC  code:

#include<iostream>
#include<algorithm>
#include<cmath>
#include<cstring>
#include<cstdio>
#include<queue>
#include<vector>
#define LL long long
#define MAXN 1000010
using namespace std;
const int INF=0x3f3f3f3f;
struct node{
    int u;
    int v;
    int w;
}e[MAXN];
int point[MAXN],f[MAXN];
int ans,n,p,cnt;
bool cmp(node a,node b)
{
    return a.w<b.w;
}
void init(int n)
{
    for(int i=1;i<=n;i++)
        f[i]=i;
}
int fin(int root)
{
    int son,tem;
    son=root;
    while(root!=f[root])
    {
        root=f[root];
    }
    while(son!=root)
    {
        tem=f[son];
        f[son]=root;
        son=tem;
    }
    return root;
}
bool join(int r1,int r2)
{
    int x=fin(r1);
    int y=fin(r2);
    if(x!=y)
    {
        f[x]=y;
        return true;
    }
    return false;
}
int clu()
{
    ans=0;
    for(int i=1;i<=p;i++)
    {
        int u=e[i].u;
        int v=e[i].v;
        if(join(u,v))
        {
            ans+=e[i].w;
            cnt++;
               
        }
    }
    return ans;
}
int main()
{
    //freopen("D:\in.txt","r",stdin);
    int i,j,l,tt;
    while(scanf("%d",&n)!=EOF)
    {
        p=0;
        for(i=2;i<=n;i++)
        {
            p++;
            e[p].u=1;
            e[p].v=i;
            scanf("%d",&e[p].w);
        }
        for(i=2;i<=n;i++)
        {
            for(j=2;j<=n;j++)
            {   
                scanf("%d",&tt);
                p++;
                if(i==j)
                {
                    tt=INF;
                }   
                e[p].u=i;
                e[p].v=j;
                e[p].w=tt;
            }
        }
        sort(e+1,e+p+1,cmp);
        cnt=0;
        init(n);
        cout<<clu()<<endl;
    }
    return 0;
}



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

林下的码路

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值