2018 ICPC 沈阳网络预赛 Fantastic Graph (优先队列)

【传送门】https://nanti.jisuanke.com/t/31447

 

【题目大意】:有一个二分图,问能不能找到它的一个子图,使得这个子图中所有点的度数在区间【L,R】之内。

【题解】首先我们分这几种情况讨论:

(1)如果集合U,V中存在某个点,它的度数小于L,那么肯定就不满足题意,直接输出No。所以对任意i, degree[i] >= L

(2)如果集合U,V中所有点的度数都在给定区间内,直接输出Yes。

(3)如果集合U,V中存在某些点的度数大于R,则需要减少与它关联的边,直到它的度数小于等于R

 

那么如何删边呢?我们把某个度数过大的点X的所有终点放入优先队列中,这个队列根据点的度数排好序,度数大的点Y在队首,当X的度数大于R时,我们取出队首Y,如果Y度数大于L,代表可以删边,X,Y的度数均自减1。

如果X的度数大于R时,队首Y的度已经不能再减(已经小于等于L了),那么就表明找不到这样的子图,输出No。

把所有的点都按照上述过程扫一遍,看中途是不是会判定找不到这样的子图。

时间复杂度:O(N*LogN)

有网上题解说可以使用网络流,暂时记下以后再探讨。

 

【AC代码】

#include <queue>
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
const int maxn = 41000;
vector<int> G[maxn];//存图 
int offset = 20010;//两个集合标号都从1开始为了区分设置一个偏移量,右边的序号都加上偏移量  
int degree[maxn];//存度数 


// 自定义优先级  按度优先 
struct cmp  
{  
    bool operator()(const int &t1,const int &t2)  
    {  
        return degree[t1] < degree[t2];
    }  
};

//初始化 
void init(){
    memset(degree , 0, sizeof degree);
    for(int i=0; i<maxn; i++)    G[i].clear();
}

int main(){
    int n,m,k;
    int l,r;
    int u,v;
    int ca = 1;
    while(scanf("%d %d %d", &n,&m,&k) != EOF){
        init();
        int flag = 1;
        scanf("%d %d",&l, &r);
        //建图,记录度数 
        for(int i=1; i<=k; i++){
            scanf("%d %d",&u, &v);
            G[u].push_back(v+offset);
            G[v+offset].push_back(u);
            degree[u]++;
            degree[v+offset]++;
        }
        //只要有一个点度数小于L就GG 
        for(int i=1 ; i<=n; i++){
        //    cout<<" "<<degree[i]<<endl;
            if(degree[i] < l){
                flag = 0;
                break;
            }
        }
        for(int i=1+offset; i<=m+offset; i++){
        //    cout<<" "<<degree[i]<<endl;
            if(degree[i] < l){
                flag = 0;
                break;
            }
        }
        if(!flag){
            printf("Case %d: No\n" , ca++);
            continue;
        }
            
        //开始执行步骤(3) 对左边集合所有点  删边减度 
        for(int i=1; i<=n; i++){
            if(flag == 0)    break;
            
             priority_queue<int,vector<int>,cmp> q; //定义优先队列 
             while(!q.empty()) q.pop();
             
             //对每一个点X的终点入队等待删边 
             for(int j=0; j<G[i].size(); j++){
                 q.push(G[i][j]);
            }
            //只要这个点 X的度数大于R必须删边减度 
            while(degree[i] > r){
                int f = 0;
                //取出队首 
                int tp = q.top();
                int t = degree[tp];
                q.pop();
                if(t-1 >= l){
                    f = 1;
                    degree[tp] --;
                    degree[i]--;
                    
                }else{
                    f = 0;
                }
                if(degree[tp] >= l+1)
                    q.push(tp);
                if(f == 0){
                    flag = 0;
                    break;
                }
            }    
        }
        
        //一样的操作,对右边集合 
        for(int i=1+offset; i<=m+offset; i++){
            if(flag == 0)    break;
             priority_queue<int,vector<int>,cmp> q;
             while(!q.empty()) q.pop();
             
             for(int j=0; j<G[i].size(); j++){
                 q.push(G[i][j]);
            }
            
            while(degree[i] > r){
                int f = 0;
                int tp = q.top();
                int t = degree[tp];
                q.pop();
                if(t-1 >= l){
                    f = 1;
                    degree[tp] --;
                    degree[i]--;
                    
                }
                if(degree[tp] >= l+1)
                    q.push(tp);
                if(f == 0){
                    flag = 0;
                    break;
                }
            }    
        }
        
        ///最终判定 
        if(flag)    printf("Case %d: Yes\n" , ca++);
        else    printf("Case %d: No\n" , ca++);    
        
    }
}

 

posted @ 2018-09-09 09:46  西风show码  阅读(184)  评论(0编辑  收藏  举报