5.二次求和
暴力
首先观察询问,树上链u→vu\to vu→v点权加,显然可以用树上差分LOJ dfs序4 O(1)O(1)O(1)完成此操作,然后考虑对这些权值对答案的影响?
设经过某点uuu符合条件的路径条数为pathu\text{path}_upathu
当uuu点权+c+c+c后,那么它对答案的贡献则是pathu×c\text{path}_u×cpathu×c,而对于u→vu\to vu→v这条路径来说,点权全部+c+c+c后对答案的贡献是
∑i∈u→vpathi×c\sum_{i \in u\to v} \text{path}_i×ci∈u→v∑pathi×c
如果完成pathu\text{path}_upathu预处理后以及最开始的答案,之后每个操作对答案的贡献可根据上述方法求出。
观察题目数据范围发现前50pts50\text{pts}50pts的n≤2000n\leq 2000n≤2000,这就很容易了,暴力枚举每个点为起点,把所有路径都拿出来然后记录一下每条路径,当一条路径符号题意时维护pathu\text{path}_upathu即可。
然后观察到60pts60\text{pts}60pts的点是一条链,可以乱搞一下在骗10pts10\text{pts}10pts
#include<queue>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
using ll=long long;
constexpr int N=2010;
constexpr ll mod=1e9+7;
int h[N],e[2*N],ne[2*N],idx;
void add(int a,int b){e[idx]=b,ne[idx]=h[a],h[a]=idx++;}
int n,m,L,R;
int a[N],d[N];
bool vis[N];
ll b[N],ans,path[N];
int pre[N];
void bfs(int S)
{memset(d,0,sizeof(int)*(n+1));memset(pre,0,sizeof(int)*(n+1));memset(b,0,sizeof(ll)*(n+1));memset(vis,0,sizeof(bool)*(n+1));queue<int> q;q.push(S); d[S]=0;b[S]=a[S];vis[S]=1;while(q.size()){int u=q.front();q.pop();for(int i=h[u];i!=-1;i=ne[i]){int v=e[i];if(!vis[v]){d[v]=d[u]+1;b[v]=(b[u]+a[v])%mod;pre[v]=u;//记录路径q.push(v);vis[v]=1;}}}for(int i=1;i<=n;i++)if(L<=d[i]&&d[i]<=R) {ans=(ans+b[i])%mod;int u=i;while(u) //维护路径path{path[u]++;u=pre[u];}}
}
int sz[N],fa[N],dep[N],son[N];
ll s[N];
void dfs1(int u)
{dep[u]=dep[fa[u]]+1;sz[u]=1;s[u]=path[u]+s[fa[u]];// 预处理s数组for(int i=h[u];i!=-1;i=ne[i]){int v=e[i];if(v==fa[u]) continue;fa[v]=u;dfs1(v);sz[u]+=sz[v];if(sz[son[u]]<sz[v]) son[u]=v;}
}
int dfn[N],timestamp,top[N];
void dfs2(int u,int t)
{dfn[u]=++timestamp;top[u]=t;if(son[u]) dfs2(son[u],t);for(int i=h[u];i!=-1;i=ne[i]){int v=e[i];if(v==fa[u]||v==son[u]) continue;dfs2(v,v);}
}
int lca(int u,int v)
{while(top[u]!=top[v]){if(dep[top[u]]>=dep[top[v]]) u=fa[top[u]];else v=fa[top[v]];}return dep[u]>=dep[v]?v:u;
}
void init(int n)
{memset(h,-1,sizeof(int)*(n+1));memset(s,0,sizeof(ll)*(n+1));memset(path,0,sizeof(ll)*(n+1));memset(dfn,0,sizeof(int)*(n+1));memset(son,0,sizeof(int)*(n+1));memset(top,0,sizeof(int)*(n+1));memset(sz,0,sizeof(int)*(n+1));timestamp=idx=0;ans=0;
}
int main()
{int T;cin>>T;while(T--){cin>>n>>m>>L>>R;init(n);--L,--R;for(int i=1;i<=n;i++) cin>>a[i];for(int i=2;i<=n;i++){int p;cin>>p;add(i,p),add(p,i);}for(int i=1;i<=n;i++) bfs(i);ans=1ll*ans*500000004%mod;for(int i=1;i<=n;i++) path[i]/=2;dfs1(1);dfs2(1,1);while(m--){int a,b,c;cin>>a>>b>>c;int anc=lca(a,b);ans=(ans+(s[a]+s[b]-s[anc]-s[fa[anc]])*c%mod)%mod;cout<<ans<<'\n';}}
}
点分治
学过点分治的一看就知道是个点分治的题,但是看到之后就懒得写代码又臭又长
考虑如何通过点分治求出最初的答案以及维护出数组pathu\text{path}_upathu?
首先对于最初的答案显然就是个点分治模板题这里不在赘述,而对于pathu\text{path}_upathu显然不能像暴力一下记录路径然后加,这样就没必要分治了~~
点分治的技巧是花费log的代价把任意路径变成通过当前根节点的路径,也就是目前考虑的路径都会穿过当前根节点
既然穿过根节点,我们只需要在每条路径的端点记录一下,显然如果当前点是一个路径的端点,那么它的祖先节点也需要被这条路径覆盖,只需要一遍dfs把子节点“回收”一下就能够实现将此路径覆盖的点都标记。
不过一条路径是有两个端点的,但是点分治的过程(枚举当前子树的端点,在前面的子树中查找符合条件的另一个端点)只能知道当前的一个端点,而另一个端点我们是不知道的。
比如当前子树vvv的一个端点bbb,而前面的子树有一个端点aaa,也就是a→rt→ba\to rt\to ba→rt→b这条路径符合条件,点分治的过程中我们只知道当前子树vvv的端点bbb
这里我采用先从前向后遍历子树(找到端点bbb),然后再从后往前遍历子树(找到端点aaa),不难发现这样枚举都会枚举到a→rt→ba\to rt\to ba→rt→b这条路径,并且一次是端点bbb,另一次是端点aaa(注意rt可能会多算需要减去)
对于直接到当前根节点的路径我们特殊处理即可
时间复杂度O(αnlog2n+mlogn)O(\alpha n\log^2 n+m\log n)O(αnlog2n+mlogn)
#pragma GCC optimize(2)
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
using ll=long long;
constexpr int N=100010;
constexpr ll mod=1e9+7;
int h[N],h2[N],e[4*N],ne[4*N],idx;
void add(int h[],int a,int b){e[idx]=b,ne[idx]=h[a],h[a]=idx++;}
int n,m,L,R;
int a[N],p[N];
int sz[N],fa[N],dep[N],son[N];
int dfn[N],timestamp,top[N];
ll s[N];
ll path[N],ans;
int rt;
ll fw[2][N];
int lowbit(int x){return x&-x;}
void update(int i,int k,ll x){if(k<=0) return;for(;k<=n;k+=lowbit(k)) fw[i][k]=(fw[i][k]+x)%mod;}
ll query(int i,int k){if(k<=0) return 0;ll res=0;for(;k;k-=lowbit(k)) res=(res+fw[i][k])%mod;return res;}
struct node
{int d,u;ll w;
}d[N];
ll b[N];int cnt;
bool del[N];
void dfs_rt(int u,int fa,int tot)//找重心
{sz[u]=1;int mx=0;for(int i=h[u];i!=-1;i=ne[i]){int v=e[i];if(v==fa||del[v]) continue;dfs_rt(v,u,tot);sz[u]+=sz[v];mx=max(mx,sz[v]);}mx=max(mx,tot-sz[u]);if(2*mx<=tot) rt=u;
}
void dfs_sz(int u,int fa)//处理sz
{sz[u]=1;for(int i=h[u];i!=-1;i=ne[i]){int v=e[i];if(v==fa||del[v]) continue;dfs_sz(v,u);sz[u]+=sz[v];}
}
void dfs_dist(int u,int fa,int dist,ll w)//找路径
{w%=mod;d[++cnt]={dist,u,w};for(int i=h[u];i!=-1;i=ne[i]){int v=e[i];if(v==fa||del[v]) continue;dfs_dist(v,u,dist+1,w+a[v]);}
}
void dfs_fw(int u,int fa,int dist,ll w)//清空树状数组
{w%=mod;update(0,dist,-w);update(1,dist,-1);for(int i=h[u];i!=-1;i=ne[i]){int v=e[i];if(v==fa||del[v]) continue;dfs_fw(v,u,dist+1,w+a[v]);}
}
void dfs_add(int u,int fa)
{for(int i=h[u];i!=-1;i=ne[i]){int v=e[i];if(v==fa||del[v]) continue;dfs_add(v,u);b[u]+=b[v];b[u]%=mod;}path[u]+=b[u];path[u]%=mod;
}
void dfs_b(int u,int fa)//清空b数组
{b[u]=0;for(int i=h[u];i!=-1;i=ne[i]){int v=e[i];if(v==fa||del[v]) continue;dfs_b(v,u);}
}
void dfs_calc(int u,int fa,int dist,ll w)//到当前根节点特殊路径
{w%=mod;if(L<=dist&&dist<=R) b[u]++,ans=(ans+w)%mod;for(int i=h[u];i!=-1;i=ne[i]){int v=e[i];if(v==fa||del[v]) continue;dfs_calc(v,u,dist+1,w+a[v]);}
}
void work(int u,int tot)
{dfs_rt(u,0,tot);u=rt;dfs_sz(u,0);del[u]=1;//顺着子树for(int i=h[u];i!=-1;i=ne[i]){int v=e[i];if(del[v]) continue;cnt=0;// 指针dfs_dist(v,u,1,a[v]);for(int k=1;k<=cnt;k++) ans=(ans+query(0,R-d[k].d)-query(0,L-1-d[k].d))%mod;for(int k=1;k<=cnt;k++) {int tmp=query(1,R-d[k].d)-query(1,L-1-d[k].d);ans=(ans+1ll*d[k].w%mod*tmp%mod)%mod;b[u]-=tmp;//防止当前根节点重复算b[d[k].u]+=tmp;}for(int k=1;k<=cnt;k++)update(0,d[k].d,d[k].w+a[u]),update(1,d[k].d,1);}dfs_fw(u,0,0,a[u]);//逆子树for(int i=h2[u];i!=-1;i=ne[i]){int v=e[i];if(del[v]) continue;cnt=0;// 指针dfs_dist(v,u,1,a[v]);for(int k=1;k<=cnt;k++) {int tmp=query(1,R-d[k].d)-query(1,L-1-d[k].d);b[d[k].u]+=tmp;}for(int k=1;k<=cnt;k++)update(0,d[k].d,d[k].w+a[u]),update(1,d[k].d,1);}dfs_calc(u,0,0,a[u]);dfs_add(u,0);dfs_fw(u,0,0,a[u]); dfs_b(u,0);for(int i=h[u];i!=-1;i=ne[i]){int v=e[i];if(del[v]) continue;work(v,sz[v]);}}
//==================================================点分治
void dfs1(int u)
{dep[u]=dep[fa[u]]+1;sz[u]=1;s[u]=(path[u]+s[fa[u]])%mod;for(int i=h[u];i!=-1;i=ne[i]){int v=e[i];if(v==fa[u]) continue;fa[v]=u;dfs1(v);sz[u]+=sz[v];if(sz[son[u]]<sz[v]) son[u]=v;}
}
void dfs2(int u,int t)
{dfn[u]=++timestamp;top[u]=t;if(son[u]) dfs2(son[u],t);for(int i=h[u];i!=-1;i=ne[i]){int v=e[i];if(v==fa[u]||v==son[u]) continue;dfs2(v,v);}
}
int lca(int u,int v)
{while(top[u]!=top[v]){if(dep[top[u]]>=dep[top[v]]) u=fa[top[u]];else v=fa[top[v]];}return dep[u]>=dep[v]?v:u;
}
//================================================== 树剖求lca
void init(int n)
{memset(h,-1,sizeof(int)*(n+1));memset(h2,-1,sizeof(int)*(n+1));memset(path,0,sizeof(ll)*(n+1));memset(s,0,sizeof(ll)*(n+1));memset(dfn,0,sizeof(int)*(n+1));memset(son,0,sizeof(int)*(n+1));memset(top,0,sizeof(int)*(n+1));memset(sz,0,sizeof(int)*(n+1));memset(del,0,sizeof(bool)*(n+1));timestamp=idx=0;ans=0;
}
int main()
{int T;scanf("%d",&T);while(T--){scanf("%d%d%d%d",&n,&m,&L,&R);init(n);--L,--R;for(int i=1;i<=n;i++) scanf("%lld",&a[i]);for(int i=2;i<=n;i++) scanf("%lld",&p[i]);for(int i=2;i<=n;i++) add(h,i,p[i]),add(h,p[i],i);//顺着子树for(int i=n;i>=2;i--) add(h2,i,p[i]),add(h2,p[i],i);//逆着子树work(1,n);dfs1(1);dfs2(1,1);while(m--){int a,b,c;scanf("%d%d%d",&a,&b,&c);int anc=lca(a,b);ans=(ans+(s[a]+s[b]-s[anc]-s[fa[anc]])*c%mod)%mod;ans=(ans+mod)%mod;printf("%lld\n",ans);}}
}
这题真蛋疼,写完还因为常数太大(点分治9次dfs)TLE了,吸氧才AC
AcWing 3261. 二次求和吸氧过了,官网还是TLE,80pts
要加油哦~