=== ===
这里放传送门
=== ===
题解
这玩意儿算啥算法啊。。说它是乱搞吧,它其实也挺机智的;但是好像ATP知道的任何一种除了乱搞以外的算法都不能描述它。。。
这道题的意思是给定一个数列和
m
个集合,集合总大小也不超过
这题的难点就在于对一个集合的修改可能会影响到好多别的集合的数字和,关键就是怎么解决这个问题。然而我们可以发现因为题目中告诉我们集合总大小不超过
m
,那么大小超过
然后考虑如果只有大小大于
m−−√
的集合的话我们该怎么做,设这一类集合的个数为
S
,可以维护一个
那把这两种情况结合一下就有一种科学的做法了。设大小小于
m−−√
的集合为S类集合,个数为
|S|
;大于
m−−√
的集合为B类集合,个数为
|B|
。
修改S类集合的时候首先暴力修改原序列以方便S类集合的求和操作,然后要考虑对B类集合造成的影响。这个时候我们预处理一个大小为
(|S|+|B|)×|B|
的数组来记录每个B类集合和其它所有集合有多少交集,然后对于每个B类集合维护一个数组记录它的元素和,修改的时候根据那个记录的数组来更新元素和就可以。修改B类集合的时候直接维护它的增量就可以了。
对于S类集合的求和操作,先枚举原序列中的元素,这样可以统计S类集合的修改对它造成的影响;因为B类集合的增量没有累加到原序列里,所以枚举所有B类集合,把增量*交集大小累加到答案里。对于B类集合的求和操作,因为S类集合的增量已经累加过来了,所以直接枚举其它的B类集合累加增量就可以了。
可以发现对于S类集合的操作是和它的集合大小成正比的,对于B类集合的操作是和它的集合个数成正比的。总复杂度是 O(mm−−√) 。
代码
#include<cmath>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int n,m,q,tot,rec[100010],cnt[100010][360],num[360],Limit,W,tmp[100010];
long long delta[360],sum[360],a[100010];
struct Set{
int k,l,r;
}s[100010];
long long AskBig(int now){
int pos=tmp[now];
long long ans=sum[pos];
for (int i=1;i<=W;i++)
ans+=(long long)cnt[now][i]*delta[i];
return ans;
}
long long AskSmall(int now){
long long ans=0;
for (int i=s[now].l;i<=s[now].r;i++)
ans+=a[rec[i]];
for (int i=1;i<=W;i++)
ans+=(long long)cnt[now][i]*delta[i];
return ans;
}
void ChangeSmall(int now,int v){
for (int i=s[now].l;i<=s[now].r;i++)
a[rec[i]]+=v;
for (int i=1;i<=W;i++)
sum[i]+=(long long)cnt[now][i]*v;
}
int main()
{
scanf("%d%d%d",&n,&m,&q);
Limit=ceil(sqrt(n));
for (int i=1;i<=n;i++) scanf("%I64d",&a[i]);
for (int i=1;i<=m;i++){
int k;scanf("%d",&k);
s[i].k=k;s[i].l=tot+1;
for (int j=1;j<=k;j++){
int x;scanf("%d",&x);
rec[++tot]=x;
}s[i].r=tot;
if (k>Limit) num[++W]=i;
}
for (int i=1;i<=W;i++){
int u=num[i];
for (int j=s[u].l;j<=s[u].r;j++){
++tmp[rec[j]];
sum[i]+=a[rec[j]];
}
for (int j=1;j<=m;j++)
for (int k=s[j].l;k<=s[j].r;k++)
cnt[j][i]+=tmp[rec[k]];
for (int j=s[u].l;j<=s[u].r;j++) --tmp[rec[j]];
}
for (int i=1;i<=W;i++) tmp[num[i]]=i;
for (int i=1;i<=q;i++){
char c=getchar();
int x,v;
while (c!='?'&&c!='+') c=getchar();
if (c=='?'){
scanf("%d",&x);
if (s[x].k>Limit)
printf("%I64d\n",AskBig(x));
else printf("%I64d\n",AskSmall(x));
}else{
scanf("%d%d",&x,&v);
if (s[x].k>Limit) delta[tmp[x]]+=v;
else ChangeSmall(x,v);
}
}
return 0;
}
偏偏在最后出现的补充说明
这种按照大数据和小数据分类讨论的思路有时候可以解决一些用普通方法很难解决的问题。考虑的时候要注意对于两种类型的元素的特点设计复杂度合适的算法,每一种类型的元素进行操作的时候都要考虑另外一种元素的影响。