给定一个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;
}