jzoj5051 【GDOI2017模拟一试4.11】平行宇宙 [贪心,并查集]

28 篇文章 0 订阅
19 篇文章 0 订阅

Problem

众所周知,根据“M 理论”对宇宙的描述,无数的平行宇宙漂浮在广阔无垠的物质世界里,而我们的世界只是众多宇宙泡泡中不起眼的一个。
在公元XXXX 年,伟大的Q 博士终于创造了一种能在宇宙和宇宙中穿梭的通道,从此在科学一个黑暗的领域打开了光明的大门。由于技术还很不成熟,所以刚开始一个宇宙只能打通一条到另外一个宇宙的通道,通道是单向的,经过每条通道所花费的时间是单位1,而我们所在的宇宙被称为1号宇宙或root宇宙。
不久人们决定新建一些单向通道来满足日益膨胀的交通需求,同时有一个要求,就是root宇宙到其他宇宙的时间不能超过k。人们想知道最少要建多少条通道。
20%的数据保证N<=10^4
40%的数据保证N<=10^5
100%的数据保证N<=500000,k<=20000

分析

若干颗环套树。
显然可以贪心,由1往外连边必定是最优的。

树的部分类似拓扑,O(n)可以搞定
环的部分,每次枚举一个起点。预处理一个 nx[i] ,表示往i染色后下一个需要染色的点。显然直接往后跳就可以了。
维护并查集,将跳过的点并在一起,使得每个点只会被跳过一次。
按秩合并,维护到最后一个的距离。一个点到当前集合最右点的距离表示为其祖先节点的tag之和,十分容易维护。

打出错的地方

1) tarjan忘记判断 vis 标记。
2) lt不应该直接赋为 f ,而是gf(f)

Code

SID244770

#include <cstdio>
#include <iostream>
#include <cstring>
#define INF (1<<30)
#define maxn 1000100
#define max(a,b) ((a)>(b)?(a):(b))
#define min(a,b) ((a)>(b)?(b):(a))
using namespace std;
int to[maxn],vis[maxn];
int S[maxn],on[maxn],inS[maxn],d[maxn],ac[maxn],dep[maxn],mor[maxn];
int Q[maxn],L,R;
int n,k,ans;
int nx[maxn],Li[maxn],fst[maxn]; 
int fa[maxn];
int tag[maxn],cnt[maxn],last[maxn],rk[maxn];
int gf(int x,int &dis) {
    dis+=tag[x];
    if (fa[x]==0) return x;
    return fa[x]=gf(fa[x],dis);
}
int merge(int x,int y) {
    if (rk[x]<=rk[y]) { 
        fa[x]=y;
        tag[x]+=cnt[y]+1;
        cnt[y]+=cnt[x]+1;
        if (rk[x]==rk[y]) rk[y]++;
        return y;
    } else {
        fa[y]=x;
        tag[x]+=cnt[y]+1;
        tag[y]-=tag[x];
        cnt[x]+=cnt[y]+1;
        last[x]=max(last[y],last[x]);
        return x;
    }
}
void tarjan(int x) {
    inS[x]=vis[x]=1;
    S[++S[0]]=x;
    if (inS[to[x]]==1) {
        int tmp=S[0];
        do {
            on[S[tmp]]=1;
        } while (S[tmp--]!=to[x]);
    } else 
        if (vis[to[x]]==0)tarjan(to[x]);
    S[S[0]--]=0;
    inS[x]=0;
}
void solveTree() {
    for (int i=1; i<=n; i++) if (vis[i]==0) tarjan(i);
    for (int i=1; i<=n; i++) {
        if (d[i]==0) Q[++R]=i;
        mor[i]=-1;
    }
    mor[1]=k;
    while (L<R) {
        int a=Q[++L];
        if (mor[a]==-1 && on[a]==0) mor[a]=k-1,ans++;
        if ((--d[to[a]])==0 && on[a]==0) Q[++R]=to[a];
        mor[to[a]]=max(mor[to[a]],mor[a]-1);
    }
}
void mkNext() {
    int t=k;
    for (int j=1; j<=Li[0]*2; j++) {
        while (t<Li[0]*2 && (ac[t]==1 || t<j+k)) t++;
        if (t<j+k) t=INF;
        nx[j]=t;
    }
}
int main() {
    freopen("parralle.in","r",stdin);
    freopen("parralle.out","w",stdout);
    cin>>n>>k;
    int x,y; 
    for (int i=1; i<=n; i++) {
        scanf("%d %d",&x,&y);
        to[x]=y,d[y]++;
    }
    solveTree();
    for (int i=1; i<=n; i++) {
        if (on[i]) {
            int t=i,bz=0,mxx=i,mxMor,mi=INF,ad,lt,f,dis;;
            Li[0]=0;
            do{
                Li[++Li[0]]=t;
                on[t]=0;
                if (t==1) bz=Li[0];
                t=to[t];
            } while (on[t]);
            memcpy(Li+Li[0]+1,Li+1,Li[0]*4);
            mxMor=-1;
            for (int p=1; p<=Li[0]*2; p++) {
                mxMor=max(mxMor-1,mor[Li[p]]);
                if (mxMor>=0) ac[p]=1; else ac[p]=0;
            }
            mkNext();
            if (bz) {
                nx[bz]=nx[bz+Li[0]]=INF;
                for (int z=bz+k+1; z<=Li[0]*2; z++) {
                    if (ac[z]==0) {
                        nx[bz]=z;
                        break;
                    }
                }
                for (int z=bz+Li[0]+k+1; z<=Li[0]*2; z++) {
                    if (ac[z]==0) {
                        nx[bz+Li[0]]=z;
                        break;
                    }
                }
            }
            for (int z=1; z<=Li[0]*2; z++) cnt[z]=0,last[z]=z,fa[z]=0,tag[z]=0,rk[z]=0;
            fst[Li[0]*2+1]=INF;
            for (int z=Li[0]*2; z; z--) fst[z]=(ac[z]==0)?z:fst[z+1];
            for (int st=1; st<=Li[0]; st++) {
                ad=0,lt=0;
                for (int j=fst[st]; j<st+Li[0];) {
                    dis=0,f=gf(j,dis);
                    ad+=dis+1;
                    j=nx[last[f]];
                    if (lt!=0 && j<=Li[0]) lt=merge(lt,f);
                }
                mi=min(ad,mi);
                if (mi==1) break;
            }
            ans+=mi;
        }
    }
    cout<<ans<<endl;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值