【BZOJ2599】[IOI2011]Race【点分治】

【题目链接】

点分治。

考虑经过点x的路径,对于x,用类似TreeDP的方法,记录no[d],表示路径长度为d时经过边最少的点的编号。

对于已经走过的子树,更新no。对于当前子树,遍历到一个点v,用depth[no[k - dis[v]]] + depth[v]更新答案。

注意给no清零时,用dfs姿势清零,这样做是O(n)的。如果直接用for或者memset,这样做是O(k)的,会TLE。

/* Telekinetic Forest Guard */
#include <cstdio>
#include <cstring>
#include <algorithm>

using namespace std;

const int maxn = 200005, maxk = 1000005, inf = 0x3f3f3f3f;

int n, k, head[maxn], cnt, dis[maxn], depth[maxn], no[maxk], mx[maxn], size[maxn];
int root, nsum, ans;
bool vis[maxn];

struct _edge {
	int v, w, next;
} g[maxn << 1];

inline int iread() {
	int f = 1, x = 0; char ch = getchar();
	for(; ch < '0' || ch > '9'; ch = getchar()) f = ch == '-' ? -1 : 1;
	for(; ch >= '0' && ch <= '9'; ch = getchar()) x = x * 10 + ch - '0';
	return f * x;
}

inline void add(int u, int v, int w) {
	g[cnt] = (_edge){v, w, head[u]};
	head[u] = cnt++;
}

inline void getroot(int x, int f) {
	size[x] = 1; mx[x] = 0;
	for(int i = head[x]; ~i; i = g[i].next) if(!vis[g[i].v] && g[i].v ^ f) {
		getroot(g[i].v, x);
		size[x] += size[g[i].v];
		mx[x] = max(mx[x], size[g[i].v]);
	}
	mx[x] = max(mx[x], nsum - size[x]);
	if(mx[x] < mx[root]) root = x;
}

inline void calc(int x, int f) {
	if(dis[x] > k) return;
	if(~no[k - dis[x]]) ans = min(ans, depth[no[k - dis[x]]] + depth[x]);
	for(int i = head[x]; ~i; i = g[i].next) if(!vis[g[i].v] && g[i].v ^ f) {
		dis[g[i].v] = dis[x] + g[i].w;
		depth[g[i].v] = depth[x] + 1;
		calc(g[i].v, x);
	}
}

inline void update(int x, int f) {
	if(dis[x] > k) return;
	if(!~no[dis[x]] || depth[no[dis[x]]] > depth[x]) no[dis[x]] = x;
	for(int i = head[x]; ~i; i = g[i].next) if(!vis[g[i].v] && g[i].v ^ f)
		update(g[i].v, x);
}

inline void clear(int x, int f) {
	if(dis[x] > k) return;
	no[dis[x]] = -1;
	for(int i = head[x]; ~i; i = g[i].next) if(!vis[g[i].v] && g[i].v ^ f)
		clear(g[i].v, x);
}

inline void work(int x) {
	vis[x] = 1;
	no[0] = x; dis[x] = depth[x] = 0;
	for(int i = head[x]; ~i; i = g[i].next) if(!vis[g[i].v]) {
		dis[g[i].v] = g[i].w; depth[g[i].v] = 1;
		calc(g[i].v, x);
		update(g[i].v, x);
	}
	clear(x, 0);
	for(int i = head[x]; ~i; i = g[i].next) if(!vis[g[i].v]) {
		root = 0; nsum = size[g[i].v];
		getroot(g[i].v, 0);
		work(root);
	}
}

int main() {
	n = iread(); k = iread();
	for(int i = 1; i <= n; i++) head[i] = -1; cnt = 0;
	for(int i = 1; i < n; i++) {
		int u = iread(), v = iread(), w = iread(); u++; v++;
		add(u, v, w); add(v, u, w);
	}

	for(int i = 0; i <= k; i++) no[i] = -1;
	mx[0] = ans = inf;
	root = 0; nsum = n;
	getroot(1, 0);
	work(root);

	if(ans == inf) ans = -1;
	printf("%d\n", ans);
	return 0;
}
时间: 2024-11-05 10:20:23

【BZOJ2599】[IOI2011]Race【点分治】的相关文章

BZOJ 2599: [IOI2011]Race( 点分治 )

数据范围是N:20w, K100w. 点分治, 我们只需考虑经过当前树根的方案. K最大只有100w, 直接开个数组CNT[x]表示与当前树根距离为x的最少边数, 然后就可以对根的子树依次dfs并更新CNT数组和答案. ------------------------------------------------------------------------------------------ #include<bits/stdc++.h> using namespace std; typ

【BZOJ-2599】Race 点分治

2599: [IOI2011]Race Time Limit: 70 Sec  Memory Limit: 128 MBSubmit: 2590  Solved: 769[Submit][Status][Discuss] Description 给一棵树,每条边有权.求一条简单路径,权值和等于K,且边的数量最小.N <= 200000, K <= 1000000 Input 第一行 两个整数 n, k第二..n行 每行三个整数 表示一条无向边的两端和权值 (注意点的编号从0开始) Output

bzoj2599: [IOI2011]Race(点分治)

写了四五道点分治的题目了,算是比较理解点分治是什么东西了吧= = 点分治主要用来解决点对之间的问题的,比如距离为不大于K的点有多少对. 这道题要求距离等于K的点对中连接两点的最小边数. 那么其实道理是一样的.先找重心,然后先从重心开始求距离dis和边数num,更新ans,再从重心的儿子开始求得dis和num,减去这部分答案 因为这部分的答案中,从重心开始的两条链有重叠部分,所以要剪掉 基本算是模板题,但是减去儿子的答案的那部分还有双指针那里调了好久,所以还不算特别熟练.. PS跑了27秒慢到飞起

【点分治】【哈希表】bzoj2599 [IOI2011]Race

给nlog2n随便过的跪了,不得已弄了个哈希表伪装成nlogn(当然随便卡,好孩子不要学)…… 不过为啥哈希表的大小开小点就RE啊……?必须得超过数据范围一大截才行……谜 #include<cstdio> #include<algorithm> #include<cstring> using namespace std; int f,c; inline void R(int &x){ c=0;f=1; for(;c<'0'||c>'9';c=getc

bzoj1758 [Wc2010]重建计划 &amp; bzoj2599 [IOI2011]Race

两题都是树分治. 1758这题可以二分答案avgvalue,因为avgvalue=Σv(e)/s,因此二分后只需要判断Σv(e)-s*avgvalue是否大于等于0,若大于等于0则调整二分下界,否则调整二分上界.假设一棵树树根为x,要求就是经过树根x的最大答案,不经过树根x的可以递归求解.假设B[i]为当前做到的一颗x的子树中的点到x的距离为i的最大权值,A[i]为之前已经做过的所有子数中的点到x的距离为i的最大权值(这里的权值是Σv(e)-i*avgvalue),那么对于当前子树的一个距离i,

[BZOJ2599][IOI2011]Race

试题描述 给一棵树,每条边有权.求一条简单路径,权值和等于K,且边的数量最小.N <= 200000, K <= 1000000 输入 第一行 两个整数 n, k第二..n行 每行三个整数 表示一条无向边的两端和权值 (注意点的编号从0开始) 输出 一个整数 表示最小边数量 如果不存在这样的路径 输出-1 输入示例 4 3 0 1 1 1 2 2 1 3 4 输出示例 2 数据规模及约定 见“试题描述” 题解 点分治裸题.我还调了半天TAT...好久没写什么都忘了... 每次找子树的中心往下递

IOI2011 Race [点分治]

题意 给一棵树,每条边有权.求一条简单路径,权值和等于 $K$,且边的数量最小. 点分治,求距离时带上经过边的数量即可.用的第一种写法(下面). 食用淀粉质注意事项 1. 统计子树内答案的两种写法: 跟树形dp一样将某子树与前面的子树合并   或者是   考虑所有子树的答案再容斥,减去不合法的一棵子树内答案. 2.好好写求重心,千万不要写假!!!假淀粉害死人 注意每次遍历先初始化$f[x]=0$,要有子树大小$S$. 1 #include <cstdio> 2 #include <cst

P4149 [IOI2011]Race 点分治

思路: 点分治 提交:5次 题解: 刚开始用排序+双指针写的,但是调了一晚上,总是有两个点过不了,第二天发现原因是排序时的\(cmp\)函数写错了:如果对于路径长度相同的,我们从小往大按边数排序,当双指针出现\(==k\)时,即我们应先左移右指针,否则答案可能会变劣(仔细想一想):若反着排序,应该先右移左指针. #include<bits/stdc++.h> #define R register int using namespace std; namespace Luitaryi { tem

【BZOJ2599】[IOI2011]Race 树的点分治

[BZOJ2599][IOI2011]Race Description 给一棵树,每条边有权.求一条简单路径,权值和等于K,且边的数量最小.N <= 200000, K <= 1000000 Input 第一行 两个整数 n, k第二..n行 每行三个整数 表示一条无向边的两端和权值 (注意点的编号从0开始) Output 一个整数 表示最小边数量 如果不存在这样的路径 输出-1 Sample Input 4 3 0 1 1 1 2 2 1 3 4 Sample Output 2 题解:本题大