bzoj 3697
题目描述:这里
发现还是点对之间的问题,于是还是上点分
只不过是怎么做的问题
首先对每条边边权给成1和-1(即把原来边权为0的边边权改为-1),那么合法的路径总权值一定为0!
还是将路径分为经过当前根节点和不经过当前根节点的,对不经过当前点的递归处理
那么我们讨论经过当前根节点的路径算法即可
可以发现,如果一条路径经过当前根节点,那么当前根节点可以将这条路径分成两部分,且这两部分权值互为相反数!
(这是很显然的,如果不互为相反数的话那么加起来肯定不是0啊)
接下来我们分析中转站的问题:
其实中转站的含义就是在路径上找到一个点,使得这个点左右两部分权值和均为0
那么这里需要dp处理
我们用两个dp数组处理,分别为$f[i][0/1],g[i][0/1]$,其中g是f的前缀和,f[i][0]表示长度为i的路径,0/1用来记录路径上是否存在距离为i的节点
之所以要记录这一点,是因为起点和终点不能作为休息站,所以当某个权值第一次出现时我们要记录在f[i][0]中,在另半条路径中找到g[-i][1]的点数才能保证休息站的存在
那么答案就由几部分组成:
第一:之前的几棵子树中某一个点到根的路径权值为0,当前子树中某一个点到根的路径权值也为0,那么方案数就是$f[0][0]*g[0][0]$
第二:之前的几棵子树中某一个点到根的路径权值为i,当前子树中某一个点权值为-i,那么答案即为$f[i][1]*g[-i][0]+f[i][0]*g[-i][1]+f[i][1]*g[i][-1]$(注意这里的i不一定是正值!!!)
还是比较好理解,因为不管这个权值之前是否出现过,只需互为相反数然后累计即可,因为左右一定能找到一个合法的位置
但是注意一点:
我们认为根节点的g[0][0]初值为1,这样在累计以根节点为端点的情况的时候是有效的,但对于根节点恰好是端点而且根节点被迫成为休息站的情况是有问题的,这种情况是不合法的,所以事实上累计两边权值都是0的时候应该用的是$f[0][0]*(g[0][0]-1)$!!
然后算完把f累计到g里做个前缀和就可以了
- #include <cstdio>
- #include <cmath>
- #include <cstring>
- #include <cstdlib>
- #include <iostream>
- #include <algorithm>
- #include <queue>
- #include <stack>
- #define ll long long
- using namespace std;
- const int inf=0x3f3f3f3f;
- struct Edge
- {
- int next;
- int to;
- int val;
- }edge[];
- int head[];
- int siz[];
- int maxp[];
- bool vis[];
- int dis[];
- int dep[];
- int has[];
- ll g[][];
- ll f[][];
- ll ans=;
- int maxdep=;
- int cnt=;
- int s,rt;
- int n;
- void init()
- {
- memset(head,-,sizeof(head));
- cnt=;
- }
- void add(int l,int r,int w)
- {
- edge[cnt].next=head[l];
- edge[cnt].to=r;
- edge[cnt].val=w;
- head[l]=cnt++;
- }
- void get_rt(int x,int fa)
- {
- siz[x]=,maxp[x]=;
- for(int i=head[x];i!=-;i=edge[i].next)
- {
- int to=edge[i].to;
- if(to==fa||vis[to])continue;
- get_rt(to,x);
- siz[x]+=siz[to];
- maxp[x]=max(maxp[x],siz[to]);
- }
- maxp[x]=max(maxp[x],s-siz[x]);
- if(maxp[x]<maxp[rt])rt=x;
- }
- void get_dis(int x,int fa)
- {
- maxdep=max(maxdep,dep[x]);
- if(has[dis[x]])f[dis[x]][]++;
- else f[dis[x]][]++;
- has[dis[x]]++;
- for(int i=head[x];i!=-;i=edge[i].next)
- {
- int to=edge[i].to;
- if(vis[to]||to==fa)continue;
- dep[to]=dep[x]+;
- dis[to]=dis[x]+edge[i].val;
- get_dis(to,x);
- }
- has[dis[x]]--;
- }
- void solve(int x)
- {
- vis[x]=;
- g[n][]=;
- int maxx=;
- for(int i=head[x];i!=-;i=edge[i].next)
- {
- int to=edge[i].to;
- if(vis[to])continue;
- dis[to]=n+edge[i].val,maxdep=,dep[to]=;
- get_dis(to,);
- maxx=max(maxx,maxdep);
- ans+=(g[n][]-)*f[n][];
- for(int j=-maxdep;j<=maxdep;j++)ans+=g[n-j][]*f[n+j][]+g[n-j][]*f[n+j][]+g[n-j][]*f[n+j][];
- for(int j=n-maxdep;j<=n+maxdep;j++)
- {
- g[j][]+=f[j][];
- g[j][]+=f[j][];
- f[j][]=f[j][]=;
- }
- }
- for(int j=n-maxx;j<=n+maxx;j++)g[j][]=g[j][]=;
- for(int i=head[x];i!=-;i=edge[i].next)
- {
- int to=edge[i].to;
- if(vis[to])continue;
- rt=,maxp[rt]=inf,s=siz[to];
- get_rt(to,);
- solve(rt);
- }
- }
- int main()
- {
- scanf("%d",&n);
- init();
- for(int i=;i<n;i++)
- {
- int x,y,z;
- scanf("%d%d%d",&x,&y,&z);
- if(!z)z--;
- add(x,y,z),add(y,x,z);
- }
- maxp[rt]=s=n;
- get_rt(,);
- solve(rt);
- printf("%lld\n",ans);
- return ;
- }
bzoj 3697的更多相关文章
- [BZOJ 3697] 采药人的路径
[题目链接] https://www.lydsy.com/JudgeOnline/problem.php?id=3697 [算法] 首先 , 将黑色的边变成1 ,白色的边变成-1 那么 , 问题就转化 ...
- BZOJ 3697: 采药人的路径 [点分治] [我想上化学课]
传送门 题意: 路径有$-1,1$两种权值,求有多少路径满足权值和为$0$且有一个点将路径分成权值和为$0$的两段 第四节课本来想去上化学,然后快上课了这道题还没调出来.....可恶我想上化学 昨天两 ...
- 【BZOJ 3697】采药人的路径
题目链接: TP 题解: 调了好久233. 大概想一想就是树分,然后考虑这样路径(u,v)的特征,以根节点(root)切开,u到root的阴阳差值,和v到root巧合互为相反数,然后考虑要有一个点可作 ...
- bzoj 3697: 采药人的路径【点分治】
点分治,设当前处理的块的重心为rt,预处理出每个子树中f[v][0/1]表示组合出.没组合出一对值v的链数(从当前儿子出发的链),能组合出一对v值就是可以有一个休息点 然后对于rt,经过rt且合法的路 ...
- BZOJ 3697/3127 采药人的路径 (点分治)
题目大意: 从前有一棵无向树,树上边权均为$0$或$1$,有一个采药人,他认为如果一条路径上边权为$0$和$1$的边数量相等,那么这条路径阴阳平衡.他想寻找一条合法的采药路径,保证阴阳平衡.然后他发现 ...
- BZOJ 3697: 采药人的路径 点分治
好久不做点分治的题了,正好在联赛之前抓紧复习一下. 先把边权为 $0$ 的置为 $-1$.定义几个状态:$f[dis][0/1],g[dis][0/1]$ 其中 $f$ 代表在当前遍历的子树内的答案. ...
- 【BZOJ】【3697】采药人的路径&【3127】【USACO2013 Open】Yin and Yang
点分治 Orz hzwer 倒是比较好想到点分治……然而在方案统计这里,我犯了两个错误…… 1.我比较傻逼的想的是:通过儿子来更新父亲,也就是统计以x为根的子树中xxxx的路径有多少条……这样转移. ...
- BZOJ3697: 采药人的路径
传送门 不是那么裸的点分治. $f[i][0/1]$表示当前节点的一个子树中总权值和为$i$,且是否存在一个前缀使得其前缀和为$i$ $g[i][0/1]$表示当前节点的已遍历过的子树,其余一样. 对 ...
- NOI前总结:点分治
点分治: 点分治的题目基本一样,都是路径计数. 其复杂度的保证是依靠 $O(n)$ 找重心的,每一次至少将问题规模减小为原先的$1/2$. 找重心我喜欢$BFS$防止爆栈. int Root(int ...
随机推荐
- Frame Interpolation
对于视频网站.电视厂商以及进行视频压制的用户来说,改变视频的帧率算是一个比较常见的需求.视频网站改变帧率主要是为了向不同级别的网站用户提供差异化服务:电视厂商则是以提供更好的显示效果作为电视的卖点:对 ...
- [Luogu 4245] 任意模数NTT
Description 给定 \(2\) 个多项式 \(F(x), G(x)\),请求出 \(F(x) * G(x)\). 系数对 \(p\) 取模,且不保证 \(p\) 可以分解成 \(p = a ...
- Mergeable Stack ZOJ - 4016(list)
ZOJ - 4016 vector又T又M list是以链表的方式存储的 是一个双向链表 元素转移操作中,不能一个个遍历加入到s中,list独有的splic函数可以在常数时间内实现合并(并删除源lis ...
- 【THUSC2017】【LOJ2977】巧克力 斯坦纳树
题目大意 有一个网格(或者你可以认为这是一个图),每个点都有颜色 \(c_i\) 和点权 \(a_i\). 求最小的连通块,满足这个连通块内点的颜色数量 \(\geq k\).在满足点数最少的前提下, ...
- luogu5290 春节十二响
题目链接 思路 先考虑一条链的情况怎么做. 因为只有两个子树,并且两个子树都是链.所以可以把这两条链找出来,然后\(sort\)一下.合并起来. 然后推广到树上 对于每一棵树都可以按照和上面同样的方法 ...
- laravel 配置MySQL读写分离
前言:说到应对大流量.高并发的解决方案的时候,总会有这样的回答,如:读写分离,主从复制...等,数据库层今天先不讨论,那么今天我们就来看看怎么在应用层实现读写分离. 框架:laravel5.7(所有配 ...
- Chart控件,鼠标选择区域,可以局部放大缩小
例子: 代码设置部分: chartArea1.CursorX.Interval = 0D; chartArea1.CursorX.IntervalOffsetType = System.Windows ...
- Python高级笔记(一) -- GIL (全局解释器锁)
1. GIL概念 (cpython历史遗留问题) 概念? 对Python多线程的影响? 编写一个多线程抓取网页的程序? 阐述多线程抓取程序是否比单线程性能有提升, 并解释原因. GIL:全局解释器锁, ...
- 阿里云ACA主要内容
阿里云 ACA,云计算助理工程师,是阿里云使用的一个入门级别课程.内容比较浅显,但都很很有意思的知识.课程的内容主要有7门,具体见下图: 课程的学习方式是视频+实验 先学习视频 再实际操作.阿里云为每 ...
- 网络协议 HTTP入门学习
概述 Web 的诞生,源于三大技术的诞生,它们都是当年 Web 之父 Tim Berners-Lee 自己 开发的,世界上第一个网站诞生的时间是 1991 年,三大技术的诞生也就是在此之前的不久: 1 ...