题解:暴力+技巧
两种操作:(1)将集合中每个元素对应位置的权值+x
(2)查询集合中每个元素对应位置的权值和。
设集合大小的和为n
我们将集合分成两类,定义大集合为集合中元素个数大于sqrt(n),小集合为集合中元素个数小于等于sqrt(n)
然后预处理出每个集合与每个大集合交集得到大小,因为大集合的数量不会超过sqrt(n),所以时间复杂度是O(nsqrt(n))
对于每个集合的和我们分成三部分计算:集合中元素初始值的和+小集合对该集合的影响+大集合对该集合的影响。
修改:
(1)小集合:我们将每个位置的val增加x,然后考虑这个小集合对于每个大集合的影响+num[i][j]*x (num[i][j]表示两者的交集大小)
(2)大集合:我们直接将大集合的影响标记+x
查询:
(1)小集合:将每个位置的权值累加,然后枚举每个大集合,计算大集合的delta对小集合的影响 +num[i][j]*delta[j]
(2)大集合:初始权值和+小集合对大集合的影响+大集合对该集合的影响。
这样均摊的时间辅助度是O(m sqrt(n))
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#define N 100003
#define LL long long
using namespace std;
int n,m,q;
int point[N],v[N],next[N],tot,size[N];
LL sum[N],delta[N],tag[N],val[N];
int num[N][400],mark[N],a[N],opt[N],belong[N];
void add(int x,int y)
{
tot++; next[tot]=point[x]; point[x]=tot; v[tot]=y;
}
int main()
{
freopen("a.in","r",stdin);
scanf("%d%d%d",&n,&m,&q);
for (int i=1;i<=n;i++) scanf("%I64d",&val[i]);
for (int i=1;i<=m;i++) {
scanf("%d",&size[i]); size[0]+=size[i];
for (int j=1;j<=size[i];j++){
int x; scanf("%d",&x);
add(i,x); sum[i]+=val[x];
}
}
int cnt=0;
for (int i=1;i<=m;i++)
if (size[i]>sqrt(size[0]*1.0)) a[++cnt]=i,opt[i]=1,belong[i]=cnt;
else opt[i]=0;
for (int i=1;i<=cnt;i++) {
memset(mark,0,sizeof(mark));
for (int j=point[a[i]];j;j=next[j])
mark[v[j]]=1;
for (int j=1;j<=m;j++) {
for (int k=point[j];k;k=next[k]) num[j][i]+=mark[v[k]];
}
}
/*for (int i=1;i<=m;i++) {
for (int j=1;j<=cnt;j++) cout<<num[i][j]<<" ";
cout<<endl;
}*/
for (int i=1;i<=q;i++) {
char s[10]; int x,y;
scanf("%s",s);
if (s[0]=='?') {
scanf("%d",&x);
LL ans=0;
if (opt[x]==0) {
for (int j=point[x];j;j=next[j]) ans+=val[v[j]];
for (int j=1;j<=cnt;j++)
ans+=delta[j]*num[x][j];
printf("%I64d\n",ans);
}
else {
for (int j=1;j<=cnt;j++) ans+=delta[j]*num[x][j];
printf("%I64d\n",ans+sum[x]+tag[belong[x]]);
}
}
else{
scanf("%d%d",&x,&y);
if (opt[x]==0) {
for (int j=point[x];j;j=next[j])
val[v[j]]+=y;
for (int j=1;j<=cnt;j++)
tag[j]+=y*num[x][j];
}
else {
delta[belong[x]]+=y;
}
}
}
}