[UOJ388]配对树


题解
贪心+线段树
首先如果我们知道了哪些点是关键点应该怎么搞
显然最小的匹配方案所有的边至多被经过一次
可以考虑每条边的贡献
因为我们要贡献尽量小
所以我们尽量让每条边经过的人尽量少
那么每条边被经过的条件就是一条边连接的两个节点的子树内关键点数量是奇数
然后我们可以直接考虑每条边会被多少个区间影响
一条边能被一个区间影响的条件就是这个区间的大小是偶数且这个区间内在这个边的深度较深的端点的子树内的点的数量有奇数个
也就是满足\(j-i=0(\mod 2) s_j - s_i=1(\mod 2)\)
这个东西可以用线段树维护
就记录下标为\(奇数/偶数\)且前缀和为奇数的数量
那么修改就是修改从这个点到\(m\)都翻转,并且可以直接计算了
然后可以用线段树合并来处理这个东西
但是我写了半天写不动><
就直接上dsu on tree来搞子树了==
代码
#include<vector>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
# define ls (now << 1)
# define rs (now << 1 | 1)
const int M = 100005 ;
const int N = 20 ;
const int mod = 998244353 ;
using namespace std ;
inline int read() {
char c = getchar() ; int x = 0 , w = 1 ;
while(c>'9'||c<'0') { if(c=='-') w = -1 ; c = getchar() ; }
while(c>='0'&&c<='9') { x = x*10+c-'0' ; c = getchar() ; }
return x*w ;
}
int n , m ;
int num , hea[M] ;
int cnt , ans , fdis[M] ;
int size[M] , bsn[M] ;
vector < int > vec[M] ;
struct E {
int nxt , to , dis ;
} edge[M * 2] ;
struct Node {
bool rev ;
int odd , even ;
} t[M * N] ;
inline void add_edge(int from , int to , int dis) {
edge[++num].nxt = hea[from] ;
edge[num].to = to ;
edge[num].dis = dis ;
hea[from] = num ;
}
inline void Flip(int now , int l , int r) {
t[now].even = r / 2 - (l - 1) / 2 - t[now].even ;
t[now].odd = (r + 1) / 2 - l / 2 - t[now].odd ;
t[now].rev ^= 1 ;
}
inline void pushup(int now) {
t[now].odd = t[ls].odd + t[rs].odd ;
t[now].even = t[ls].even + t[rs].even ;
}
inline void pushdown(int now , int l , int r) {
if(t[now].rev) {
t[now].rev = 0 ; int mid = (l + r) >> 1 ;
if(ls) Flip(ls , l , mid) ; if(rs) Flip(rs , mid + 1 , r) ;
}
}
void Change(int L , int R , int l , int r , int now) {
if(!now || l > R || r < L) return ;
if(l >= L && r <= R) { Flip(now , l , r) ; return ; }
pushdown(now , l , r) ;
int mid = (l + r) >> 1 ;
if(mid >= R) Change(L , R , l , mid , ls) ;
else if(mid < L) Change(L , R , mid + 1 , r , rs) ;
else { Change(L , mid , l , mid , ls) ; Change(mid + 1 , R , mid + 1 , r , rs) ; }
pushup(now) ;
}
inline void Calc(int u) {
int sum_e = m / 2 + 1 , sum_o = (m + 1) / 2 ;
ans = ((ans + 1LL * fdis[u] * t[1].even % mod * (sum_e - t[1].even) % mod) % mod + 1LL * fdis[u] * t[1].odd % mod * (sum_o - t[1].odd) % mod) % mod ;
}
void fdfs(int u , int father) {
size[u] = 1 ; int mx = -1 ;
for(int i = hea[u] ; i ; i = edge[i].nxt) {
int v = edge[i].to ; if(v == father) continue ;
fdis[v] = edge[i].dis ;
fdfs(v , u) ; size[u] += size[v] ;
if(size[v] > mx) mx = size[v] , bsn[u] = v ;
}
}
inline void Add(int u) {
for(int i = 0 , x , sz = vec[u].size() ; i < sz ; i ++) {
x = vec[u][i] ;
Change(x , m , 1 , m , 1) ;
}
}
void Update(int u , int father) {
Add(u) ;
for(int i = hea[u] ; i ; i = edge[i].nxt) {
int v = edge[i].to ;
if(v != father)
Update(v , u) ;
}
}
void dfs(int u , int father , bool Kp) {
for(int i = hea[u] ; i ; i = edge[i].nxt) {
int v = edge[i].to ;
if(v != father && v != bsn[u])
dfs(v , u , 0) ;
}
if(bsn[u]) dfs(bsn[u] , u , 1) ;
for(int i = hea[u] ; i ; i = edge[i].nxt) {
int v = edge[i].to ;
if(v != father && v != bsn[u])
Update(v , u) ;
}
Add(u) ;
if(fdis[u]) {
int sum_e = m / 2 + 1 , sum_o = (m + 1) / 2 ;
ans = (ans + 1LL * fdis[u] * t[1].even % mod * (sum_e - t[1].even) % mod) % mod ;
ans = (ans + 1LL * fdis[u] * t[1].odd % mod * (sum_o - t[1].odd) % mod) % mod ;
}
if(!Kp) Update(u , father) ;
}
int main() {
n = read() ; m = read() ;
for(int i = 1 , u , v , w ; i < n ; i ++) {
u = read() ; v = read() ; w = read() ;
add_edge(u , v , w) ; add_edge(v , u , w) ;
}
for(int i = 1 , x ; i <= m ; i ++) {
x = read() ;
vec[x].push_back(i) ;
}
fdfs(1 , 0) ;
dfs(1 , 0 , 1) ;
printf("%d\n",ans) ;
return 0 ;
}
[UOJ388]配对树的更多相关文章
- 【UOJ#388】【UNR#3】配对树(线段树,dsu on tree)
[UOJ#388][UNR#3]配对树(线段树,dsu on tree) 题面 UOJ 题解 考虑一个固定区间怎么计算答案,把这些点搞下来建树,然后\(dp\),不难发现一个点如果子树内能够匹配的话就 ...
- 【UOJ388】配对树(dsu on tree+线段树)
传送门 题意: 给出一颗含有\(n\)个结点的无根树,之后给出一个长度为\(m\)的序列,每个元素在\([1,n]\)之间. 现在序列中每个长度为偶数的区间的完成时间定义为树上最小配对方法中每对匹配点 ...
- [UOJ388]【UNR #3】配对树
uoj description 给你一棵\(n\)个节点的树以及一个长为\(m\)的序列,序列每个位置上的值\(\in[1,n]\),你需要求出把序列中所有长度为偶数的区间内所有数拿出来在树上以最小代 ...
- uoj#388. 【UNR #3】配对树(线段树合并)
传送门 先考虑一个贪心,对于一条边来说,如果当前这个序列中在它的子树中的元素个数为奇数个,那么这条边就会被一组匹配经过,否则就不会 考虑反证法,如果在这条边两边的元素个数都是偶数,那么至少有两组匹配经 ...
- C. 【UNR #3】配对树
题解: 首先可以贪心 于是问题可以等价成一条边被算当且仅当子树中个数为奇数个 题解的做法比较简单 考虑每条边,加入其子树内的点 然后为了保证区间长度为偶数 分成f0,0 f0,1 f1,0 f1,1即 ...
- Noip前的大抱佛脚----赛前任务
赛前任务 tags:任务清单 前言 现在xzy太弱了,而且他最近越来越弱了,天天被爆踩,天天被爆踩 题单不会在作业部落发布,所以可(yi)能(ding)会不及时更新 省选前的练习莫名其妙地成为了Noi ...
- UNR#3 Day1——[ 堆+ST表+复杂度分析 ][ 结论 ][ 线段树合并 ]
地址:http://uoj.ac/contest/45 第一题是鸽子固定器. 只会10分.按 s 从小到大排序,然后 dp[ i ][ j ][ k ] 表示前 i 个元素.已经选了 j 个.最小值所 ...
- GZOI 2017配对统计 树状数组
题目 https://www.luogu.com.cn/problem/P5677 分析 最开始读题的时候没有读的太懂,以为i是在选定区间内给的,实际上不是,这道题的意思应该是在l和r的区间内找出有多 ...
- 51Nod 1737 配对(树的重心)
http://www.51nod.com/onlineJudge/questionCode.html#!problemId=1737 题意: 思路: 树的重心. 树的重心就是其所以子树的最大的子树结点 ...
随机推荐
- C++ std::tr1::bind使用
1. 简述 同function函数相似.bind函数相同也能够实现相似于函数指针的功能.但却却比函数指针更加灵活.特别是函数指向类 的非静态成员函数时.std::tr1::function 能够对静态 ...
- apc smart UPS下使用apcupsd注意事项
公司的apc smart UPS安装有管理卡(似乎是AP-9631),server环境有FreeBSD.Windows Server.Linux(CentOS.Ubuntu) 实际使用中有例如以下问题 ...
- [javase学习笔记]-9.2 单继承与多重继承
这一节我们来看java中的单继承和多重继承. 在java语言中,支持的是单继承,不直接支持多继承,可是对C++中的多继承进行了改良. 那么什么是单继承和多继承呢? 单继承:一个子类仅仅能有一个直接父类 ...
- 微信小程序之 SideBar(侧栏分类)
项目目录: 模拟数据: utils / data.js function getSData() { var data = [ { "id": 1, "tree" ...
- axis client tomcat jsp调用最少jar
tomcat调用和main方法调用不同在于引入的jar不一样.tomcat引入的jar是全部lib以下的jar包,main是project引入的jar.假设直接进行公布lib下的全部jar都会引入到p ...
- leetcode:283. Move Zeroes(Java)解答
转载请注明出处:z_zhaojun的博客 原文地址:http://blog.csdn.net/u012975705/article/details/50493772 题目地址:https://leet ...
- spring实战笔记6---springMVC的请求过程
之前有一次在面试其中被问到了这个问题.当时说得不是非常清楚,有些细节的地方想不起来了.所以在这里从新回想和总结一下SpringMVC的起步.请求的运行过程. 在SpringMVC其中.跟请求(Requ ...
- 《Java设计模式》之訪问者模式
訪问者模式是对象的行为模式.訪问者模式的目的是封装一些施加于某种数据结构元素之上的操作.一旦这些操作须要改动的话,接受这个操作的数据结构则能够保持不变. 分派的概念 变量被声明时的类型叫做变量的静态类 ...
- 转载 Url编码
http://www.cnblogs.com/artwl/archive/2012/03/07/2382848.html 混乱的URI编码 JavaScript中编码有三种方法:escape.enco ...
- 借助ltp语义分析提取特征,之后,文本生成
"""地点-哪里有做-业务-的(正规|靠谱)-公司?地点-做-业务-的(正规|靠谱)-公司(有哪些?|的联系方式是什么?|哪家口碑好值得信赖?)地点-做-业务-(怎么能省 ...