P4074 [WC2013]糖果公园
P4074 [WC2013]糖果公园
Tag:树上带修莫队
题意:树上每个点有一种糖果,求\(\sum_c\sum_{i=1}^{cnt_c}v_c*w_i\)
其中c为糖果种类,\(cnt_c\)其为出现次数。
思路
离线树上带修莫队。
先进行树上分块。分块内的询问按照出发点、终止点、询问id优先级依次递减排序。
对于树上莫队,其实就是在欧拉序上莫队。因为欧拉序的性质,即每个节点子树内的节点一定会经过两次,我们就可以用一个括号序列的方式在莫队时消除子树内无用节点的影响。
具体来说,序列长度为2*n,每一个节点出入队时我们异或它是否在队中即可。也就是记录每个点出队和入队的时间戳,然后在序列上修改。
注意,对于LCA其实在欧拉序时是没有包括的,所以我们需要单独求一下LCA的影响。但是如果一个端点本身就是LCA就不用啦。
但是!用指针实现也太low了,我们直接利用它的树形结构莫队就行(实际上就是我看错了题解)
代码
分块和排序
int dfs(int x,int f){//毒瘤的树分块而非序列分块
int siz=0;
fa[x][0]=f;dep[x]=dep[f]+1;
dfn[x]=++tim;
for(int i=0;i<=20;i++)fa[x][i+1]=fa[fa[x][i]][i];
for(int i=head[x];i;i=nxt[i]){
int u=to[i];
if(u==f)continue;
siz+=dfs(u,x);
if(siz>=_nsiz){
_n++;
while(siz)belong[st[top--]]=_n,siz--;
}
}
st[++top]=x;
return siz+1;
}
//----------------
++_n;
while(top)belong[st[top--]]=_n;
inline bool operator < (const query&zp)const {return belong[u]<belong[zp.u] or (belong[u]==belong[zp.u] and (belong[v]<belong[zp.v] or (belong[v]==belong[zp.v] and id<zp.id)));}//运算符版
莫队
inline void reverse(const int& x,ll &ans){//将进队的出队,出队的进队。
if(vis[x])ans-=1ll*w[num[color[x]]]*v[color[x]],num[color[x]]--;
else num[color[x]]++,ans+=1ll*w[num[color[x]]]*v[color[x]];
vis[x]^=1;
}
inline void solve(int x,int y,ll &ans){//直接利用树形结构跳father,更新答案,反正括号序列也就是翻转。
while(x!=y){
if(dep[x]>dep[y])reverse(x,ans),x=fa[x][0];
else reverse(y,ans),y=fa[y][0];
}
}
for(int i=1;i<=cntq;i++){
while(now<cntm and mo[now+1].id<=q[i].id){//处理时间问题,只能暴力消除影响。
now++;
if(vis[mo[now].pos])ans-=w[num[color[mo[now].pos]]]*v[color[mo[now].pos]],num[color[mo[now].pos]]--;
color[mo[now].pos]=mo[now].aft;
if(vis[mo[now].pos])num[color[mo[now].pos]]++,ans+=1LL*w[num[color[mo[now].pos]]]*v[color[mo[now].pos]];
}
while(now>=1 and mo[now].id>=q[i].id){
if(vis[mo[now].pos])ans-=w[num[color[mo[now].pos]]]*v[color[mo[now].pos]],num[color[mo[now].pos]]--;
color[mo[now].pos]=mo[now].bef;//注意变成了什么(我就是傻
if(vis[mo[now].pos])num[color[mo[now].pos]]++,ans+=1LL*w[num[color[mo[now].pos]]]*v[color[mo[now].pos]];
now--;
}
if(i==1)solve(q[i].u,q[i].v,ans);
else solve(q[i].u,q[i-1].u,ans),solve(q[i].v,q[i-1].v,ans);//保证继承答案连续
int lca=LCA(q[i].u,q[i].v);
reverse(lca,ans);
q[i].ans=ans;
reverse(lca,ans);//单独计算,最后要消除影响。不要被继承。
}
事实证明,这种写法是真毒瘤,开了O2才勉强能过。
全代码
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cctype>
#include<cmath>
using namespace std;
inline int read(){
int w=0,x=0;char c=getchar();
while(!isdigit(c))w|=c=='-',c=getchar();
while(isdigit(c))x=(x<<3)+(x<<1)+(c^48),c=getchar();
return w?-x:x;
}
namespace star
{
const int maxn=1e5+10;
typedef long long ll;
int n,m,Q,_n,fa[maxn][23],_nsiz,dfn[maxn],dep[maxn],c[maxn],color[maxn];
ll v[maxn],w[maxn];
int ecnt,tim,st[maxn<<1],belong[maxn],top,head[maxn],to[maxn<<1],nxt[maxn<<1];
struct query{
int u,v,id;
ll ans;
inline bool operator < (const query&zp)const {return belong[u]<belong[zp.u] or (belong[u]==belong[zp.u] and (belong[v]<belong[zp.v] or (belong[v]==belong[zp.v] and id<zp.id)));}
}q[maxn];
inline bool cmp(query a,query b){return a.id<b.id;}
struct modify{
int pos,bef,aft,id;
}mo[maxn<<1];
inline void addedge(int a,int b){
to[++ecnt]=b,nxt[ecnt]=head[a],head[a]=ecnt;
to[++ecnt]=a,nxt[ecnt]=head[b],head[b]=ecnt;
}
int dfs(int x,int f){
int siz=0;
fa[x][0]=f;dep[x]=dep[f]+1;
dfn[x]=++tim;
for(int i=0;i<=20;i++)fa[x][i+1]=fa[fa[x][i]][i];
for(int i=head[x];i;i=nxt[i]){
int u=to[i];
if(u==f)continue;
siz+=dfs(u,x);
if(siz>=_nsiz){
_n++;
while(siz)belong[st[top--]]=_n,siz--;
}
}
st[++top]=x;
return siz+1;
}
inline int LCA(int x,int y){
if(dep[x]<dep[y])swap(x,y);
for(int i=20;i+1;i--)if(dep[fa[x][i]]>=dep[y])x=fa[x][i];
if(x==y)return x;
for(int i=20;i+1;i--)if(fa[x][i]!=fa[y][i])x=fa[x][i],y=fa[y][i];
return fa[x][0];
}
bool vis[maxn];
int num[maxn];
inline void reverse(const int& x,ll &ans){
if(vis[x])ans-=1ll*w[num[color[x]]]*v[color[x]],num[color[x]]--;
else num[color[x]]++,ans+=1ll*w[num[color[x]]]*v[color[x]];
vis[x]^=1;
}
inline void solve(int x,int y,ll &ans){
while(x!=y){
if(dep[x]>dep[y])reverse(x,ans),x=fa[x][0];
else reverse(y,ans),y=fa[y][0];
}
}
inline void work(){
n=read(),m=read(),Q=read();
_nsiz=pow(n,0.666666666);
for(int i=1;i<=m;i++)v[i]=read();
for(int i=1;i<=n;i++)w[i]=read();
for(int i=1;i<n;i++)addedge(read(),read());
for(int i=1;i<=n;i++)c[i]=color[i]=read();
int cntq=0,cntm=0,x;
for(int i=1;i<=Q;i++){
if(read())q[++cntq].id=i,q[cntq].u=read(),q[cntq].v=read();
else mo[++cntm].id=i,mo[cntm].bef=c[(x=read())],mo[cntm].pos=x,mo[cntm].aft=c[x]=read();
}
dfs(1,0);
for(int i=1;i<=cntq;i++)if(dfn[q[i].v]<dfn[q[i].u])swap(q[i].u,q[i].v);//对于这种写法这句其实可有也可无了。这种写法的一个好处就是不交换不影响正确性。
++_n;
while(top)belong[st[top--]]=_n;
sort(q+1,q+1+cntq);
int now=0;
long long ans=0;
for(int i=1;i<=cntq;i++){
while(now<cntm and mo[now+1].id<=q[i].id){
now++;
if(vis[mo[now].pos])ans-=w[num[color[mo[now].pos]]]*v[color[mo[now].pos]],num[color[mo[now].pos]]--;
color[mo[now].pos]=mo[now].aft;
if(vis[mo[now].pos])num[color[mo[now].pos]]++,ans+=1LL*w[num[color[mo[now].pos]]]*v[color[mo[now].pos]];
}
while(now>=1 and mo[now].id>=q[i].id){
if(vis[mo[now].pos])ans-=w[num[color[mo[now].pos]]]*v[color[mo[now].pos]],num[color[mo[now].pos]]--;
color[mo[now].pos]=mo[now].bef;
if(vis[mo[now].pos])num[color[mo[now].pos]]++,ans+=1LL*w[num[color[mo[now].pos]]]*v[color[mo[now].pos]];
now--;
}
if(i==1)solve(q[i].u,q[i].v,ans);
else solve(q[i].u,q[i-1].u,ans),solve(q[i].v,q[i-1].v,ans);
int lca=LCA(q[i].u,q[i].v);
reverse(lca,ans);
q[i].ans=ans;
reverse(lca,ans);
}
sort(q+1,q+1+cntq,cmp);
for(int i=1;i<=cntq;i++)printf("%lld\n",q[i].ans);
}
}
signed main(){
star::work();
return 0;
}
总结
写了个假的(gxy称之为非主流)树上莫队。再写一道免得脑子了装着奇怪的东西。
因为写法毒瘤所以#define int long long会T飞。
大家千万不要学非主流