BJ模拟:医院(支配树)

传送门

题解:
新建 S S <script type="math/tex" id="MathJax-Element-22">S</script>点连向所有普通护士。
注意到两个特殊护士能放假的等价条件是支配树上的lca为S,一个普通护士和一个特殊护士不能放假的条件是后者在支配树上位于前者子树中,建出支配树即可。

#include <bits/stdc++.h>
using namespace std;
typedef long long LL;

const int RLEN=1<<18|1;
inline char nc() {
    static char ibuf[RLEN],*ib,*ob;
    (ib==ob) && (ob=(ib=ibuf)+fread(ibuf,1,RLEN,stdin));
    return (ib==ob) ? -1 : *ib++;
} 
inline int rd() {
    char ch=nc(); int i=0,f=1;
    while(!isdigit(ch)) {if(ch=='-')f=-1; ch=nc();}
    while(isdigit(ch)) {i=(i<<1)+(i<<3)+ch-'0'; ch=nc();}
    return i*f;
}
inline void W(LL x) {
    static int buf[50];
    if(!x) {putchar('0'); return;}
    if(x<0) {putchar('-'); x=-x;}
    while(x) {buf[++buf[0]]=x%10; x/=10;}
    while(buf[0]) {putchar(buf[buf[0]--]+'0');}
}

const int N=1e5+50;
int n,k,S,ind;
int fa[N],anc[N],mnsd[N],sze[N];
int dfn[N],id[N],sdom[N],idom[N];
vector <int> pre[N];
vector <int> buc[N];
vector <int> edge[N];

inline void add(int x,int y) {
    edge[x].push_back(y);
    pre[y].push_back(x);
}
inline void dfs(int x,int f) {
    anc[x]=x; dfn[x]=++ind; id[ind]=x; 
    sdom[x]=mnsd[x]=x; fa[x]=f;
    for(auto v:edge[x]) if(!dfn[v]) dfs(v,x);
}
inline void ga(int x) {
    if(anc[x]==x) return;
    ga(anc[x]);
    mnsd[x]=(dfn[sdom[mnsd[x]]]<dfn[sdom[mnsd[anc[x]]]]) ? mnsd[x] : mnsd[anc[x]];
    anc[x]=anc[anc[x]];
}
inline int eval(int x) {return ga(x),mnsd[x];}
inline void find_idom() {
    dfs(S,0);
    for(int i=ind;i>=2;i--) {
        int u=id[i];
        for(auto v:pre[u]) {
            if(!dfn[v]) continue;
            int t=eval(v);
            if(dfn[sdom[t]]<dfn[sdom[u]]) sdom[u]=sdom[t];
        }
        int f=fa[u]; anc[u]=f;
        buc[sdom[u]].push_back(u);
        for(auto v:buc[f]) {
            int t=eval(v);
            if(dfn[sdom[t]]<dfn[sdom[v]]) idom[v]=t;
            else idom[v]=sdom[v];
        }
        buc[f].clear();
    }
    for(int i=2;i<=ind;i++) {
        int u=id[i];
        if(idom[u]!=sdom[u]) idom[u]=idom[idom[u]];
    }
}
int main() {
    n=rd(), k=rd(), S=n+1;
    for(int i=1;i<=k;i++) sze[i]=1;
    for(int i=k+1;i<=n;i++) add(S,i);
    for(int i=1;i<=k;i++) 
        for(int j=rd();j;j--) add(rd(),i);
    find_idom(); int j=0;
    for(int i=1;i<=k;i++)  j+=(!dfn[i]);
    W(j); putchar('\n');
    for(int i=1;i<=k;i++) if(!dfn[i]) W(i), putchar(' '); if(j) putchar('\n');
    for(int i=ind;i>=2;i--) sze[idom[id[i]]]+=sze[id[i]];
    LL ans=0, sum=0;
    for(int i=ind;i>=2;i--) {
        int u=id[i]; if(idom[u]!=S) continue;
        ans+=sum*sze[u]; sum+=sze[u];
    }
    ans=(LL)(k-j)*(k-j-1)/2-ans;
    for(int i=2;i<=ind;i++) {
        int u=id[i]; 
        if(u>k) ans+=sze[u];
    }
    W(ans);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值