题解:CF348C Subset Sums
一眼根号分治,然后发现各种问题置换根本理不清楚。这也是我在想这道题的时候遭遇的最大的困难。
显然的想法:我们设置输入的集合总大小是
为了方便,我们称呼大小小于
我们抛开代码难度不谈,我们总是可以把贡献划分成四块:
- 小集合修改,贡献给小集合。显然这可以暴力维护原序列
\Delta_i ,两边都是\mathcal O(B) 的。 - 小集合修改,贡献给大集合。
- 大集合修改,贡献给小集合。
- 大集合修改,贡献给大集合。
不妨考虑最后一个怎么做,因为最后一个的操作指南比较固定(无论是修改还是询问都不能扫描集合)。为了保证单次复杂度
然后可以发现这是容易的,并且显然可以扩展到任意集合对大集合的贡献。只要我们能算出一张表
算出
剩下只用考虑大集合修改,贡献给小集合怎么做。由于我们已经会小集合修改,贡献给大集合了,所以我们只需转置问题,反过来在询问处扫描大集合加入贡献即可。
确实有点绕。但代码还挺好写。
int n,m,q;
int B;
int t;
int org[N];
int id[N];
int big[N];
pair<int,vector<int>> a[N];
vector <int> in[N];
int cnt[N][320];
ll sum[N];
ll del[N],add[N];
int main(){
#ifdef Shun
freopen(".in","r",stdin);
freopen(".out","w",stdout);
#endif
ios::sync_with_stdio(false);
cin>>n>>m>>q;
fr1(i,1,n) cin>>org[i];
B=sqrt(100000);
fr1(i,1,m){
int k;
cin>>k;
a[i].se.resize(k);
fr1(j,0,k-1) cin>>a[i].se[j],sum[i]+=org[a[i].se[j]];
if(k>=B){
t++;
big[t]=i,id[i]=t;
for(auto j:a[i].se) in[j].pb(t);
}
}
fr1(i,1,m){
for(auto j:a[i].se){
for(auto k:in[j]) cnt[i][k]++;
}
}
while(q--){
char c;
int k,x;
cin>>c>>k;
if(c=='?'){
if(a[k].se.size()>=B) cout<<sum[k]<<'\n';
else{
ll ans=sum[k];
for(auto j:a[k].se) ans+=del[j];
fr1(j,1,t) ans+=1ll*cnt[k][j]*add[j];
cout<<ans<<'\n';
}
}
else{
cin>>x;
if(a[k].se.size()>=B) add[id[k]]+=x;
else for(auto j:a[k].se) del[j]+=x;
fr1(j,1,t) sum[big[j]]+=1ll*cnt[k][j]*x;
}
}
ET;
}
//ALL FOR Zhang Junhao.