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(α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

要加油哦~

2018CCF-CSP 5.二次求和(点分治)相关推荐

  1. CCF201803-5 二次求和(100分题解链接)

    试题编号: 201803-5 试题名称: 二次求和 时间限制: 10.0s 内存限制: 512.0MB 问题描述: 问题描述 给一棵 n 个节点的树,用 1 到 n 的整数表示.每个节点上有一个整数权 ...

  2. C++数据结构和算法2 栈 双端/队列 冒泡选择插入归并快排 二三分查找 二叉树 二叉搜索树 贪婪 分治 动态规划

    C++数据结构和算法2 栈 双端/队列 冒泡选择插入归并快排 二三分查找 二叉树 二叉搜索树 贪婪 分治 动态规划 博文末尾支持二维码赞赏哦 _ github 章3 Stack栈 和 队列Queue= ...

  3. 考研高数之无穷级数题型二:求和函数(题目讲解)

    和函数就是函数项无穷级数的和 在进行具体的题目讲解之前我想说一说个人做题的思路,经过做了一些题可以感受到,求和函数我们的目标就是把一个累加的级数变成用一个函数去表示他,那么我们的目标就很明确了,根据常 ...

  4. 【2018.3.24】模拟赛之二-ssl2546 求和【贪心】

    前言 依据十分的水,其实我依据是不想写的,依旧是老师要求的QAQ 正题 大意 有一个数S,通过然后在1-n自然数前加上"+"或"-"使其等于S,求最小的n 解题 ...

  5. 洛谷P4458 /loj#2512.[BJOI2018]链上二次求和(线段树)

    题面 传送门(loj) 传送门(洛谷) 题解 我果然是人傻常数大的典型啊-- 题解在这儿 //minamoto #include<bits/stdc++.h> #define R regi ...

  6. n平方的求和公式_n的二次方怎么求和?

    展开全部 n的二次方求和公式:(n+1)²=n²+2n+1: 同理(32313133353236313431303231363533e59b9ee7ad9431333363396434a+b)²=a² ...

  7. Stargazer的分治讲义

    cyk的分治讲义 文章目录 分治讲义 一.一般分治 1.序列分治 2.整体二分 3.CDQ分治 前置 引入 进入正题 4.二进制分组 5.线段树分治 二.树上分治 6.点分治 关于操作树上点分(论文) ...

  8. CCF CSP认证菜鸟刷题日志

    CCF CSP菜鸟刷题日志(c/c++) 本萌新写给自己看的,要是有大佬路过,请多多指教orz 立个flag:每日一更,至201903 9月15ccf csp,冲鸭! 今天(2019.8.18)起每天 ...

  9. CCF CSP认证考试题解目录

    由于本人的书籍<算法详解(C++11 语言描述)>已经出版,为了降低题解的维护难度,有关CCF CSP考试的所有题解的更新将全部在书籍的配套仓库进行,CSDN博客中不再进行任何题解的更新. ...

最新文章

  1. [自带避雷针]DropShadowEffect导致内存暴涨
  2. Android:安卓APP开发显示一个美女,安卓APP开发显示两个美女
  3. Redis Streams 介绍
  4. mysql online ddl
  5. GOF之行为型模式Ⅰ(重点)
  6. java基础工具VisualVM介绍与详细使用
  7. 计算机naf类型是什么,计算机系统结构课后习题答案
  8. Linux之深入理解anaconda使用
  9. [jzoj 4528] [GDOI2019模拟2019.3.26] 要换换名字 (最大权闭合子图)
  10. vnc远程桌面,细数五款使用感超强的vnc远程桌面软件
  11. 安卓蓝牙BluetoothBLE开发JDY-10M
  12. 干涉测量技术的应用_特殊工程的施工测量技术应用分享
  13. 使用Kali Linux系统生成木马病毒并实现远程控制计算机
  14. 现金流贴现法估值模型
  15. 基于Sentinel的高可用限流系统HASentinel设计及实现
  16. CDN流量是什么,怎么计算?
  17. Python 核心编程(第二版)——网络编程
  18. 【SCIR Lab】事件表示学习简述
  19. VS2005/SQL2005等原版镜像高速下载
  20. 解决Android 8.0和9.0无法获取SSID (unknow ssid)

热门文章

  1. 机器学习与气象数据_气象大数据与机器学习联合实验室 大数据和气象的“联姻”...
  2. 各纬度气候分布图_欧洲气候特征:以温带气候类型为主,是海洋性气候最显著的大洲...
  3. python 防止转义_python字符串前加r、f、u、l 的区别
  4. 怎么判断再一个局域网内一个ip被两台机器占用_交换机与 VLAN 到底是怎么来的...
  5. 7-2 最长公共子序列 (10 分)(思路加详解)
  6. linux多行变一列,多行转为一列
  7. [EDA] 第1章 EDA技术概述-潘松版
  8. 高等数学下-赵立军-北京大学出版社-题解-练习12.4
  9. LeetCode 559N叉树的最大深度-简单
  10. [蓝桥杯2015初赛]手链样式-思维+next_permutation枚举(好题)