Graph
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.
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.
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.
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
Case 1: 6 3 3 Case 2: 3 0 4
重新分析一下此题:由于每个点只有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;
}