【VJ】gxu warm up D-任意模数在线快速数论变换 题解

传送门:任意模数在线快速数论变换
标签:构造

题目大意

给出n个点,每个点有一个属性ai,要求你构造出一个连通图,且满足以下三个条件:1、该图中无自环、无重边;2、对任意的1<=i<=n,有di<=ai(其中di是点i的度数);3、该图的直径尽可能大(直径是一个连通图中距离最远的两个点直接的距离)。给出的数据存在无解的情况。
输入:一个正整数n(3<=n<=500),以及n个正整数a1,a2,······,an(1<=ai<=n-1)。
输出:如果无解就输出“NO”。否则输出“YES”,然后输出一个正整数m,代表你构造的图中的边数,再输出m行,每行两个正整数分别代表边的两个端点。

算法分析

  • 先考虑无解的情况。我们知道,最简单的连通图是树。如果给出的数据连一棵树都构造不出来,那么必然是无解的。而离散数学告诉我们,只有总度数/2>=n-1时才有可能得到一棵树,那么我们就先以这个条件特判。显然当某个结点的度数小于等于0时也时是非法的,但题目保证了ai>=1,就不用担心这个问题了。
  • 有解的情况下要保证连通图的直径最大,我们当然希望它最后是一条很长的链,最好是包含n个节点的链,但在这条链上只有两端的度数是1,其它点的度数都是2。既然如此,我们就尽可能地让度数大的点往中间放,度数小的放两端。如果ai为1的点的数量大于2,我们就只能舍弃,把它们放到一些分支上。
  • 所以m的值就是度数大于等于2的点的数量加上min(度数为1的点的个数,2)。然后还要处理一下细节:把n个点按度数排序,然后先找到第一个度数大于等于2的点,放两个点在两端。剩下的点挨个遍历,如果当前点的度数有剩,就插一个度数为1的点到上面,然后ai–。如果度数为1的点用完了,就直接退出循环。因为m可以提前计算,所以可以一边遍历一边输出答案。算法总体时间复杂度为排序复杂度,即O(nlogn)。

代码实现

#include <iostream>
using namespace std;
#include <algorithm>
#include <queue>
struct Item{
	int x,id;
}I[505];
bool rule(Item a,Item b){
	return a.x<b.x;
}
int main(){
	long long i,m,i0,i1,j,x,n,y,x1,y1,x2,y2,mw,len,T,k,ans=1e9;
	long long p,sum;
	char ch;
	ios::sync_with_stdio(false);
	cin.tie(0);cout.tie(0);
	cin>>n;
	sum=0LL;
	for(i=1;i<=n;i++){
		cin>>I[i].x;
		I[i].id=i;
		sum+=I[i].x;
	}
	if(sum/2<n-1){
		cout<<"NO";
		return 0;
	}
	cout<<"YES ";
	sort(I+1,I+n+1,rule);
	i=1;
	while(I[i].x==1)i++;
	I[i].x++;
	I[n].x++;
	cout<<n-max(i-3,0LL)-1<<'\n'<<n-1<<'\n';
	p=1;
	if(I[1].x==1){
		cout<<I[1].id<<' '<<I[n].id<<'\n';
		I[n].x--;
		p=2;
	}
	for(j=i;j<=n;j++){
		if(j!=n)cout<<I[j].id<<' '<<I[j+1].id<<'\n';
		while(p<i&&I[j].x>2){
			cout<<I[p].id<<' '<<I[j].id<<'\n';
			I[j].x--;
			p++;
		}
	}
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值