hdu 4467 Graph ( 神奇的思想 降低复杂度 )

Graph

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


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

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

思路:这题很容易想到直接暴力解决 但数据量过大 复杂度为O(q*m)  肯定是得TLE的  q不能优化了  那么只能优化m了   暴力的话每次都要对一个点的每条相邻边扫描一遍  太耗时了  于是要想一想能不能每次不遍历所有相邻边呢?

重新分析一下此题:由于每个点只有0 1两个状态,那么答案只有3种情况,用一个数组维护即可。即ans[0]统计边的两端都是0的权值和,ans[1]统计边的两端为1和0的权值总和,ans[2]统计边的两端都是1的权值和。这样只要维护好这个数组,那么对于查询操作,可以O(1)的时间输出。那么关键就在change操作的维护了。

先说一个想法:如果记录每个点与之相连的点颜色分别为0和1 的权值之和,以w0和w1来表示。那么,该点改变时,例如:当当前点由0->1时,ans[0]-w[0],ans[1]+w[1],ans[2]-w[1]+w[0];  这样就是O(1)复杂度了,而且当前点得出结果也是正确的。但是,当处理其他点时,其他点的w0和w1 没有给到更新,这显然是不行的,还需要再做些文章。

说到这里,先说个结论,对于点x,与x相连并且度数大于x的度数的点不会超过 (2m)^(1/2),这个证明很好证,可以自己去推下。如果把与x相连的点分为两类,一类的度数大于他(该类不超过(2m)^(1/2)),另一类度数小于他。对度数小于他的整体操作,度数大于他的逐边操作,那么就能降低复杂度了。接下来,我采取的方法是,用 w[x][0] (与x相连的边的颜色为0)和 w[x][1] (与x相连的边的颜色为1)表示度数比x节点小的权值之和,至于度数比它大,则逐边进行处理,处理时更新w[y][0]、w[y][1] (因为相对x而言,y是度数大的点,所以可以在对度数比它大的点逐边枚举时也可以更新w的信息,一举三得,因为还降低了复杂度)。这样复杂度就可以降到 q*(m)^1/2 了。

最后,说下这种思想为什么是正确的。对于点x,度数大于它的是逐边处理的,ans可以直接更新;度数小于它的,信息则没有更新,那么,当处理到那些度数小的点时,也是按同样的方法在处理,度数大于的点逐边处理,这不就是把以前没有更新的过程给完成了吗?这样,就不会出现需要使用改点时,信息没得到完善。总而言之,就是利用度数将点分类,然后一部分整体处理,一部分枚举。


感想:这种降低复杂度的思想真的太妙了,值得研究!啥时候能自己想出来就牛逼了。 嘻嘻

哦 对了 这题还要考虑重边的情况,真的好没意思,没办法,现场赛的题目就是各种位置都要坑你一下才行,我对那些哪些卡在重边上的队真的表示惋惜。


代码:

// Exe.Time  1750MS   Exe.Memory   6104K   hdu暂居第五
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#define maxn 100005
using namespace std;

int n,m,cxx;
int color[maxn];
int deg[maxn];
long long w[maxn][2],ww[2];
long long ans[3];
char s[50];
struct Node
{
    int l,r;
    long long val;
} edge[maxn];
int point[maxn];
struct node      // 图的邻接表
{
    int r,next;
    long long val;
}side[maxn];

bool cmp(const Node &x1,const Node &x2)
{
    if(x1.l<x2.l) return true ;
    else if(x1.l==x2.l) return x1.r<x2.r;
    return false ;
}
void addedge(int u,int v,long long cost)
{
    cxx++;
    side[cxx].r=v;
    side[cxx].val=cost;
    side[cxx].next=point[u];
    point[u]=cxx;
}
int main()
{
    int i,j,l,r,q,t=0,tmp,cnt,lc,nc;
    long long temp;
    while(~scanf("%d%d",&n,&m))
    {
        t++;
        for(i=1; i<=n; i++)
        {
            scanf("%d",&color[i]);
        }
        ans[0]=ans[1]=ans[2]=0;
        for(i=1; i<=m; i++)
        {
            scanf("%d%d%I64d",&edge[i].l,&edge[i].r,&edge[i].val);
            ans[color[edge[i].l]+color[edge[i].r]]+=edge[i].val;        // 更新初始 ans
            if(edge[i].l>edge[i].r)       // 保证 l<r 方便下面找重边
            {
                temp=edge[i].l;
                edge[i].l=edge[i].r;
                edge[i].r=temp;
            }
        }
        sort(edge+1,edge+m+1,cmp);
        cnt=1;
        for(i=2; i<=m; i++)    // 重边合并
        {
            if(edge[i].l==edge[cnt].l&&edge[i].r==edge[cnt].r)
            {
                edge[cnt].val+=edge[i].val;
            }
            else
            {
                cnt++;
                if(i!=cnt)
                {
                    edge[cnt].l=edge[i].l;
                    edge[cnt].r=edge[i].r;
                    edge[cnt].val=edge[i].val;
                }
            }
        }
        memset(deg,0,sizeof(deg));
        for(i=1;i<=cnt;i++)          // 记录各点的度数
        {
            deg[edge[i].l]++;
            deg[edge[i].r]++;
        }
        cxx=0;
        memset(point,0,sizeof(point));
        memset(w,0,sizeof(w));
        for(i=1;i<=cnt;i++)      // 对度数比他大的点 建立邻接表
        {
            l=edge[i].l;
            r=edge[i].r;
            if(deg[l]<=deg[r])
            {
                w[r][color[l]]+=edge[i].val;   // 度数小的点没有进邻接表 则加入W[]数组内
                addedge(l,r,edge[i].val);
            }
            else
            {
                w[l][color[r]]+=edge[i].val;
                addedge(r,l,edge[i].val);
            }
        }
        printf("Case %d:\n",t);
        scanf("%d",&q);
        while(q--)
        {
            scanf("%s",s);
            if(s[0]=='A')
            {
                scanf("%d%d",&l,&r);
                printf("%I64d\n",ans[l+r]);
            }
            else
            {
                scanf("%d",&r);
                lc=color[r];
                color[r]=!lc;
                ww[0]=ww[1]=0;
                for(i=point[r]; i ;i=side[i].next)      // 度数大的点进行处理
                {
                    ww[color[side[i].r]]+=side[i].val;
                    if(lc)               // 对度数大的而言 r就是度数小的 需更新信息
                    {
                        w[side[i].r][0]+=side[i].val;
                        w[side[i].r][1]-=side[i].val;
                    }
                    else
                    {
                        w[side[i].r][0]-=side[i].val;
                        w[side[i].r][1]+=side[i].val;
                    }
                }
                if(lc)          // 一起更新ans
                {
                    ans[0]=ans[0]+ww[0]+w[r][0];
                    ans[1]=ans[1]-ww[0]+ww[1]-w[r][0]+w[r][1];
                    ans[2]=ans[2]-ww[1]-w[r][1];
                }
                else
                {
                    ans[0]=ans[0]-ww[0]-w[r][0];
                    ans[1]=ans[1]+ww[0]-ww[1]+w[r][0]-w[r][1];
                    ans[2]=ans[2]+ww[1]+w[r][1];
                }
            }
        }
    }
    return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值