hdu4467 Graph(构造法求解)

Graph

Time Limit: 10000/5000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)
Total Submission(s): 642    Accepted Submission(s): 94


Problem Description
P. T. Tigris is a student currently studying graph theory. One day, when he was studying hard, GS appeared around the corner shyly and came up with a problem:
Given a graph with n nodes and m undirected weighted edges, every node having one of two colors, namely black (denoted as 0) and white (denoted as 1), you’re to maintain q operations of either kind:
* Change x: Change the color of x th node. A black node should be changed into white one and vice versa.
* Asksum A B: Find the sum of weight of those edges whose two end points are in color A and B respectively. A and B can be either 0 or 1.
P. T. Tigris doesn’t know how to solve this problem, so he turns to you for help.
 

Input
There are several test cases.
For each test case, the first line contains two integers, n and m (1 ≤ n,m ≤ 10 5), where n is the number of nodes and m is the number of edges.
The second line consists of n integers, the i th of which represents the color of the i th node: 0 for black and 1 for white.
The following m lines represent edges. Each line has three integer u, v and w, indicating there is an edge of weight w (1 ≤ w ≤ 2 31 - 1) between u and v (u != v).
The next line contains only one integer q (1 ≤ q ≤ 10 5), the number of operations.
Each of the following q lines describes an operation mentioned before.
Input is terminated by EOF.
 

Output
For each test case, output several lines.
The first line contains “Case X:”, where X is the test case number (starting from 1).
And then, for each “Asksum” query, output one line containing the desired answer.
 

Sample Input
   
   
   
   
4 3 0 0 0 0 1 2 1 2 3 2 3 4 3 4 Asksum 0 0 Change 2 Asksum 0 0 Asksum 0 1 4 3 0 1 0 0 1 2 1 2 3 2 3 4 3 4 Asksum 0 0 Change 3 Asksum 0 0 Asksum 0 1
 

Sample Output
   
   
   
   
Case 1: 6 3 3 Case 2: 3 0 4
 

Source
2012 Asia Chengdu Regional Contest

题目大意:给一张无向图,有n个点,m条边,每个点2个状态:0和1,每条边有一个权值w,现在给q个操作。有2种操作,change x表示改变x点的状态。asksum a,b表示查询所有端点为a 和 b的边的权值之和。

题目分析:这题很容易想到暴力枚举所有边统计求和的方法。比赛的时候实际也是这么做的。这么做的复杂度O(q*m),显然是不能承受的。比赛的时候一直以为是某种高深的数据结构,结果看了题解才发现是构造。想法其实也不难想,只是不敢想。

因为暴力的话,时间主要消耗在边的遍历上,那么如何减少遍历边的时间呢。很容易想到对度比较大的节点特殊处理,但具体如何处理,实在是想不出来,太弱了啊。。。

重新分析一下此题:由于每个点只有0 1两个状态,那么答案只有3种情况,用一个数组维护即可。即sum[0]统计边的两端都是0的权值和,sum[1]统计边的两端为1和0的权值总和,sum[2]统计边的两端都是1的权值和。

那么只要维护好这个数组,那么对于查询操作,可以O(1)的时间输出。那么关键就在change操作的维护了。

仔细分析可知,每次change操作只改变一个点的状态,那么与这个点有边相连的点的状态是不会发生改变的。我们在每个点上维护2个值:ans[0]维护与当前顶点相连且状态为0的权值和,ans[1]维护与当前顶点相连且状态为1的权值和。每次改变当前顶点的状态后,其他的点状态是不变的,那么这些权值和也是不变的。而每次改变的是与当前顶点相连的点的ans值,因为当前顶点的状态发生了改变。而总的sum数组也要随着一个点的状态的改变而改变。而这个改变其实很简单:

设要改变状态的点为x,先要从sum[color[x] + 0]里面减去x.ans[0],因为x改变状态之前,x.ans[0]被加进了sum[color[x] + 0]里面(想一想,为什么),那么现在x改变了状态,所以要先把x.ans[0]减掉,转而加到sum[color[x]^1+0]里面,即加到转换状态后应该加的地方。sum[color[x] + 1]同理。

说了半天还没有说到最关键的地方,如何降低复杂度。刚才说了统计每个点的度数,那么我们把所有的点分成两类,第一类度数小于sqrt(m)(为什么是sqrt(m),这个其实不一定非要这么多,这只是一个大概的分类,因为可以证明,当边为m的时候,无向图中点的度数大于sqrt(m)的点不超过2*sqrt(m)个)。这样的话,我们可以在建邻接表的时候,对于普通点,将所有与之邻接的点都加入邻接表,直接暴力更新即可,对于超级点,我们可以忽略与之相邻的普通点,只需要将与之相邻的超级点加入邻接表即可。因为我们change的时候对每个普通点是暴力遍历的,每次重新统计即可,但是对于超级点,我们只遍历与之相邻的超级点,维护相应的ans,这样大大节省了时间。

也许描述很抽象,最后一点也是刚走路的时候才想同。给大家个例子,大家不妨在纸上划划,就会明白的,为什么可以无视与超级点直接相连的普通点。

例子:一个超级点,度数为n,与n个普通点相连。这样建邻接表的时候,超级点是没有相邻点的,随便更改状态,跑出来的结果是不会错的,对于超级点,ans[0],ans[1]是不会变的,对于普通点,不管超级点怎么变,反正每次都要重新统计,不会出错的。

这样一来,原来的复杂度就变成了O(q*sqrt(m)),是可以接受的。

还有2点要注意的:重边要合并!!!!边权值要用__int64!!!此处WA数次!!!

详情请见代码:

#include <iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N = 100005;
typedef __int64 ll;
ll sum[3];
int cl[N],de[N];
const int lim = 350;
/*
330  2140MS	8456K
300  2156MS	8456K
*/
struct nd
{
    int a,b;
    ll val;//!!!!
}edge[N];

struct spn
{
    bool flag;
    ll ans[2];
}supernode[N];
int cmp(struct nd ta,struct nd tb)
{
    if(ta.a != tb.a)
        return ta.a < tb.a;
    else
        return ta.b < tb.b;
}
struct adj
{
    int to,next;
    ll val;//!!!!
}g[N + N];
int num;
int head[N];
int m,n,q;

void build(int s,int e,ll v)
{
    g[num].to = e;
    g[num].val = v;
    g[num].next = head[s];
    head[s] = num;
    num ++;
}

void change(int x)
{
    int i;
    if(supernode[x].flag)//super node
    {
        for(i = head[x];i != -1;i = g[i].next)
        {
            supernode[g[i].to].ans[cl[x]] -= g[i].val;
            supernode[g[i].to].ans[cl[x]^1] += g[i].val;
        }
    }
    else//非super node
    {
        supernode[x].ans[0] = supernode[x].ans[1] = 0;//每次要重新统计!!
        for(i = head[x];i != -1;i = g[i].next)
        {
            supernode[x].ans[cl[g[i].to]] += g[i].val;
            if(supernode[g[i].to].flag)
            {
                supernode[g[i].to].ans[cl[x]] -= g[i].val;
                supernode[g[i].to].ans[cl[x]^1] += g[i].val;
            }
        }
    }
    /*其实上面的if else完全去掉,只加上这一段也是能AC的,但是不严谨,因为
    为了节省时间,我们在处理超级点的时候没有考虑与之相连的普通点,那么我们在
    暴力遍历普通点的时候,是要重新统计的,因为超级点状态改变了,与之相连的
    普通点也应该改变的,我们没有变,为了省时间,但如果我们有机会遍历该普通点
    应该想办法弥补的,弥补的办法就是重新统计!!
    for(i = head[x];i != -1;i = g[i].next)
        {
            supernode[g[i].to].ans[cl[x]] -= g[i].val;
            supernode[g[i].to].ans[cl[x]^1] += g[i].val;
        }
    */
    sum[cl[x]] -= supernode[x].ans[0];
    sum[cl[x] + 1] -= supernode[x].ans[1];
    cl[x] ^= 1;
    sum[cl[x]] += supernode[x].ans[0];
    sum[cl[x] + 1] += supernode[x].ans[1];
}

int main()
{
    int i;
    int a,b;
    int cas = 0;
    while(scanf("%d%d",&n,&m) != EOF)
    {
        memset(sum,0,sizeof(sum));
        for(i = 1;i <= n;i ++)
        {
            scanf("%d",&cl[i]);
            head[i] = -1;
            de[i] = 0;
            supernode[i].flag = false;
            memset(supernode[i].ans,0,sizeof(supernode[i].ans));
        }
        for(i = 1;i <= m;i ++)
        {
            scanf("%d%d%I64d",&edge[i].a,&edge[i].b,&edge[i].val);
            if(edge[i].a > edge[i].b)
                edge[i].a ^= edge[i].b ^= edge[i].a ^= edge[i].b;
        }
        sort(edge + 1,edge + m + 1,cmp);
        int t = 1;//离散化  去重边
        for(i = 2;i <= m;i ++)
        {
            if(edge[i].a == edge[t].a && edge[i].b == edge[t].b)
                edge[t].val += edge[i].val;
            else
                edge[++t] = edge[i];
        }
        m = t;
        for(i = 1;i <= m;i ++)//统计度数   找出超级点
        {
            de[edge[i].a] ++;
            de[edge[i].b] ++;
            sum[cl[edge[i].a] + cl[edge[i].b]] += edge[i].val;
        }
        for(i = 1;i <= n;i ++)
        {
            if(de[i] >= lim)//super node
                supernode[i].flag = true;
            if(de[i] >= lim)
                supernode[i].flag = true;
        }
        num = 0;
        for(i = 1;i <= m;i ++)//普通点建邻接表,超级点维护ans
        {
            int ta = edge[i].a;
            int tb = edge[i].b;
            supernode[ta].ans[cl[tb]] += edge[i].val;
            supernode[tb].ans[cl[ta]] += edge[i].val;
            if(supernode[ta].flag && supernode[tb].flag)//
            {
                build(ta,tb,edge[i].val);
                build(tb,ta,edge[i].val);
            }
            if(supernode[ta].flag == false)
                build(ta,tb,edge[i].val);
            if(supernode[tb].flag == false)
                build(tb,ta,edge[i].val);
        }
        scanf("%d",&q);
        printf("Case %d:\n",++cas);
        char op[20];
        while(q --)
        {
            scanf("%s",op);
            if(op[0] == 'A')
            {
                scanf("%d%d",&a,&b);
                printf("%I64d\n",sum[a + b]);
            }
            else
            {
                scanf("%d",&a);
                change(a);
            }
        }
    }
    return 0;
}
//1625MS	8456K
//1578MS	8456K




你可能感兴趣的:(构造)