CF348C Subset Sums(分块)

给定一个n个数的序列a,m个下标集合,记 Sk={Sk,i} 。两种操作:
1. ? k 求集合k的和,即 i=1|Sk|aSk,i
2. + k w 给集合k的所有下标代表的数加w。
3. 记 n=i=1m|Si|
我们把集合大小大于 n 的集合叫做重集合,其余的叫做轻集合。显然重集合的个数小于 n 的。我们处理出一个num[i][k]数组,表示第i个集合和第k个重集合的交集大小。我们可以 O(nn) 预处理出这个数组。(对于每一个重集合,记一个O(n)的bool数组,表示一个数是否出现在这个重集合中,我们就可以 O(1) 得到一个数有没有在一个重集合出现过了。)对每一个重集合,我们记sum和add标记。
对于操作1:
1)如果k是重集合,直接修改add[k]。
2)如果k是轻集合,暴力修改集合内每一个元素,然后遍历所有重集合,把sum+=交集个数*val
对于操作2:
1)如果k是重集合,答案就是sum[k],再遍历所有重集合,加上add*交集个数即可。
2)如果k是轻集合,答案就是先暴力统计轻集合内的数的和,然后再遍历所有重集合,加上add*交集个数即可。
复杂度 O(nn)

#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
#include <vector>
using namespace std;
#define ll long long
#define inf 0x3f3f3f3f
#define N 100010
inline int read(){
    int x=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar();
    return x*f;
}
int n,m,mm,nn,id[N],sz[350],num[N][350],tot=0;
ll add[350],sum[350],a[N];bool exist[350][N];
vector<int>S[N];
inline void doadd(int k,int val){
    for(int i=0;i<S[k].size();++i){
        int x=S[k][i];a[x]+=val;
    }for(int i=1;i<=tot;++i) sum[i]+=(ll)num[k][i]*val;
}
inline ll query(int k){
    ll res=0;
    if(id[k]) res=sum[id[k]];
    else for(int i=0;i<S[k].size();++i) res+=a[S[k][i]];
    for(int i=1;i<=tot;++i) res+=num[k][i]*add[i];
    return res;
}
int main(){
//  freopen("a.in","r",stdin);
    n=read();m=read();mm=read();
    for(int i=1;i<=n;++i) a[i]=read();
    for(int i=1;i<=m;++i){
        int k=read();for(int j=1;j<=k;++j) S[i].push_back(read());nn+=k;
    }nn=sqrt(nn);
    for(int i=1;i<=m;++i){
        if(S[i].size()<=nn) continue;id[i]=++tot;
        for(int j=0;j<S[i].size();++j) exist[tot][S[i][j]]=1,sum[tot]+=a[S[i][j]];
    }
    for(int i=1;i<=m;++i)
        for(int j=1;j<=tot;++j)
            for(int k=0;k<S[i].size();++k)
                num[i][j]+=exist[j][S[i][k]];
    while(mm--){
        char op[5];scanf("%s",op+1);int k=read();
        if(op[1]=='?'){
            printf("%lld\n",query(k));continue;
        }int val=read();
        if(id[k]) add[id[k]]+=val;
        else doadd(k,val);
    }return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值