分块法 hdu4858 项目管理

一看到这题,首先会觉得非常像单点更新的线段树,但是却不怎么好操作

然后应该往分块的方向去想


因为只有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;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值