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
题目大意:给一张无向图,有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