poj3694 Network (LCT+并查集 | tarjan+树链剖分)

17 篇文章 0 订阅
16 篇文章 0 订阅

Description

A network administrator manages a large network. The network consists of N computers and M links between pairs of computers. Any pair of computers are connected directly or indirectly by successive links, so data can be transformed between any two computers. The administrator finds that some links are vital to the network, because failure of any one of them can cause that data can’t be transformed between some computers. He call such a link a bridge. He is planning to add some new links one by one to eliminate all bridges.

You are to help the administrator by reporting the number of bridges in the network after each new link is added.

Input

The input consists of multiple test cases. Each test case starts with a line containing two integers N(1 ≤ N ≤ 100,000) and M(N - 1 ≤ M ≤ 200,000).
Each of the following M lines contains two integers A and B ( 1≤ A ≠ B ≤ N), which indicates a link between computer A and B. Computers are numbered from 1 to N. It is guaranteed that any two computers are connected in the initial network.
The next line contains a single integer Q ( 1 ≤ Q ≤ 1,000), which is the number of new links the administrator plans to add to the network one by one.
The i-th line of the following Q lines contains two integer A and B (1 ≤ A ≠ B ≤ N), which is the i-th added new link connecting computer A and B.

The last test case is followed by a line containing two zeros.

Output

For each test case, print a line containing the test case number( beginning with 1) and Q lines, the i-th of which contains a integer indicating the number of bridges in the network after the first i new links are added. Print a blank line after the output for each test case.

Sample Input

3 2
1 2
2 3
2
1 2
1 3
4 4
1 2
2 1
2 3
1 4
2
1 2
3 4
0 0

Sample Output

Case 1:
1
0

Case 2:
2
0


分析:
这不是bzoj2959
这道题也可以用LCT搞吗?

LCT动态维护了点的连通性,这道题中我们需要查询图中桥的个数
然而我转念一想,如果我们用LCT+并查集缩点之后,得到的是一棵树,树边的条数就是桥的个数啊
而树的边数等于树的点数减一,那么问题就是转化成了求树中的点数

我觉得应该是可以用LCT搞↓

设置一个计数器 ans ,统计树中边的个数
我们用并查集维护图的连通性,
新加入的一条边是 xy ,如果之前的 x y不连通,我们直接 linkxy ,并且 ans++
然而如果 x y之前是连通的,我们把 x>y 这条路径提出来:

makeroot(x);
expose(y); splay(y);

这样这条路径就变到了一棵 splay 上, splay size 就是这条路径上的结点数
那么这条路径上的边数就是 size1 (这些边就会由桥边变成非桥边)
ans=size1 ,并且把这棵splay合并一下

ans-=size[y]-1;
merge(y,y); update(y);

tip

在写LCT的时候,对于父结点的查询,都要调用findset

写完了,一A撒花

#include<cstdio>
#include<cstring>
#include<iostream>

using namespace std;

const int N=100005;
int belong[N],fa[N],pre[N],ch[N][2],q[N],Q,ans,n,m,size[N],sum;
bool rev[N];

int findset(int x)
{
    if (belong[x]!=x) belong[x]=findset(belong[x]);
    return belong[x];
}

int find(int x)
{
    if (fa[x]!=x) fa[x]=find(fa[x]);
    return fa[x];
}

void clear(int bh)
{
    pre[bh]=ch[bh][0]=ch[bh][1]=0;
}

int get(int bh)
{
    return ch[findset(pre[bh])][0]==bh? 0:1;
}

int isroot(int bh)
{
    int f=findset(pre[bh]);
    return ch[f][0]!=bh&&ch[f][1]!=bh;
}

void update(int bh)
{
    size[bh]=1;
    if (ch[bh][0]) size[bh]+=size[ch[bh][0]];
    if (ch[bh][1]) size[bh]+=size[ch[bh][1]];
}

void push(int bh)
{
    if (rev[bh])
    {
        rev[bh]=0;
        swap(ch[bh][0],ch[bh][1]);
        if (ch[bh][0]) rev[ch[bh][0]]^=1;
        if (ch[bh][1]) rev[ch[bh][1]]^=1;
    }
}

void rotate(int bh)
{
    int far=pre[bh];
    int grand=pre[far];
    int wh=get(bh);
    if (!isroot(far)) ch[grand][ch[grand][0]==far? 0:1]=bh;
    pre[bh]=grand;
    ch[far][wh]=ch[bh][wh^1];
    pre[ch[far][wh]]=far;
    ch[bh][wh^1]=far;
    pre[far]=bh;
    update(far);
    update(bh);
}

void splay(int bh)
{
    int top=0;
    q[++top]=bh;
    for (int i=bh;!isroot(i);i=findset(pre[i]))
        q[++top]=findset(pre[i]);
    while (top) push(q[top]),pre[q[top]]=findset(pre[q[top]]),top--;

    for (int fa;!isroot(bh);rotate(bh))
        if (!isroot(fa=pre[bh]))
            rotate(get(fa)==get(bh)? fa:bh); 
}

void expose(int bh)
{
    int t=0;
    while (bh)
    {
        splay(bh);
        ch[bh][1]=t;
        update(bh);
        t=bh; bh=findset(pre[bh]);
    }
}

void makeroot(int bh)
{
    expose(bh);
    splay(bh);
    rev[bh]^=1;
}

void link(int x,int y)
{
    makeroot(x);
    pre[x]=y;
}

void merge(int x,int y)
{
    belong[findset(x)]=findset(y);
    push(x);
    if (ch[x][0]) merge(ch[x][0],y);
    if (ch[x][1]) merge(ch[x][1],y);
    ch[x][0]=ch[x][1]=0;
}

int main()
{
    int T=0;
    while (scanf("%d%d",&n,&m)!=EOF&&n&&m)
    {
        ans=0;
        memset(pre,0,sizeof(pre)); memset(ch,0,sizeof(ch));
        memset(rev,0,sizeof(rev));
        for (int i=1;i<=n;i++) fa[i]=i,belong[i]=i;
        for (int i=1;i<=m;i++)
        {
            int x,y;
            scanf("%d%d",&x,&y);
            x=findset(x); y=findset(y);
            if (x==y) continue;
            int r1=find(x);
            int r2=find(y);
            if (r1!=r2) 
            {
                fa[r1]=r2;
                link(x,y);    //
                ans++;
            }
            else
            {
                makeroot(x);
                expose(y); splay(y);
                ans-=size[y]-1;
                merge(y,y); update(y);
            }
        }
        scanf("%d",&Q);
        printf("Case %d:\n",++T);
        for (int i=1;i<=Q;i++)
        {
            int x,y;
            scanf("%d%d",&x,&y);
            x=findset(x);
            y=findset(y);
            if (x==y) {
                printf("%d\n",ans);
                continue;
            }     //已经在一个连通分量中了 
            int r1=find(x),r2=find(y);
            if (r1!=r2)
            {
                fa[r1]=r2;
                link(x,y);
                ans++;
            }
            else
            {
                makeroot(x);
                expose(y); splay(y);
                ans-=size[y]-1;
                merge(y,y); update(y);
            }
            printf("%d\n",ans);
        }
        printf("\n");
    }
    return 0;
}

这道题的大众做法:
用tarjan缩点之后,图就变成了一棵树,每条树边就是图中的桥
在处理询问的时候,每次加入一条边(x,y)
显然,加入这条边后,树上x到y的路径上的每一条边就会由桥边变成非桥边
换句话说,这条路径就没有贡献了

我们在树上直接剖分(把边权变成点权),
询问的时候,我们就提出一条路径来,把ta的贡献清零(换句话说就是把路径上的边权变成0)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值