【训练题70:优先队列套队列】Go Make It Complete | Gym 102001G

题意

  • 【Go Make It Complete | Gym 102001G】
    给定一个 n n n 个点 m m m 条边的图。
    每次选择两个没有边直接相邻的点,把 d e g [ i ] + d e g [ j ] deg[i]+deg[j] deg[i]+deg[j] 即两个节点的度的值放入集合 S S S 中,然后连接这两个点。
    直到这个图变成一个完全图。
    求最大化集合中的最小值。
  • 2 ≤ n ≤ 500 2\le n\le 500 2n500
    0 ≤ m < n ( n − 1 ) 2 0\le m<\frac{n(n-1)}{2} 0m<2n(n1)

思路

  • 考虑贪心,每次拉出来最大的 d e g [ i ] + d e g [ j ] deg[i]+deg[j] deg[i]+deg[j] 的两个点 ( i , j ) (i,j) (i,j),然后连边。
    然后更新, d e g [ i ] + + , d e g [ j ] + + deg[i]++,deg[j]++ deg[i]++deg[j]++
    然后加新的点对,放进优先队列跑。
    但如果一开始是一个空图,每次优先队列里都要放 O ( n ) O(n) O(n) 个点对,光内存就 O ( n 3 ) O(n^3) O(n3) 了,时间复杂度大约 O ( n 3 log ⁡ n ) O(n^3\log n) O(n3logn)
  • 考虑优化。我们记一个 i n [ x ] [ y ] in[x][y] in[x][y] 表示在优先队列中,我们目前的最优 d e g [ x ] + d e g [ y ] deg[x]+deg[y] deg[x]+deg[y] 的值。如果更新到某个时间,发现 d e g [ x ] + d e g [ y ] > i n [ x ] [ y ] deg[x]+deg[y]>in[x][y] deg[x]+deg[y]>in[x][y],我们再让点对 ( x , y ) (x,y) (x,y) 进优先队列。但是复杂度对于空图来说没有本质变化,对于满图的复杂度倒是优化了不少。
  • 考虑优化。我们记 a n s ans ans 为目前的 min ⁡ { d e g [ i ] + d e g [ j ] } \min\{deg[i]+deg[j]\} min{deg[i]+deg[j]},容易发现如果 d e g [ x ] + d e g [ y ] ≥ a n s deg[x]+deg[y]\ge ans deg[x]+deg[y]ans,我们就直接连边吧,没必要放进优先队列里去了。我们直接连边,然后增加他们的度,就不放到优先队列里去了。但是只这样处理是错误的。 因为我们增加了 y y y 的度,相应有可能某个 z z z 满足 d e g [ y ] + d e g [ z ] < a n s deg[y]+deg[z]<ans deg[y]+deg[z]<ans,但是 d e g [ y ] + d e g [ z ] > deg[y]+deg[z]> deg[y]+deg[z]> 优先队列中的其他值。
    所以我们需要一个队列,存储这一趟我们直接连边的那些点的下标。如果队列非空,我们继续找是否有 z z z 满足可以直接连边,或者更优与 i n [ z ] [ n o w ] in[z][now] in[z][now] 然后放入优先队列中。
  • 可以感性地想,如果图较满,我们进队列的次数不会很多,大多数都是优先队列的复杂度。
    如果图较空,则 a n s ans ans 较小,大多数点对之前都能满足度和大于等于 a n s ans ans,大多数都是队列的复杂度,没有 l o g log log

代码

  • 综合复杂度: O ( n 2 l o g + n 3 ) O(n^2log+n^3) O(n2log+n3)
    T i m e s : 78 / 1000 ( M s ) Times:78/1000(Ms) Times:78/1000(Ms)
    M e m o : 5.6 ( M b ) Memo:5.6(Mb) Memo:5.6(Mb)
#include <bits/stdc++.h>
#define IOS ios::sync_with_stdio(false);cin.tie(NULL);cout.tie(NULL);
using namespace std;
typedef long long ll;
void show(){std::cerr << endl;}template<typename T,typename... Args>void show(T x,Args... args){std::cerr << "[ " << x <<  " ] , ";show(args...);}

const int MAX = 505;
const int MOD = 998244353;
const int INF = 0x3f3f3f3f;
const ll LINF = 0x3f3f3f3f3f3f3f3f;
const double EPS = 1e-7;

int deg[MAX];
bool mp[MAX][MAX];
int in[MAX][MAX];
bool vis[MAX];
int main()
{
    int n,m;cin >> n >> m;
    for(int i = 1;i <= m;++i){
        int ta,tb;cin >> ta >> tb;
        mp[ta][tb] = mp[tb][ta] = 1;
        deg[ta]++;
        deg[tb]++;
    }
    priority_queue<pair<int,int> >Q;
    for(int i = 1;i <= n;++i)
    for(int j = i + 1;j <= n;++j)
    if(!mp[i][j]){
        in[i][j] = in[j][i] = deg[i] + deg[j];
        Q.push(make_pair(deg[i]+deg[j],i*1000+j));
    }

    int ans = INF;

    while(!Q.empty()){
        int d = Q.top().first;
        int x = Q.top().second / 1000;
        int y = Q.top().second % 1000;
        Q.pop();

        if(mp[x][y])continue;

        ans = min(ans,deg[x] + deg[y]);

        mp[x][y] = mp[y][x] = 1;
        deg[x]++;deg[y]++;

        queue<int>QQ;QQ.push(x);QQ.push(y);vis[x] = vis[y] = 1;
        while(!QQ.empty()){
            int now = QQ.front();QQ.pop();
            vis[now] = 0;

            for(int i = 1;i <= n;++i){
                if(now!=i && !mp[now][i] && deg[now] + deg[i] > in[now][i]){
                    if(deg[now] + deg[i] >= ans){
                        mp[now][i] = mp[i][now] = 1;
                        deg[now]++;deg[i]++;
                        if(!vis[now]){QQ.push(now),vis[now] = 1;}		// 它不在队列中,我就加到队列中
                        if(!vis[i]){QQ.push(i),vis[i] = 1;}
                    }else{
                        in[now][i] = in[i][now] = deg[now] + deg[i];	// 更优与优先队列中的,放到优先队列中
                        Q.push(make_pair(in[now][i],i * 1000 + now));
                    }
                }
            }

        }

    }
    cout << ans;
    return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值