乱搞 [CodeForces 348C] Subset Sums nsqrtn
题面去内网找
这道题的思路很神奇,把集合分成了重集合和轻集合。我们把元素个数大于sqrt(n)的集合称为重集合。显然这样的集合超不过sqrt(n)个。
那么就可以分别处理集合了
首先统计出每个集合与每一个重集合交集有多大。维护重集合的sum(总和)和add(这个集合累计加了多少)值。
对于重集合
1.修改:只改一下add就好
2.查询:sum[x]+add所有重集合×与其交集
对于轻集合
1.修改:暴力。
2查询:暴力加+那一堆add
其实这道题的灵感来源于树链剖分的轻重之分
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <algorithm>
#include <cmath>
#define N 100005
#define ll long long
using namespace std;
struct road{int v,next;}lu[N*4];
int n,m,q,h,e,tot,ss[N],adj1[N],adj2[N],vis[N];
ll a[N],cnt[N][320],sum[320],add[320];
void add1(int u,int v){lu[++e]=(road){v,adj1[u]};adj1[u]=e;}
void add2(int u,int v){lu[++e]=(road){v,adj2[u]};adj2[u]=e;}
int main()
{
//freopen("20.in","r",stdin);
//freopen(".out","w",stdout);
scanf("%d%d%d",&n,&m,&q);ll x,y,z;h=sqrt(n);
for(int i=1;i<=n;i++)scanf("%lld",&a[i]);
for(int i=1;i<=m;i++)
{
scanf("%lld",&y);
if(y>=h)++tot,vis[i]=1,ss[i]=tot;
for(int j=1;j<=y;j++)
{
scanf("%lld",&x),add1(i,x);
if(vis[i]==1)sum[ss[i]]+=a[x],add2(x,ss[i]);
}
}
for(int i=1;i<=m;i++)
for(int k=adj1[i];k;k=lu[k].next)
{
int to=lu[k].v;
for(int j=adj2[to];j;j=lu[j].next)
cnt[i][lu[j].v]++;
}
ll ans=0;
while(q--)
{
scanf("%lld",&z);
if(z==1)
{
scanf("%lld",&x);ans=0;
if(!vis[x])
{
for(int i=adj1[x];i;i=lu[i].next)ans+=a[lu[i].v];
for(int i=1;i<=tot;i++)ans+=add[i]*cnt[x][i];
}
else
{
ans=sum[ss[x]];
for(int i=1;i<=tot;i++)ans+=add[i]*cnt[x][i];
}
printf("%lld\n",ans);
}
else
{
scanf("%lld%lld",&x,&y);
if(!vis[x])
{
for(int i=adj1[x];i;i=lu[i].next)a[lu[i].v]+=y;
for(int i=1;i<=tot;i++)sum[i]+=y*cnt[x][i];
}
else add[ss[x]]+=y;
}
}
}