codeforces 976D Degree Set 双指针

题意

给定n(300)和n个数的数组d1,d2,…,dn(从小到大,不重复,di最大1000),要求构造一个简单无向图使得所有节点的度数组成的集合恰为这个数组d,图的节点数为dn+1(即图中至少有一个点与其他所有点有边连接),输出边数和所有边(节点从1到dn+1编号)

分析

题目实际上要求对集合中的每个值,都有某些点的度数为这个值.

把当前节点从1到dn+1编号,由小到大遍历
如果一条边是由遍历到当前节点时发出的,称为主动连接,否则称为被动连接
构造时,每个点如果可以进行主动连接,那么就向它后面的所有点主动连接,否则不进行连接.
举个例子,8个点的图,是否进行主动连接的bool值依次为
1 1 0 0 1 1 0 0 (最后一个点连不连实际无所谓)
它们的度数就分别是
7 7 2 2 4 4 4 4
遍历到某个点时它的被动连接数为
0 1 2 2 2 3 4 4
这样,每个点被动连接的个数会按节点编号逐渐增大

记录当前已被连接的个数ready=0,以及集合的前后双指针l=1,r=n,
遍历到每个点时依次判断三种情况
1.save[l]==ready
意思是被动连接的个数恰好等于当前左指针位置的值,此时不连接,l++,r–
2.ready+总点数-当前点数==save[r]
意思是主动连接的个数+被动连接的个数恰好等于当前右指针位置的值,连接,ready++
3.其他情况
此时被动连接的个数等于上一个左指针位置的值,不连接,不修改记录.

1号节点符合情况2.每个情况1都不会连续出现,(应该)可以证明集合中所有的值都会出现.

至于边数的问题,时间很宽裕,多算一遍就好.

代码

/* LittleFall : Hello! */
#include <bits/stdc++.h>
typedef long long ll;
using namespace std;
inline int read();
inline void write(int x);
const int M = 1024;
int save[M];

int main(void)
{
    int n=read(),ans=0;
    for(int i=1;i<=n;i++)
        save[i]=read();
    int vecs=save[n]+1;

    int r=n,l=1,ready=0;
    for(int vec=1;vec<vecs;vec++)
    {
        if(save[l]<=ready)
            l++,r--;    
        else if(ready+vecs-vec==save[r])
            ready++,ans+=vecs-vec;
    }
    printf("%d\n",ans );

    r=n,l=1,ready=0;
    for(int vec=1;vec<vecs;vec++)
    {
        if(save[l]<=ready)
            l++,r--;
        else if(ready+vecs-vec==save[r])
        {
            for(int j=vec+1;j<=vecs;j++)
                printf("%d %d\n",vec,j );
            ready++;
            ans+=vecs-vec;
        }
    }

    return 0;
}


inline int read()
{
    int x=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9') {if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
    return x*f;
}
inline void write(int x)
{
     if(x<0) putchar('-'),x=-x;
     if(x>9) write(x/10);
     putchar(x%10+'0');
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值