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

(点击这里查看原题,不保证可以进去....外网可能比较卡)

Description

A:一心一意

B:一个顶俩

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

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

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

Input

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

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

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

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

1≤n,m,Q≤100000。保证数据合法。

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;
}

原文地址:https://www.cnblogs.com/winter-bamboo/p/11366731.html

时间: 2024-08-30 07:25:15

CSUST 2012 一个顶俩 (本校OJ题)(思维+树链剖分)的相关文章

jzoj5987. 【WC2019模拟2019.1.4】仙人掌毒题 (树链剖分+概率期望+容斥)

题面 题解 又一道全场切的题目我连题目都没看懂--细节真多-- 先考虑怎么维护仙人掌.在线可以用LCT,或者像我代码里先离线,并按时间求出一棵最小生成树(或者一个森林),然后树链剖分.如果一条边不是生成树上的边,它肯定会和树上\(u,v\)这条路径构成一个环,然后对于每条树边记录一下这条树边被覆盖过没有.如果\(u,v\)路径上有任何一条树边被覆盖过,那么就说明路径上有一条边已经在一个简单环中,这条非树边就不能加.否则就加上这条边并让这条路径上所有树边的覆盖次数加一 然后考虑期望连通块个数.首先

bzoj1036 [ZJOI2008]树的统计Count 树链剖分模板题

[ZJOI2008]树的统计Count Description 一棵树上有n个节点,编号分别为1到n,每个节点都有一个权值w.我们将以下面的形式来要求你对这棵树完成 一些操作: I. CHANGE u t : 把结点u的权值改为t II. QMAX u v: 询问从点u到点v的路径上的节点的最大权值 I II. QSUM u v: 询问从点u到点v的路径上的节点的权值和 注意:从点u到点v的路径上的节点包括u和v本身 Input 输入的第一行为一个整数n,表示节点的个数.接下来n – 1行,每行

【bzoj4811】[Ynoi2017]由乃的OJ 树链剖分+线段树区间合并

题目描述 由乃正在做她的OJ.现在她在处理OJ上的用户排名问题.OJ上注册了n个用户,编号为1-",一开始他们按照编号 排名.由乃会按照心情对这些用户做以下四种操作,修改用户的排名和编号:然而由乃心情非常不好,因为Deus天 天问她题...因为Deus天天问由乃OI题,所以由乃去学习了一下OI,由于由乃智商挺高,所以OI学的特别熟练她 在RBOI2016中以第一名的成绩进入省队,参加了NOI2016获得了金牌保送 Deus:这个题怎么做呀? yuno:这个不是NOI2014的水题吗... Deu

HDU 2966 Aragorn&#39;s Story 树链剖分第一题 基础题

Problem Description Our protagonist is the handsome human prince Aragorn comes from The Lord of the Rings. One day Aragorn finds a lot of enemies who want to invade his kingdom. As Aragorn knows, the enemy has N camps out of his kingdom and M edges c

BZOJ 2243 染色(树链剖分好题)

2243: [SDOI2011]染色 Time Limit: 20 Sec  Memory Limit: 512 MB Submit: 7971  Solved: 2990 [Submit][Status][Discuss] Description 给定一棵有n个节点的无根树和m个操作,操作有2类: 1.将节点a到节点b路径上所有点都染成颜色c: 2.询问节点a到节点b路径上的颜色段数量(连续相同颜色被认为是同一段),如“112221”由3段组成:“11”.“222”和“1”. 请你写一个程序依

HDU5052 Yaoge’s maximum profit(树链剖分)点权更新,经典题

Yaoge's maximum profit Time Limit: 10000/5000 MS (Java/Others)    Memory Limit: 65536/65536 K (Java/Others) Total Submission(s): 662    Accepted Submission(s): 182 Problem Description Yaoge likes to eat chicken chops late at night. Yaoge has eaten to

hdu 5029 Relief grain(树链剖分好题)

Relief grain Time Limit: 10000/5000 MS (Java/Others)    Memory Limit: 100000/100000 K (Java/Others) Total Submission(s): 295    Accepted Submission(s): 66 Problem Description The soil is cracking up because of the drought and the rabbit kingdom is fa

Bzoj1036 树链剖分基础题

树链剖分的基础题 因为复习到了这个部分突然发现竟然没有题解所以现在补一个.. 一些基础的东西... 重儿子:siz[u]为v的子节点中siz值最大的,那么u就是v的重儿子. 轻儿子:v的其它子节点. 重边:点v与其重儿子的连边. 轻边:点v与其轻儿子的连边. 重链:由重边连成的路径. 轻链:轻边. 然后简单的用两次dfs计算出每个节点的father,deep,size, son,w,top 其他简单的就不说了 w表示的是当前节点与其付清节点的连边在线段树中的位置 top表示的是当前节点所在的链的

light oj 1348 树链剖分(单点更新区间求值)

http://lightoj.com/volume_showproblem.php?problem=1348 Finally the Great Magical Lamp was in Aladdin's hand. Now he wanted to return home. But he didn't want to take any help from the Genie because he thought that it might be another adventure for hi