CSUST 2012 一个顶俩 (本校OJ题)(思维+树链剖分)

Description

A:一心一意

B:一个顶俩

最近QQ更新后那个成语接龙好像挺火的?但我只知道图论里一条边是一个顶俩个点的emm。

如果我给你一个n个点n-1条边的无向联通图,但是这里头有一些边是脆弱的。随时都面临崩坏的危险。

为了维持他们的连通性,善良的我又给了你m条紫水晶备用边(u,v)。我这里准备Q个问题,第i个问题为一个整数z(1zn1)表示若第z条边崩坏了,你能选出多少条备用边保证图继续保持联通。

Input

第一行三个正整数表示n,m,Q

接下来n-1行每行两个整数x,y一条边。

下面m行每行两个整数u,v表示备用边。

接下来Q行,每行一个整数z表示询问。

1n,m,Q100000。保证数据合法。

Output

Q行,每行一个整数表示答案。

Sample Input

3 2 2
1 2
1 3
2 3
1 3
1
2

Sample Output

1
2

Hint

第一个问题把第1条边(1,2)删掉,你可以选择备用边(2,3)保证连通性。

第二个问题把第2条边(2,3)删掉,你可以随意选择一条备用边都能保证连通性。

题目分析

题意:给出一个有n个结点的树,同时给出m条备用边u,v,有q次询问,每次询问删除边x后,你能选出多少种方案只加一条备用边能保证图的联通。

思路:这个实际上是一个很裸的树链剖分,更新边权的树链剖分,主要难在想到这是一个树链剖分的题目。

注意到增加一条备用边(u,v)时,这条备用边可以在原u,v两点之间任意一条边被删除后使得整个图重新连通,因此,u到v路径上所有的边都可以用这条备用边修复删除自身后的图的连通性,那么问题摆明了就是一个更新边权的树链剖分了

对于每一条备用边(u,v),我们将原图中u,v之间的每一条边的边权加一,表示这一备用边可以用于修复删除这条边后的图的连通性,最后,问边(u,v)删除时,有多少种修复方案的时候,我们输出边(u,v)的边权即可。

(博主自言自语:对于更新边权的树链剖分,我们将边权存于这条边所连接的两个结点中深度更大的结点,这样一来我们更新同一条链上的边的边权的时候,更新的区间即为 [深度小的结点的重儿子的dfs序,深度大的结点的dfs序],所以实际上记录点权和边权的熟树链剖分代码大体上只有一处不同])

代码区

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<queue>
#include<string>
#include<fstream>
#include<vector>
#include<stack>
#include <map>
#include <iomanip>

#define bug cout << "**********" << endl
#define show(x, y) cout<<"["<<x<<","<<y<<"] "
#define LOCAL = 1;
using namespace std;
typedef long long ll;
const int inf = 0x3f3f3f3f;
const ll mod = 1e9 + 7;
const int Max = 1e5 + 10;

struct Tree
{
    int deep, father, val;        //结点深度,父节点,权值
    int size, heavy;            //以此结点为根节点的树的大小。重儿子的编号
    int id, top;                //对应于线段树上的编号(dfs序),链头编号
}tree[Max];                        //原本树的结点信息

int n, m, q;
int toTree[Max],cnt;                            //记录dfs序对应的结点编号;记录dfs序,说到底,cnt最终还是等于n,在这个题目中,用处不大

//以下为线段树操作
struct Node
{
    int l, r;
    int sum, lazy;
}node[Max << 2];

void build(int l, int r, int num)
{
    node[num].l = l;
    node[num].r = r;
    node[num].lazy = 0;
    if (l == r)
    {
        node[num].sum = tree[toTree[l]].val;          //初始化边权
        return;
    }
    int mid = (l + r) >> 1;
    build(l, mid, num << 1);
    build(mid + 1, r, num << 1 | 1);
    node[num].sum = node[num << 1].sum + node[num << 1 | 1].sum;    //sum记录区间和
}

void push_down(int num)
{
    if (node[num].lazy != 0)
    {
        int &lazy = node[num].lazy;

        node[num << 1].sum +=  lazy;

        node[num << 1 | 1].sum += lazy;

        node[num << 1].lazy += lazy;
        node[num << 1 | 1].lazy += lazy;

        lazy = 0;
    }
}

void upData(int l, int r, int val, int num)    //更新区间,这个是模板的函数,其实这个题目只要单点更新即可,但对时间影响不大
{
    if (l <= node[num].l && node[num].r <= r)
    {
        node[num].sum += val;
        node[num].lazy += val;
        return;
    }
    push_down(num);
    int mid = (node[num].l + node[num].r) >> 1;

    if (l <= mid)
        upData(l, r, val, num << 1);
    if (r > mid)
        upData(l, r, val, num << 1 | 1);
    node[num].sum = node[num << 1].sum + node[num << 1 | 1].sum;
}

int query(int l, int r, int num)    //查询区间和,模板查询函数,这里我们只用于单点查询
{
    if (l <= node[num].l && node[num].r <= r)
        return node[num].sum;

    push_down(num);

    int mid = (node[num].l + node[num].r) >> 1;
    int ans = 0;
    if (l <= mid)
        ans += query(l, r, num << 1);
    if (r > mid)
        ans += query(l, r, num << 1 | 1);

    return ans;
}

//以下为树链剖分的部分
struct Edge
{
    int to, next;
}edge[Max << 1];            //

int head[Max], tot;

void init()
{
    memset(head, -1, sizeof(head));tot = 0;
}

void add(int u, int v)
{
    edge[tot].to = v;
    edge[tot].next = head[u];
    head[u] = tot++;
}

void dfs1(int now, int fa, int deep)
{
    tree[now].val = 0;              //初始化为0,这个是模板内容,可能边有初始值,不过这个题没有就对了
    tree[now].father = fa;
    tree[now].deep = deep;
    tree[now].size = 1;
    tree[now].heavy = -1;            //初始化为无重边(也就是看作叶子节点了)
    int max_son = -1;
    for (int i = head[now]; i != -1; i = edge[i].next)
    {
        int v = edge[i].to;
        if (v == fa) continue;

        dfs1(v, now, deep + 1);

        tree[now].size += tree[v].size;
        if (tree[v].size > max_son)    //更新重边
            tree[now].heavy = v, max_son = tree[v].size;
    }
}



void dfs2(int now, int top)            //top为当前链的链头
{
    tree[now].top = top;
    tree[now].id = ++cnt;
    if (tree[now].heavy == -1) return;                        //叶子结点
    dfs2(tree[now].heavy, top);                                //先处理重链
    for (int i = head[now]; i != -1; i = edge[i].next)        //处理轻链
    {
        int v = edge[i].to;
        if (v == tree[now].father || v == tree[now].heavy) continue;
        dfs2(v, v);                                            //此时v为一条轻链的链头(画一下就知道为什么了)
    }
}

void upData2(int s, int e, int val)                            //更新s->e上的结点,将dfs不连续的路径分为数个dfs序连续的路径,对这个数个dfs序连续的路径进行操作
{
    while (tree[s].top != tree[e].top)                        //不断地将深度大的上移,使得最后两个点都在同一个链上
    {
        if (tree[tree[s].top].deep < tree[tree[e].top].deep) swap(s, e);
        upData(tree[tree[s].top].id, tree[s].id, val, 1);    //同时更新分出来的dfs序连续的路径
        s = tree[tree[s].top].father;
    }
    if (tree[s].deep > tree[e].deep) swap(s, e);
    upData(tree[tree[s].heavy].id, tree[e].id, val, 1);
    //这个地方就是更新边权和点权的区别了,由于我们将边权的值存于这条边深度更大的端点处
    //因此我们更新点s,e之间的边的边权的时候,更新的边为e表示的这条边和s的重儿子表示的那条边之间的边
    //对应的dfs序就是 tree[tree[s].heavy].id ~ tree[e].id, val 了
}

int query2(int s, int e)    //思路和upData2的一样:将dfs不连续的路径分为数个dfs序连续的路径,对这个数个dfs序连续的路径进行操作
{
    int sum = 0;
    while (tree[s].top != tree[e].top)
    {
        if (tree[tree[s].top].deep < tree[tree[e].top].deep) swap(s, e);
        sum += query(tree[tree[s].top].id, tree[s].id, 1);
        s = tree[tree[s].top].father;
    }
    if (tree[s].deep > tree[e].deep) swap(s, e);
    sum += query(tree[tree[s].heavy].id, tree[e].id, 1);
    return sum;
}

int u[Max],v[Max];          //记录边

int main()
{
#ifdef LOCAL
    //freopen("input.txt", "r", stdin);
    //freopen("output.txt", "w", stdout);
#endif
    while (scanf("%d%d%d", &n, &m, &q) != EOF)
    {
        init();
        for (int i = 1;i < n;i++)
        {
            scanf("%d%d", u+i, v+i);        //因为后面根据边的编号来查询,所以用数组记录一下
            add(u[i], v[i]);add(v[i], u[i]);
        }
        dfs1(1, -1, 0);        //先预处理出每个结点的基本信息,比如每个根结点对应的重边,以为构建链做准备
        dfs2(1, 1);                //根据之前已经求出的每个结点的信息,获得链上结点dfs连续的dfs序
        build(1, n, 1);            //根据之前得到的dfs序构建线段树

        for(int i = 1, x, y; i <= m ;i ++)
        {
            scanf("%d%d",&x,&y);
            upData2(x,y,1);             //给区间每条边的边权加一
        }
        for(int i =1,x ;i <= q; i ++)
        {
            scanf("%d",&x);
            printf("%d\n",query2(u[x],v[x]));
        }
    }
    return 0;
}
View Code

猜你喜欢

转载自www.cnblogs.com/winter-bamboo/p/11366731.html