Codeforces 348C - Subset Sums(根号分治)

题面传送门

对于这类不好直接维护的数据结构,第一眼应该想到……

根号分治!

我们考虑记【大集合】为大小 \(\geq\sqrt{n}\) 的集合,【小集合】为大小 \(<\sqrt{n}\) 的集合。

显然,查询/修改小集合的时候,直接暴力跑一遍不会出问题,时间复杂度 \(\mathcal O(n\sqrt{n})\)

关键在于怎样处理【大集合】:

  • 修改大集合的时候,暴力一个一个元素修改显然不行,于是考虑整体打一个 \(+v\) 的标记 \(tag_x\)
  • 查询大集合的时候我们也不能遍历一遍集合求和,于是可以维护一个数组 \(ans_x\) 表示大集合 \(x\) 的答案,查询的时候直接输出 \(ans_x\)

这样又有一个问题了,怎样将大集合的贡献累加入答案中。

注意到大集合有一个性质,那就是大集合的个数不会超过 \(\sqrt{n}\)

故每一次集合整体加值的时候,暴力修改每个大集合的 \(ans_x\);询问小集合的时候,将每个大集合的 \(tag_x\) 累加进答案中

至于怎样计算大集合的贡献,可以维护一个 \(same_{i,j}\) 表示第 \(i\) 个大集合与第 \(j\) 个集合有多少个公共的元素。那么你对某个大集合 \(x\) 做一次 \(+v\) 的操作,它对另一个集合 \(y\) 的贡献应为 \(same_{x,y}\times v\)

总结下来,就是:

  1. 修改小集合 \(x\),暴力修改 \(a_i\),并且对于所有大集合 \(y\),令 \(ans_y\) 假设 \(same_{y,x}\times v\)
  2. 修改大集合 \(x\),令 \(tag_x\) 加上 \(v\),并且对于所有大集合 \(y\),令 \(ans_y\) 假设 \(same_{y,x}\times v\)
  3. 查询小集合 \(x\),暴力遍历一遍集合求出 \(a_i\) 的和,并枚举所有大集合 \(y\),令答案加上 \(same_{y,x}\times tag_y\)
  4. 查询大集合 \(x\),直接输出 \(ans_x\) 就行了。

时空复杂度均为 \(n\sqrt{n}\)

#include <bits/stdc++.h>
using namespace std;
#define fi first
#define se second
#define fz(i,a,b) for(int i=a;i<=b;i++)
#define fd(i,a,b) for(int i=a;i>=b;i--)
#define ffe(it,v) for(__typeof(v.begin()) it=v.begin();it!=v.end();it++)
#define fill0(a) memset(a,0,sizeof(a))
#define fill1(a) memset(a,-1,sizeof(a))
#define fillbig(a) memset(a,63,sizeof(a))
#define pb push_back
#define ppb pop_back
#define mp make_pair
template<typename T1,typename T2> void chkmin(T1 &x,T2 y){if(x>y) x=y;}
template<typename T1,typename T2> void chkmax(T1 &x,T2 y){if(x<y) x=y;}
typedef pair<int,int> pii;
typedef long long ll;
template<typename T> void read(T &x){
	x=0;char c=getchar();T neg=1;
	while(!isdigit(c)){if(c=='-') neg=-1;c=getchar();}
	while(isdigit(c)) x=x*10+c-'0',c=getchar();
	x*=neg;
}
const int MAXN=1e5;
const int SQRT=316;
int n,m,qu;ll a[MAXN+5];
vector<int> s[MAXN+5];
int bg[SQRT+5],bgnum=0,same[MAXN+5][SQRT+5];
bitset<MAXN+5> vis[SQRT+5];
ll anss[MAXN+5],add[MAXN+5];
int main(){
	scanf("%d%d%d",&n,&m,&qu);
	for(int i=1;i<=n;i++) scanf("%lld",&a[i]);
	for(int i=1;i<=m;i++){
		int len;scanf("%d",&len);
		for(int j=1;j<=len;j++){
			int x;scanf("%d",&x);s[i].pb(x);
			anss[i]+=a[x];
		}
		if(len>=SQRT){
			bg[++bgnum]=i;
			for(int j=0;j<s[i].size();j++){
				vis[bgnum][s[i][j]]=1;
			}
		}
	}
	for(int i=1;i<=m;i++)
		for(int j=0;j<s[i].size();j++)
			for(int k=1;k<=bgnum;k++)
				if(vis[k][s[i][j]]) same[i][k]++;
	while(qu--){
		static char opt[3];scanf("%s",opt+1);
		if(opt[1]=='?'){
			int x;scanf("%d",&x);
			ll ans=0;
			if(s[x].size()<SQRT){
				for(int j=0;j<s[x].size();j++) ans+=a[s[x][j]];
				for(int j=1;j<=bgnum;j++) ans+=1ll*add[bg[j]]*same[x][j];
				printf("%lld\n",ans);
			} else printf("%lld\n",anss[x]);
		} else {
			int x,y;scanf("%d%d",&x,&y);
			if(s[x].size()<SQRT){
				for(int j=0;j<s[x].size();j++) a[s[x][j]]+=y;
				for(int j=1;j<=bgnum;j++) anss[bg[j]]+=1ll*y*same[x][j];
			} else {
				for(int j=1;j<=bgnum;j++) anss[bg[j]]+=1ll*y*same[x][j];
				add[x]+=y;
			}
		}
	}
	return 0;
}
posted @ 2021-01-30 10:39  tzc_wk  阅读(123)  评论(1)    收藏  举报