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 ...
随机推荐
- drf相关问题
drf自定义用户认证: 登录默认 使用django的ModelBackend,对用户名和密码进行验证.但我们平时登录网站时除了用户名也可以用邮箱或手机进行登录,这就需要我们自己扩展backend 一. ...
- 修改CentOS6.5默认主机名(root下操作)
使用CentOS6.5官方镜像安装完毕之后,默认的主机名为localhost,不便管理,我们需要根据实际情况修改. 此处我准备讲默认的主机名 localhost 改为 comex01-ct65 第一步 ...
- 微信小程序onLaunch、onLoad执行生命周期
原文转载自:微信小程序onLaunch.onLoad执行生命周期 1.需求:先执行App的onLaunch添加验证权限等,再执行Page里的onLoad. 2.问题:还没有等onLaunch执行完成, ...
- Nginx与前端开发
Nginx与Node.js "Nginx是一款轻量级的HTTP服务器,采用事件驱动的异步非阻塞处理方式框架,这让其具有极好的IO性能,时常用于服务端的反向代理和负载均衡." 作为前 ...
- Java基础 -- 持有对象(容器)
一 容器的用途 如果对象的数量与生命周期都是固定的,自然我们也就不需要很复杂的数据结构. 我们可以通过创建引用来持有对象,如 Class clazz; 也可以通过数组来持有多个对象,如 Class[] ...
- Python与微信——itchat包
目录 itchat itchat 一安装itchat pip install itchat pip install echarts-python 二登陆并向文件传输助手发消息 import itcha ...
- 实现select联动效果,数据从后台获取
效果如下: 当type值选择完后,amount值会自动相应填入. 1. 从后台获取数据,为一个数组,里面包含多个对象. <select id="scholarshipTypeSelec ...
- Python 几个常见函数
本文主要总结常见的函数知识点. 1.zip函数 用来并行迭代,可以把两个序列并在一起,然后返回一个元组的列表 names = ['Ann','Jame','Anla'] ages = [11,12,1 ...
- 树莓派的系统安装,并且利用网线直连 Mac 进行配置
最近单位给了我一个新的树莓派3B+让我自己玩.下面是我记录的我如何安装 Raspbian Stretch Lite 系统,然后如何成功不用独立显示屏而利用 MacBook 对其进行配置. 安装 Ras ...
- [Leetcode] 01 Matrix
问题: https://leetcode.com/problems/01-matrix/#/description 基本思路:广度优先遍历,根据所有最短距离为N的格找到所有距离为N+1的格,直到所有的 ...