[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。。。好久没写什么都忘了。。。

每次找子树的中心往下递归处理,合并的时候开一个大小为 k 的数组 Cnt[i] 记录边权和为 i 时所需的最少边的条数,转移也很简单。

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cmath>
#include <stack>
#include <vector>
#include <queue>
#include <cstring>
#include <string>
#include <map>
#include <set>
using namespace std;

const int BufferSize = 1 << 16;
char buffer[BufferSize], *Head, *Tail;
inline char Getchar() {
    if(Head == Tail) {
        int l = fread(buffer, 1, BufferSize, stdin);
        Tail = (Head = buffer) + l;
    }
    return *Head++;
}
int read() {
    int x = 0, f = 1; char c = Getchar();
    while(!isdigit(c)){ if(c == ‘-‘) f = -1; c = Getchar(); }
    while(isdigit(c)){ x = x * 10 + c - ‘0‘; c = Getchar(); }
    return x * f;
}

#define maxn 200010
#define maxm 400010
#define maxk 1000010
#define oo 2147483647
int n, m, k, head[maxn], next[maxm], to[maxm], dist[maxm];

void AddEdge(int a, int b, int c) {
	to[++m] = b; dist[m] = c; next[m] = head[a]; head[a] = m;
	swap(a, b);
	to[++m] = b; dist[m] = c; next[m] = head[a]; head[a] = m;
	return ;
}

bool vis[maxn];
int root, Siz, siz[maxn], f[maxn];
void getroot(int u, int fa) {
	siz[u] = 1; f[u] = 0;
	for(int e = head[u]; e; e = next[e]) if(to[e] != fa && !vis[to[e]]) {
		getroot(to[e], u);
		siz[u] += siz[to[e]];
		f[u] = max(f[u], siz[to[e]]);
	}
	f[u] = max(f[u], Siz - siz[u]);
	if(f[u] < f[root]) root = u;
	return ;
}
int ToT, d[maxn], dep[maxn], Cnt[maxk], mark[maxk], ans;
void dfs(int u, int fa, int dis, int depth) {
	d[++ToT] = dis; dep[ToT] = depth;
	for(int e = head[u]; e; e = next[e]) if(!vis[to[e]] && to[e] != fa && dis + dist[e] <= k)
		dfs(to[e], u, dis + dist[e], depth + 1);
	return ;
}
void solve(int u) {
//	printf("%d\n", u);
	vis[u] = 1;
	bool flag = 0;
	for(int e = head[u]; e; e = next[e]) if(!vis[to[e]]) {
		ToT = 0; dfs(to[e], u, dist[e], 1);
//		printf("%d: ", to[e]); for(int i = 1; i <= ToT; i++) printf("%d ", d[i]); putchar(‘\n‘);
		for(int i = 1; i <= ToT; i++) if(d[i] == k) ans = min(ans, dep[i]);
		if(flag) {
			for(int i = 1; i <= ToT; i++) if(d[i] < k && mark[k-d[i]] == u)
				ans = min(ans, dep[i] + Cnt[k-d[i]]);
		}
		for(int i = 1; i <= ToT; i++) if(d[i] < k) {
			if(mark[d[i]] != u) Cnt[d[i]] = dep[i], mark[d[i]] = u;
			else Cnt[d[i]] = min(Cnt[d[i]], dep[i]);
		}
		flag = 1;
	}
	for(int e = head[u]; e; e = next[e]) if(!vis[to[e]]) {
		f[0] = n + 1; Siz = siz[to[e]]; root = 0; getroot(to[e], u);
		solve(root);
	}
	return ;
}

int main() {
//	freopen("data.in", "r", stdin);
//	freopen("data.out", "w", stdout);
	n = read(); k = read();
	for(int i = 1; i < n; i++) {
		int a = read() + 1, b = read() + 1, c = read();
		AddEdge(a, b, c);
	}

	f[0] = n + 1; Siz = n; root = 0; getroot(1, 0);
	ans = oo;
	solve(root);

	if(ans < oo) printf("%d\n", ans);
	else puts("-1");

	return 0;
}

IOI 居然有这么水的题!难道是因为年份太早了?!

时间: 2024-11-09 13:38:53

[BZOJ2599][IOI2011]Race的相关文章

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

给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

bzoj2599: [IOI2011]Race(点分治)

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

【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 题解:本题大

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【点分治】

[题目链接] 点分治. 考虑经过点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> #i

bzoj2599 [ IOI2011] -- 点分治

令ans[i]表示权值和等于k的路径条数,然后点分治就可以了. 具体看代码. 代码: 1 #include<iostream> 2 #include<cstdio> 3 #include<cstring> 4 #include<algorithm> 5 #include<vector> 6 using namespace std; 7 #define N 200010 8 #define INF 2147483647 9 inline int M

点分治 [IOI2011]Race

BZOJ2599. 放板子.利用agc009-D 代码简洁了很多 1 #include <bits/stdc++.h> 2 #define N 200005 3 using namespace std; 4 int n,m,K,F[N][30],H[N],x,y,z,h[N],f[1000005],T,O,e[N],ans,d[N],g[N],t; 5 vector <int> a[N],b[N]; 6 void QH(int x,int fa){ 7 int y,z=-1; 8