[平面图欧拉定理]ONTAK2015. Ogród zoologiczny

给定n个点m条边的平面图,q个询问,每次询问给出k个点,问这k个点的导出子平面图的域(不包括无穷域)的个数

n<=5e4,m<=2e5,q<=1e6,Σk<=5e6

平面图有一个性质

|E|3×n+6

平面图欧拉定理:一个连通平面图满足

R=|E||V|+2

其中 R 是域的个数(包括无穷域), E 是边集, V 是点集

那么如果平面图不连通

R=|E||V|+K+1

K 为联通块数量。

那么可以类似求拓扑序的方法,每次把当前图中度数最小的点拿出来,求出序列 A,一条 u v 的无向边可以变成一条 u v 的有向边,其中 u A 中的位置小于 v A 中的位置。这就相当于给边重点向。

这样就每次询问就最多只要枚举3n+6条边就可以求出平面图的边集,然后并查集求一下联通块数量就可以了。

#include <cstdio>
#include <iostream>
#include <algorithm>
#include <queue>

using namespace std;

typedef pair<int,int> PAR;

const int N=400010;

int n,m,cnt,icnt,G[N],d[N],iG[N],vis[N];
struct edge{
    int t,nx;
}E[N],iE[N];

inline char nc(){
    static char buf[100000],*p1=buf,*p2=buf;
    return p1==p2&&(p2=(p1=buf)+fread(buf,1,100000,stdin),p1==p2)?EOF:*p1++;
}

inline void rea(int &x){
    char c=nc(); x=0;
    for(;c>'9'||c<'0';c=nc());for(;c>='0'&&c<='9';x=x*10+c-'0',c=nc());
}

inline void Insert(int x,int y){
    E[++cnt].t=y; E[cnt].nx=G[x]; G[x]=cnt; d[x]++;
    E[++cnt].t=x; E[cnt].nx=G[y]; G[y]=cnt; d[y]++;
}

priority_queue<PAR> Q;

inline void addedge(int x,int y){
    iE[++icnt].t=y; iE[icnt].nx=iG[x]; iG[x]=icnt;
}

int q,it,iQ[N],fa[N],ivis[N],a[5000010];

int find(int x){
    return fa[x]==x?x:fa[x]=find(fa[x]);
}

int main(){
    rea(n); rea(m);
    for(int i=1,x,y;i<=m;i++)
        rea(x),rea(y),Insert(x,y);
    for(int i=1;i<=n;i++)
        if(d[i]<=6) Q.push(PAR(-d[i],i));
    while(!Q.empty()){
        int x=Q.top().second,y=-Q.top().first; Q.pop();
        if(d[x]!=y) continue; vis[x]=1;
        for(int i=G[x];i;i=E[i].nx)
            if(!vis[E[i].t]){
                d[E[i].t]--; Q.push(PAR(-d[E[i].t],E[i].t));
                addedge(x,E[i].t);
            }
    }
    rea(q);
    while(q--){
        int V,E=0,L=0,x;
        rea(V); it++;
        for(int i=1;i<=V;i++) 
            rea(a[i]),iQ[a[i]]=it,fa[a[i]]=a[i];
        for(int k=1;k<=V;k++){
            int x=a[k];
            for(int i=iG[x];i;i=iE[i].nx)
                if(iQ[iE[i].t]==it){
                    fa[find(x)]=find(iE[i].t);
                    E++;
                }
        }
        for(int i=1;i<=V;i++)
            if(ivis[find(a[i])]!=it) ivis[find(a[i])]=it,L++;
        printf("%d\n",E-V+L);
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值