CodeForces 348C Subset Sums nsqrtn的姿势。。

题意:

给n个数和m个集合, 一个集合就是n个数的下标。

有q个操作, 每个操作就是给一个集合里面代表的数加上v, 或者询问一个集合里面的数的和。


解法:

ORZ题解。。。

设B = sqrtn

先给集合分成两种, 一种是大小大于B的, 称为重集合, 小于的称为轻集合。

显然重集合的个数不会超过B个。

对每个集合, 求出它和每个重集合交集的大小, 即cnt[i][j]表示第i个集合和第j 个重集合交集的大小。 cnt数组可以O(NB)求出

对每个重集合, 维护add和sum, add表示加在这个集合上的值, sum表示这个集合没有加上其他重集合的附加值的和。


然后把操作分为4种:

1. 在轻集合上加值。 这样对每个轻集合里面的元素暴力加上v就行(O(B))。 同时要维护重集合的sum, 于是, 每个重集合的sum加上v*cnt[i][j],(O(B))。复杂度(O(B))。

2. 在重集合上加值。 直接在重集合的add上加上v。 (O(1))

3. 询问轻集合。 ans 加上轻集合里面的元素的值, (O(B))。 然后再加上重集合的add[j] * cnt[i][j], (O(B))。 O(B)

4. 询问重集合。 ans 加上重集合的sum, 然后再加上各个重集合的add[j] * cnt[i][j]。 O(B)


#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <queue>
#include <vector>
using namespace std;

#define mnx 110020
#define B 350
#define LL long long
#define PB push_back
#define vi vector<int>

LL add[B], sum[B];
int id[mnx]; //第i个集合的重集合标号
int cnt[mnx][B], c[mnx];
LL tot[mnx];
bool h[mnx]; // 第i个集合是不是重集合
vi g[mnx];   //集合
vi t[mnx];    // 第i个数被哪些重集合包括


int n, m, q, k;

int main() {
	while(scanf("%d%d%d", &n, &m, &q) != EOF) {
		for(int i = 0; i < n; ++i) scanf("%I64d", &tot[i]);
		k = 0;
		for(int i = 0; i < m; ++i) {
			scanf("%d", &c[i]);
			if(c[i] >= B) {
				h[i] = 1;
				id[i] = k++;
			}
			else h[i] = 0;
			for(int j = 0; j < c[i]; ++j) {
				int u;
				scanf("%d", &u);
				--u;
				g[i].PB(u);
				if(h[i]) {  // 重集合的话在包括u的重集合中加上k-1
					t[u].PB(k - 1);
					sum[k-1] += tot[u];
				}
			}
		}
	//	memset(cnt, 0, sizeof cnt);
		for(int i = 0; i < m; ++i) {
			for(int j =  0; j < g[i].size(); ++j) {
				int u = g[i][j];   //第i个集合包含的下标
				for(int kk = 0; kk < t[u].size(); ++kk) {
					int v = t[u][kk];  //重集合
					cnt[i][v]++;
				}
			}
		}
		while(q--) {
			char op[4];
			int p;
			LL x;
			scanf("%s%d", op, &p);
			--p;
			if(op[0] == '?') {
				LL ans = 0;
				if(h[p]) {
					ans += sum[id[p]];
					for(int i = 0; i < k; ++i)
						ans += cnt[p][i] * add[i];
				}
				else {
					for(int i = 0; i < g[p].size(); ++i) {
						ans += tot[g[p][i]];
					}
					for(int i = 0; i < k; ++i)
						ans += cnt[p][i] * add[i];
				}
				printf("%I64d\n", ans);
			}
			else {
				scanf("%I64d", &x);
				if(h[p]) {
					add[id[p]] += x;
				}
				else {
					for(int i = 0; i < g[p].size(); ++i)
						tot[g[p][i]] += x;
					for(int i = 0; i < k; ++i)
						sum[i] += cnt[p][i] * x;
				}
			}
		}
	}
	return 0;
}



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值