2018CCF-CSP 5.二次求和(点分治)

5.二次求和

暴力
首先观察询问,树上链u→vu\to vuv点权加,显然可以用树上差分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 vuv这条路径来说,点权全部+c+c+c后对答案的贡献是

∑i∈u→vpathi×c\sum_{i \in u\to v} \text{path}_i×ciuvpathi×c

如果完成pathu\text{path}_upathu预处理后以及最开始的答案,之后每个操作对答案的贡献可根据上述方法求出。

观察题目数据范围发现前50pts50\text{pts}50ptsn≤2000n\leq 2000n2000,这就很容易了,暴力枚举每个点为起点,把所有路径都拿出来然后记录一下每条路径,当一条路径符号题意时维护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 bartb这条路径符合条件,点分治的过程中我们只知道当前子树vvv的端点bbb

这里我采用先从前向后遍历子树(找到端点bbb),然后再从后往前遍历子树(找到端点aaa),不难发现这样枚举都会枚举到a→rt→ba\to rt\to bartb这条路径,并且一次是端点bbb,另一次是端点aaa(注意rt可能会多算需要减去)

对于直接到当前根节点的路径我们特殊处理即可

时间复杂度O(αnlog⁡2n+mlog⁡n)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

要加油哦~

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/318933.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

P4323-[JSOI2016]独特的树叶【换根dp,树哈希】

正题 题目链接:https://www.luogu.com.cn/problem/P4323 题目大意 给出nnn个点的树和加上一个点之后的树&#xff08;编号打乱&#xff09;。 求多出来的是哪个点&#xff08;如果有多少个就输出编号最小的&#xff09;。 1≤n≤1051\leq n\leq 10^51≤n≤105 解题思路 定义…

牛客题霸 [ 数字在升序数组中出现的次数] C++题解/答案

牛客题霸 [ 数字在升序数组中出现的次数] C题解/答案 题目描述 统计一个数字在升序数组中出现的次数。 题解&#xff1a; 直接for循环&#xff0c;if判断一下&#xff0c;如果是目标的话ant 代码&#xff1a; class Solution { public:int GetNumberOfK(vector<int>…

.Net Core微服务系列--理论篇

微服务的由来微服务最早由Martin Fowler与James Lewis于2014年共同提出来的&#xff0c;但是微服务也不是一个全新的概念&#xff0c;它是由一系列在实践中获得成功并流行起来的概念中总结出来的一种模式&#xff0c;一种概念。而这一系列的概念大体上有这些:领域驱动设计(DDD)…

codeforces1496 D. Let‘s Go Hiking(乱搞+讨论)

这题我tm服了&#xff0c;考试中途肯定添加了一组数据&#xff0c;提交完A了之后&#xff0c;还有20min结束&#xff0c;感觉写不了下一个题了&#xff0c;就下班了&#xff0c;谁知道它有填了一组测试数据把我的乱搞给卡过去了&#xff0c;我又被fst了&#xff1f;&#xff1f…

CF1511G-Chips on a Board【倍增】

正题 题目链接:https://www.luogu.com.cn/problem/CF1511G 题目大意 给出n∗mn*mn∗m的棋盘上每一行有一个棋子&#xff0c;双方轮流操作可以把一个棋子向左移动若干步&#xff08;不能不动&#xff09;&#xff0c;无法操作者输。 qqq次询问只留下期盼的l∼rl\sim rl∼r列时…

牛客题霸 [ 调整数组顺序使奇数位于偶数前面] C++题解/答案

牛客题霸 [ 调整数组顺序使奇数位于偶数前面] C题解/答案 题目描述 输入一个整数数组&#xff0c;实现一个函数来调整该数组中数字的顺序&#xff0c;使得所有的奇数位于数组的前半部分&#xff0c;所有的偶数位于数组的后半部分&#xff0c;并保证奇数和奇数&#xff0c;偶数…

Asp.net Core Jenkins Docker 实现一键化部署

写在前面在前段时间尝试过用Jenkins来进行asp.net core 程序在IIS上面的自动部署。大概的流程是Jenkins从git上获取代码最开始Jenkins是放在Ubuntu的Docker中&#xff0c;但是由于Powershell执行的原因&#xff0c;就把Jenkins搬到了windows上。因为我们网站的部署需要停掉IIS站…

P5782-[POI2001]和平委员会【2-SAT】

正题 题目链接:https://www.luogu.com.cn/problem/P5782 题目大意 nnn对人&#xff0c;每对之间恰好有一个人出席。mmm对仇恨关系表示两个人不能同时出席。 求是否有解并输出。 1≤n≤8000,1≤m≤200001\leq n\leq 8000,1\leq m\leq 200001≤n≤8000,1≤m≤20000 解题思路 裸…

codeforces1497 E. Square-free division(数学+dp)

开学了&#xff0c;感觉没时间打cf了&#xff0c;上课听不懂&#xff0c;而且一直在忙转班的事情~~ 下周就要回学校了开心 昨天卡C题太久了&#xff0c;一直在想lcm的性质&#xff0c;还好最后回头了&#xff0c;当成构造题做了&#xff0c;瞎搞了搞就出来了&#xff0c;然后看…

牛客题霸 [合并二叉树] C++题解/答案

牛客题霸 [合并二叉树] C题解/答案 题目描述 已知两颗二叉树&#xff0c;将它们合并成一颗二叉树。合并规则是&#xff1a;都存在的结点&#xff0c;就将结点值加起来&#xff0c;否则空的位置就由另一个树的结点来代替。例如&#xff1a; 两颗二叉树是: Tree 1 1 / \ 3 2 / …

“校长”潘淳:侠之大者,一蓑烟雨任平生

我是与丁磊、蔡文胜同时代的人&#xff0c;他们都是70后大我两岁。我的经历与爱好与丁磊有丁点接近&#xff0c;但是没他下海走一走的胆识。又或者与蔡文胜一样&#xff0c;也算是国内最早的域名代理商&#xff0c;却又没有投资的勇气。—— 潘淳《IT英雄传》这一期的主角儿是江…

P4922-[MtOI2018]崩坏3?非酋之战!【dp】

正题 题目链接:https://www.luogu.com.cn/problem/P4922 题目大意 题目好长直接放了 在崩坏 3 中有一个叫做天命基地的地方&#xff0c;女武神们将在基地中开派对与敌人们厮杀。 女武神们的攻击力为 atkatkatk&#xff0c;她们将进行资源保卫战&#xff01; 天命基地中有 …

C. Minimum Grid Path(思维)

昨天晚上写的时候看错题了&#xff0c;以为并不是交替走&#xff0c;最后没时间了读了一遍题目发现是交替走&#xff0c;然后就秒了但是已经没时间写了。 其实昨天并不想写&#xff0c;不过看了下D题发现是个数学题&#xff0c;虽然我数学题非常渣渣&#xff0c;但是拿起笔就推…

牛客题霸 [扑克牌顺子] C++题解/答案

管理博文 牛客题霸 [扑克牌顺子] C题解/答案 题目描述 LL今天心情特别好,因为他去买了一副扑克牌,发现里面居然有2个大王,2个小王(一副牌原本是54张_)…他随机从中抽出了5张牌,想测测自己的手气,看看能不能抽到顺子,如果抽到的话,他决定去买体育彩票,嘿嘿&#xff01;&#x…

微软Cloud+AI本地化社区贡献指南

本文主要介绍微软CloudAI本地化社区&#xff0c;以及通过多种途径贡献本地化的操作指南。什么是本地化社区CloudAI本地化社区是微软技术社区的组成部分之一&#xff0c;负责对微软官方技术文档本地化的支持工作。微软近些年大力拥抱开源&#xff0c;不断在各类技术社区保持与开…

P7581-「RdOI R2」路径权值【长链剖分,dp】

正题 题目链接:https://www.luogu.com.cn/problem/P7581 题目大意 给出nnn个点的有边权有根树&#xff0c;mmm次询问一个节点xxx的所有kkk级儿子两两之间路径长度。 1≤n,m≤1061\leq n,m\leq 10^61≤n,m≤106 解题思路 有根长剖&#xff0c;无根点分治。所以这题应该是长剖…

牛客题霸 [ 未排序数组中累加和为给定值的最长子数组长度] C++题解/答案

牛客题霸 [ 未排序数组中累加和为给定值的最长子数组长度] C题解/答案 题目描述 给定一个无序数组arr, 其中元素可正、可负、可0。给定一个整数k&#xff0c;求arr所有子数组中累加和为k的最长子数组长度 题解&#xff1a; 先求出前缀和 然后用map来记录第i位的前缀和 要找…

「分块」数列分块入门1 – 9

ACM模板 放暑假了&#xff0c;回归&#xff01;&#xff01;&#xff01; 自己不会写暴力&#xff0c;而且好久没写代码了&#xff0c;于是学学分块的优雅暴力~ 「分块入门-LibreOJ」 「分块」数列分块入门1 – 9 by hzwer 数列简单分块问题实际上有三项东西要我们思考&#…

C#:在Task中使用依赖注入的Service/EFContext

dotnet core时代,依赖注入基本已经成为标配了,这就不多说了.前几天在做某个功能的时候遇到在Task中使用EF DbContext的问题,学艺不精的我被困扰了不短的一段时间,于是有了这个文章.先说一下代码结构和场景.首先有一个HouseDbContext,代码大概是下面这样:public class HouseDbCo…

牛客题霸 [ 最长公共前缀] C++题解/答案

牛客题霸 [ 最长公共前缀] C题解/答案 题目描述 编写一个函数来查找字符串数组中的最长公共前缀。 题解&#xff1a; 原本想暴力做&#xff0c;直接多层循环套&#xff0c;发现并不用 注意本题&#xff0c;给出了多组字符串&#xff0c;求它们的最长公共前缀&#xff0c;这…