一看到这题,首先会觉得非常像单点更新的线段树,但是却不怎么好操作
然后应该往分块的方向去想
因为只有m条边,所以所有点的度总和是2m,那么设度数>=sqrt(m)的点叫做重点,反之则是轻点
那么重点的个数<=2m/sqrt(m)=2sqrt(m)
设A[i]表示该点的值,sum[i]表示该点周围相连的点的A[i]之和
构造图的时候也有技巧,对于一条边(u,v)
如果u是轻点,直接添加(u,v)这条边 //因为边数不会超过sqrt(m)条
如果u是重点,那么v也是重点时候,才添加(u,v)这条边 //因为重点个数小于2sqrt(m),所以这里添加的边也不算特别多
更新时
对于每个点,先更新自己,A[u]+=d,遍历其所有的边,使sum[v]+=d
实际上本来如果v是轻点,是可以不用操作的,但是也无所谓啦,反正只有u也是轻点的时候,v才可能是轻点,但是u的边数已经很小了,加不加判断都无所谓
操作了也并不会影响答案,因为如果i是轻点,sum[i]是没用的,等下继续讲
查询时
如果x是重点,由之前的构图和更新能知道,对于任意v是重点边,都是添加在图里面的,所以sum[x]就是答案
如果x是轻点,实际上sum[x]是没意义的,但是与x连接的边数<=sqrt(m),所以直接枚举所有周围的点,积累周围点的A值即可
#include<cstdio> #include<cstring> #include<cstdlib> #include<cmath> #include<vector> #include<algorithm> using namespace std; typedef long long LL; const int mod=1e9+7; const int MX=100100+5; const int INF=0x3f3f3f3f; struct Edge{ int u,v; }E[MX]; int n,m,unit,Q; int P[MX],sum[MX],A[MX]; vector<int>G[MX]; void update(int x,int d){ A[x]+=d; for(int i=0;i<G[x].size();i++){ int nxt=G[x][i]; sum[nxt]+=d; } } int query(int x){ if(P[x]<unit){ sum[x]=0; for(int i=0;i<G[x].size();i++){ int nxt=G[x][i]; sum[x]+=A[nxt]; } return sum[x]; }else{ return sum[x]; } } int main(){ int T; scanf("%d",&T); while(T--){ scanf("%d%d",&n,&m); memset(A,0,sizeof(A)); memset(P,0,sizeof(P)); memset(sum,0,sizeof(sum)); for(int i=1;i<=n;i++) G[i].clear(); for(int i=1;i<=m;i++){ scanf("%d%d",&E[i].u,&E[i].v); P[E[i].u]++; P[E[i].v]++; } unit=sqrt(m+0.5); for(int i=1;i<=m;i++){ int u=E[i].u,v=E[i].v; if(P[u]<unit) G[u].push_back(v); else if(P[v]>=unit) G[u].push_back(v); if(P[v]<unit) G[v].push_back(u); else if(P[u]>=unit) G[v].push_back(u); } scanf("%d",&Q); while(Q--){ int cmd,a,b; scanf("%d",&cmd); if(cmd==0){ scanf("%d%d",&a,&b); update(a,b); } else{ scanf("%d",&a); printf("%d\n",query(a)); } } } return 0; }