ZOJ 3984 Graph Generator(2017CCPC秦皇岛 D)可撤销并查集+思维

题目传送门

题意:

T组数据,每组数据给你n(<=1e5)个点,m(<=min(1e5,n*(n-1)/2))条无向边,你需要构造一个合法的序列,其中每一项输出三个数:

点x,你选择的点集的大小,然后给出这些点。(初始为空图,之后系统会增加一个新点x,并将x与你给出的这些点所在的连通块的每一个结点连边!)

最后形成题中给你的图。问你是否存在合法方案,如果存在输出Yes和任意一种方案,否则输出No。

思路:

直接正着做找合法序列太难了,我们考虑从原图往下一个一个拆点,拆成一个合法序列,最后倒着输出。

注意到我们拆这一个点u,它的度数 必须 等于 所在连通块的大小 -1 。

因此我们将所有点按度数从小到大排序,先用并查集(注意,这里用的是可撤销并查集,因为要拆点会造成连通块数变多,不能路径压缩!!!)建立初始的连通块,记录度数大的点向度数小的点连的边。

之后再从度数最大的点开始拆,一直拆到度数最小的点(显然度数最大的点一定是后面才添加进图里去的)。

对于某个点u,如果它的度数d[u] != u所在的连通块的大小 -1 则答案一定不存在。否则u点便可以拆下来。

然后更新u所连的所有点的度数(显然连的点的度数一定小于等于u的度数)。然后撤销u向度数小的点连的边,记录答案。

并查集这一块还是用的不够熟练,还需要多加练习。

代码:

#include<bits/stdc++.h>
#define ll long long
#define inf 0x3f3f3f3f
#define mst(head,x,n) memset(head+1,x,n*sizeof(head[0]))
#define rep(i,a,b) for(int i=(a);i<=(b);i++)
#define dep(i,a,b) for(int i=(a);i>=(b);i--)
using namespace std;
const int maxn=1e5+5;
//const double pi=acos(-1.0);
//const double eps=1e-9;
//const ll mo=1e9+7;
int n,m,k;
int mp[maxn];
int tmp,cnt;
int flag;
bool ok[maxn];
vector<int>vc[maxn];
template <typename T>
inline void read(T &X){
    X=0;int w=0; char ch=0;
    while(!isdigit(ch)) {w|=ch=='-';ch=getchar();}
    while(isdigit(ch)) X=(X<<3)+(X<<1)+(ch^48),ch=getchar();
    if(w) X=-X;
}
struct node{
    int x,y,fax,szx,hx;
}stk[maxn];
struct st{
    int id;
    int v;
}d[maxn];
struct BCJ{
    int fa[maxn];
    int sz[maxn];
    int h[maxn];
    void init(int n){
        cnt=0;
        rep(i,0,n){
            fa[i]=-1;
            sz[i]=1;
            h[i]=0;
        }
    }
    int find(int x){return fa[x]==-1?x:find(fa[x]);}
    int un(int ox,int oy){
        int x=find(ox);
        int y=find(oy);
        if(x==y) return 0;
        if(h[x]>h[y]) swap(x,y);
        if(h[x]==h[y]) h[y]++;
        stk[++cnt]=node{x,y,fa[x],sz[x],h[x]};
        fa[x]=y;
        sz[y]+=sz[x];
        return 1;
    }
    void undo(int num){
        while(num--){
            node k=stk[cnt--];
            h[k.x]=k.hx;
            sz[k.y]-=k.szx;
            fa[k.x]=k.fax;
        }
    }
    int get(int x){
        x=find(x);
        return sz[x];
    }
}bcj;
bool cmp(st x,st y){
    return x.v<y.v||(x.v==y.v&&x.id<y.id);
}
int main(){

#ifdef ONLINE_JUDGE
#else
    freopen("D:/Temp/in.txt", "r", stdin);
#endif

    int T,cas=1;
    read(T);
    while(T--)
    {
        //cout<<" &^% "<<endl;
        read(n);read(m);
        bcj.init(n);
        rep(i,1,n) {
            vc[i].clear();
            d[i].v=0;
            d[i].id=i;
            ok[i]=false;
        }
        rep(i,1,m){
            int u,v;
            read(u);read(v);
            vc[u].push_back(v);
            vc[v].push_back(u);
            d[u].v++;d[v].v++;
        }
        sort(d+1,d+1+n,cmp);
        vector<vector<int> >tp(n+1);
        //cout<<" *&(^^ "<<endl;
        for(int i=1;i<=n;i++){
            int u=d[i].id;
            mp[u]=i;
            for(int j=0;j<vc[u].size();j++){
                int v=vc[u][j];
                if(!ok[v]) continue;
                if(bcj.un(u,v)){
                    tp[u].push_back(v);
                }
            }
            ok[u]=true;
        }
        //cout<<" *&(^^ "<<endl;
        vector<pair<int,vector<int> > >ans;
        int fg=1;
        dep(i,n,1){
            int u=d[i].id;
            //cout<<u<<" "<<d[i].v<<" "<<bcj.get(u)-1<<endl;
            if(d[i].v!=bcj.get(u)-1) {
                puts("No");
                fg=0;
                break;
            }
            for(int j=0;j<vc[u].size();j++){
                int v=vc[u][j];
                d[mp[v]].v--;
            }
            ans.push_back(make_pair(u,tp[u]));
            bcj.undo(tp[u].size());
        }
        if(!fg) continue;
        puts("Yes");
        reverse(ans.begin(),ans.end());
        for(int i=0;i<ans.size();i++){
            int nm=ans[i].first;
            vector<int> mm=ans[i].second;
            int msz=mm.size();
            printf("%d %d",nm,msz);
            for(int j=0;j<msz;j++){
                printf(" %d",mm[j]);
            }
            puts("");
        }
    }
    return 0;
}

 

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值