Codeforces 349E - Subset Sums

n(n105) 个数,有 m 个下标的集合(集合的大小之和小于105)

维护两个操作

询问操作: ? k 将下标在第k个集合内数求和输出

更新操作: + k x 将下标在第k个集合内的数都 +x


因为集合的大小之和小于 105 ,不妨记集合的大小之和为S考虑将集合分类。将大小超过 S 的集合称之为大集合,大小小于 S 的集合称之为小集合。显然大集合的个数小于 S

预处理每一个集合和每一个大集合的交集的大小。

考虑询问的时候,最后的答案是 (+)+

对于每个大集合维护这个大集合更新的 x 的和,预处理这个大集合中元素初始的和作为大集合的前两项的贡献。

先考虑更新操作

如果更新大集合,我们只维护这个集合更新的x的和。 O(1)

如果更新小集合,我们遍历一遍小集合中的元素去更新,并且遍历所有的大集合更新该大集合维护的前两项的贡献。 O(2S)=O(S)

再考虑询问操作

如果询问小集合,可以遍历下标在小集合内的元素的和统计前两项。 O(S)

如果询问大集合,我们每次更新都维护了前两项。 O(1)

对于所有的集合,大集合更新的贡献只需要遍历一遍大集合,使用之前维护的该集合和每一个大集合的交集的大小可以 O(S) 的求解

这样我们就在 O(m×S) 的时间复杂度内做出了本题


说了这么多其实本质就是分大小集合的贡献去暴力而已

#include<bits/stdc++.h>
using namespace std;

const int maxn = 100010;
#define LL long long 

LL arr[maxn], stv[maxn];
vector<int> rev[maxn],seter[maxn],big;
bitset<maxn> hav[400];
LL add[maxn];
int ctb[maxn][400], id[maxn];

int main(){
    int n,m,q;
    scanf("%d %d %d",&n,&m,&q);
    for(int i = 1;i<=n;i++) scanf("%I64d",&arr[i]);

    int len, allsiz = 0;
    for(int i = 1;i<=m;i++){
        scanf("%d",&len);
        allsiz += len;
        id[i] = -1;
        add[i] = stv[i] = 0;
        int x;
        while(len--){
            scanf("%d",&x);
            seter[i].push_back(x);
            stv[i] += arr[x];
        }
    }
    int unit = (int)sqrt(allsiz * 1.0) + 1;
    for(int i = 1;i<=m;i++){
        if(seter[i].size() >= unit){
            id[i] = big.size();
            big.push_back(i);
            for(auto x : seter[i]){
                hav[id[i]][x] = 1;
                rev[x].push_back(id[i]);
            }
        }
    }
    memset(ctb,0,sizeof(ctb));
    for(int i = 1;i<=m;i++){
        for(auto x : seter[i]){
            for(auto loca : rev[x]){
                ctb[i][loca]++;
            }
        }
    }
    char ord[3];
    int k;
    LL v;
    while(q--){
        scanf("%s %d",ord,&k);
        if(*ord == '?'){
            LL ans = 0;
            if(id[k] == -1) for(auto x : seter[k]) ans += arr[x];
            else ans = stv[k];
            for(auto x : big){
                ans += add[x] * ctb[k][id[x]];
            }
            printf("%I64d\n",ans);
        }
        else{
            scanf("%I64d",&v);
            if(id[k] != -1) add[k] += v;
            else{
                for(auto x : seter[k]) arr[x] += v;
                for(auto x : big ){
                    stv[x] += v * ctb[k][id[x]];
                }
            }
        }
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值