Luogu CF22C 【System Administrator】

题目大意:

这个题目的大概意思就是求一个图,两个点之间最多只能连接一次(这个地方题面貌似没翻译啊),开始的时候这个图是连通的,然后去掉一个顶点 $ v $ ,使得整个图不再连通(连通的意思就是有一些点无法通过任何方式到达另一个点)。

思路:

这个题目大概的思路算是一个贪心吧。(原谅我这个蒟蒻为了想这个贪心想了一整天)。如果我们要尽量满足这个目标,就必须要构造下面的这个图形:

1440436-20180826214631519-1975794951.png

其中最上面的点Q是所有点中除了$ v $ 以外的一个点,为了方便计算,如果v!=n那么这个点就是 v+1 ,如果v==n那么这个点就是 v-1 ,然后现在这个图中的所有的边就是为了保证开始时图的连通性所弄的边。

至于为什么这几个点要这么摆,我们之后再讲。这个时候我们可以发现如果 $ m < (n-1) $ 那么我们连一开始的连通性都无法保证,直接输出-1走人。

如果可以保证了我们再这么做,由于整个图是连通的,如果我们把 $ v $ 删掉了以后,图一定会分成两个连通块(如果不知道连通块就去问老师)。 所以我们要保证去掉 $ v $ 后两个连通块之间都没有边相连,所以点 $ v $ 的位置一定在这两个连通块之间,并且所有从一个连通块到达另一个连通块的边一定是要经过点 $ v $ 的(要不然删掉点 $ v $ 后整个图还是连通的)。

所以我们接下来剩下的 $ m-(n-1) $ 条边就只能分别在两个连通块里添加,而且我们需要一种方案,能够使得剩下的边尽量被用完,如果不能被用完,那么输出-1走人。

之后我们要确定一边的连通块里的点的个数,要如何安排点的个数使得图能尽量能让剩下的边能添加进去。我们设一个连通块里的点的个数 $ x $ 那么另一个连通块里的点的个数为 $ (n-1-x) $ 很显然当一个连通块里有n个点的时候我们可以最多在这个连通块里连
$ \frac{(x-1)\times (x-1+1)}{2} $ 条边,而另一个连通块里则最多可以连 $ \frac{(n-1-x)\times (n-2-x)}{2} $ 条边。所以我们可以把这两个式子加起来,去掉常数后的到 $ x^2+(1-n)\times x $ 我们发现当 $ x=\frac{1-n}{2} $ 的时候这个式子的值是最小的,而 $ \frac{1-n}{2} $ 本身是个负数,所以我们可以得到这个性质:$ x $ 越大,这个图所能连的最多的边数越多。

所以我们让一边的连通块的个数为 $ n-2 $ , 另一边的连通块的个数为 $ 1 $ 就可以了,之后我们先判断一下剩下的边会不会超过这个连通块所能连的最多的边的数量,如果不行就输出 $ -1 $ 走人,之后把剩下的边往个数为 $ n-2 $ 的那个连通块里添加,这样就解决这个问题了。

代码:

#include <iostream>
#include <cstdio>
#include <vector>
#include <algorithm>

using namespace std;

int n,m,v;
vector <int> num;
vector <pair <int,int> > ans;

int main()
{
    scanf("%d %d %d",& n,& m,& v);

    if(m<(n-1) || ((m-(n-1))>(n-3)*(n-2)/2)){ //两个Bug
        printf("-1\n");
        return 0;
    }
    
    //首先要保证图连通
    for(register int i=1;i<=n;i++){
        if(i==v) continue;
        else m--,printf("%d %d\n",i,v);
    }
    
    //如果刚好连完了就停止程序要不然就要死循环
    if(m==0) return 0;
    
    if(v!=n){
        for(register int i=1;i<=n;i++){
            if(i==v || i==v+1) continue;
            num.push_back(int(i));
        }
    }
    else{
        for(register int i=1;i<=n;i++){
            if(i==v || i==v-1) continue;
            num.push_back(int(i));
        }
    }
    
    register int i=0,j=1;
    while(true){
        if(j==(int)num.size()) i++,j=i+1;
        if(i==(int)num.size()-1) break;
        printf("%d %d\n",num[i],num[j]);
        m--,j++; if(m==0) break;
    }
    
    return 0;
}

转载于:https://www.cnblogs.com/lixiao189/p/9539093.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值