传送门:任意模数在线快速数论变换
标签:构造
题目大意
给出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;
}