【OI做题记录】【BZOJ】【Apio2008】免费道路

13 篇文章 0 订阅
3 篇文章 0 订阅

BZOJ题号3624

题目描述

输入描述


输出描述


输入样例

5 7 2
1 3 0
4 5 1
3 2 0
5 3 1
4 3 0
1 2 1
4 2 1

输出样例

3 2 0
4 3 0
5 3 1
1 2 1

题目分析

试题大意

给出n个村庄,m条路,其中有0种路和1种路,要求村庄互相连通,保存道路最少,而且保存k条0种路。

输出保存的路径。

①算法确定

看到这道题我就窃喜,果然是大水题!

这道题有两个条件:在所有的村庄都连通时保存的免费道路最少,保存的石子路为K条。要最少,最少就是n-1条路,很明显就是最小生成树。但是问题在于:我们要求k条0种边。这种删边题目,要么是最小生成树,要么是最小割。

②算法细则

我们不能知道最后的结果,但是我们来一步一步看:

A.有没有一些0种边,必须要选择,如果不选择就不能联通?

这些边是我们答案的一部分(如果有部分分那就太好了)

这些边其实很容易求:先用最小生成树算法,用1种边连接,这之后有一些点已经联通了,但是还有一些点没有联通。剩下的这些点就不得不用0种边联通了。到此为止还是最小生成树,如果完全连成一个图,那么边数是n-1。

(因为这里求的是最少的边数,所以每条边的权为1。那么这个最小生成树的贪心法则直接就是能让一个新的点进入联通图中,就是最优的。这一点不证自明。)

B.接下来怎么做呢?

我们现在得到了一些必须要选的0种边。我们把图中的边清空,先把必须要加上的0种边加上去。这种时候,既然要K条0种边,我就用最小生成树把0种边加到K条。如果现在还不行,就加1种边。这样为什么是最优的?看下面:

加必须的0种边(不加不能联通)

0种边加到K(不加不是我们的答案)

加1种边(不加你能干什么)

所以这样就得出了我们的结果。

C.有什么可以优化呢?

1、如果求必须要加的边时,必须加的边超过K,或者必须加的0种边和已经加的1种边和不为n-1(得不到联通),直接输出无解。

2、如果最后得到的图中,0种边加不到K,输出无解。(不一定是总的0种边没有K条,而是加到K条不是最小生成树)

代码

不给你看

#include<cstdio>
#include<cstdlib>
#include<cstring>
const int M=100005;
const int N=20005;
int n,m,k,top;
struct qq{
	int x,y;
	bool stone;
}ans[M],a[M];
int num[2];
int fa[N];
bool tf[M];
int find(int x)
{
	if(fa[x]==x)return fa[x];
	else fa[x]=find(fa[x]);
	return fa[x];
}
void done(bool type,int maxx)
{
	for(int i=1;i<=m;i++)
	{
		if(a[i].stone==type && num[type]<maxx)
		{
			int fx=find(a[i].x),fy=find(a[i].y);
			if(fx!=fy)
			{
				fa[fx]=fy;
				ans[++top].x=a[i].x;ans[top].y=a[i].y;ans[top].stone=a[i].stone;
				tf[i]=true;
				num[type]++;
			}
		}
	}
}
int main()
{
	num[0]=num[1]=top=0;
	scanf("%d %d %d",&n,&m,&k);
	for(int i=1;i<=n;i++)fa[i]=i;
	for(int i=1;i<=m;i++)
	{
		scanf("%d %d %d",&a[i].x,&a[i].y,&a[i].stone);
	}
	done(1,999999999);
	done(0,999999999);
	if(num[0]+num[1]!=n-1 || num[0]>k)
	{
		printf("no solution\n");
		return 0;
	}
	num[0]=num[1]=top=0;
	for(int i=1;i<=n;i++)fa[i]=i;
	for(int i=1;i<=m;i++)
	{
		if(a[i].stone==0 && tf[i]==true)
		{
			int fx=find(a[i].x),fy=find(a[i].y);
			if(fx!=fy)
			{
				fa[fx]=fy;
				ans[++top].x=a[i].x;ans[top].y=a[i].y;ans[top].stone=a[i].stone;
				num[0]++;
			}
		}
	}
	done(0,k);
	done(1,99999999);
	if(num[0]<k)
	{
		printf("no solution\n");
		return 0;
	}
	for(int i=1;i<=top;i++)
		printf("%d %d %d\n",ans[i].x,ans[i].y,ans[i].stone);
	return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值