bzoj 2599(点分治)

2599: [IOI2011]Race

Time Limit: 70 Sec  Memory Limit: 128 MB
Submit: 3642  Solved: 1081
[Submit][Status][Discuss]

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

/*
开一个100W的数组t,t[i]表示权值为i的路径最少边数
找到重心分成若干子树后, 得出一棵子树的所有点到根的权值和x,到根a条边,用t[k-x]+a更新答案,全部查询完后
然后再用所有a更新t[x]
这样可以保证不出现点分治中的不合法情况
把一棵树的所有子树搞完后再遍历所有子树恢复T数组,如果用memset应该会比较慢
*/
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>

#define inf 1000000000
#define maxn 1000005
#define maxx 200005

using namespace std;

int n,K,cnt,sum,root,ans,x,y,z;
int tot[maxn],head[maxx],son[maxx],f[maxx],dis[maxx],d[maxx];
bool vis[maxx];
struct edge
{
    int to,next,w;
}e[maxx<<1];

inline int read()
{
    int x=0,f=1;char ch=getchar();
    while(ch<‘0‘||ch>‘9‘){if(ch==‘-‘)f=-1;ch=getchar();}
    while(ch>=‘0‘&&ch<=‘9‘){x=x*10+ch-‘0‘;ch=getchar();}
    return x*f;
}

void add(int u,int v,int w)
{
    e[++cnt].to=v;e[cnt].next=head[u];head[u]=cnt;e[cnt].w=w;
}

void get_root(int now,int fa)
{
    son[now]=1;f[now]=0;
    for(int i=head[now];i;i=e[i].next)
    {
        int v=e[i].to;
        if(v!=fa&&!vis[v])
        {
            get_root(v,now);
            son[now]+=son[v];f[now]=max(f[now],son[v]);
        }
    }
    f[now]=max(f[now],sum-son[now]);
    if(f[now]<f[root]) root=now;
}

void cal(int now,int fa)
{
    if(dis[now]<=K) ans=min(ans,d[now]+tot[K-dis[now]]);
    for(int i=head[now];i;i=e[i].next)
    {
        int v=e[i].to;
        if(!vis[v]&&v!=fa)
        {
            d[v]=d[now]+1;
            dis[v]=dis[now]+e[i].w;
            cal(v,now);
        }
    }
}

void updata(int now,int fa,int flag)
{
    if(dis[now]<=K)
    {
        if(flag) tot[dis[now]]=min(tot[dis[now]],d[now]);
        else tot[dis[now]]=inf;
    }
    for(int i=head[now];i;i=e[i].next)
    {
        int v=e[i].to;
        if(!vis[v]&&v!=fa)
          updata(v,now,flag);
    }
}

void work(int now)
{
    vis[now]=1;tot[0]=0;
    for(int i=head[now];i;i=e[i].next)
    {
        int v=e[i].to;
        if(!vis[v])
        {
            d[v]=1;dis[v]=e[i].w;
            cal(v,0);updata(v,0,1);
        }
    }
    for(int i=head[now];i;i=e[i].next)
    {
        int v=e[i].to;
        if(!vis[v])
          updata(v,0,0);//去掉重心之后要重新统计
    }
    for(int i=head[now];i;i=e[i].next)
    {
        int v=e[i].to;
        if(!vis[v])
        {
            root=0;sum=son[v];
            get_root(v,0);
            work(root);
        }
    }
}

int main()
{
    n=read();K=read();
    for(int i=1;i<=K;i++)tot[i]=n;
    for(int i=1;i<n;i++)
    {
        x=read();y=read();z=read();
        x++;y++;
        add(x,y,z);add(y,x,z);
    }
    ans=sum=f[0]=n;get_root(1,0);
    work(root);
    if(ans!=n)printf("%d\n",ans);
    else puts("-1");
    return 0;
}
时间: 2024-12-10 18:50:03

bzoj 2599(点分治)的相关文章

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(树分治)

题目链接:http://61.187.179.132/JudgeOnline/problem.php?id=2599 题意:给一棵树,每条边有权.求一条路径,权值和等于K,且边的数量最小. 题意:每次找到当前树的重心作为树根,查找通过当前树根的路径. 1 #include<algorithm> 2 #include<cstdio> 3 #include<cmath> 4 #include<cstring> 5 #include<iostream>

bzoj 2599 [IOI2011]Race (点分治)

[题意] 问树中长为k的路径中包含边数最少的路径所包含的边数. [思路] 统计经过根的路径.假设当前枚举到根的第S个子树,若x属于S子树,则有: ans<-dep[x]+min{ dep[y] },y属于前S-1个子树,dis[x]<=K 所以只需要用一个数组t[len]记录前S-1棵子树中长度为len的最少边数即可.t只用开到K的最大值. 然后分治处理子树. [代码] 1 #include<set> 2 #include<cmath> 3 #include<qu

bzoj 2599 [IOI2011]Race 未调完_点分治

Code: // luogu-judger-enable-o2 // luogu-judger-enable-o2 #include <bits/stdc++.h> #define setIO(s) freopen(s".in","r",stdin) #define maxn 1000000 #define inf 0x7f7f7f using namespace std; int hd[maxn],to[maxn],nex[maxn],val[maxn

【刷题】BZOJ 2599 [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 Solution 点分治 考虑如何计算答案,有一个节点,我

【BZOJ 4059】 (分治暴力|扫描线+线段树)

4059: [Cerc2012]Non-boring sequences Time Limit: 10 Sec  Memory Limit: 128 MBSubmit: 632  Solved: 227 Description 我们害怕把这道题题面搞得太无聊了,所以我们决定让这题超短.一个序列被称为是不无聊的,仅当它的每个连续子序列存在一个独一无二的数字,即每个子序列里至少存在一个数字只出现一次.给定一个整数序列,请你判断它是不是不无聊的. Input 第一行一个正整数T,表示有T组数据.每组数

[BZOJ 3262] 陌上花开 分治

题意 给定三个序列 $S = \left\{ s_1, s_2, ..., s_n \right\}$ , $C = \left\{ c_1, c_2, ..., c_n \right\}$ , $M = \left\{ m_1, m_2, ..., m_n \right\}$ . 对任意 $i$ , 求 $level_i = \sum_{j \ne i, 1 \le j \le n}[s_i \ge s_j][c_i \ge c_j][m_i \ge m_j]$ . 分析 宏观上, 将第一维按

BZOJ 4025 二分图 分治+并查集

题目大意:给定一张n个点的图,有m条边,T个时间段,每条边只存在于(st,ed]这些时间段,求每个时间段内这个图是否是二分图 分治并查集大法好 定义Solve(x,y,E)为当前处理的区间为[x,y],E为所有存在时间为[x,y]的子集的边的集合 那么对于E中的每一条边(u,v),讨论: 若当前边的存在时间为[x,y],则在并查集上判断是否出现奇环 如果出现,[x,y]内的所有时刻都一定不是二分图,输出答案即可 如果不出现,在并查集中连接(u,v) 否则判断存在时间和mid的关系讨论扔进左区间还

BZOJ 3897 Power 分治

题目大意 一个人打工,每一天有一个收益,使用一点体力可以获得一份收益,每天回复固定的体力,体力有一个上限,超出之后就不回复了.问最多可以获得多少收益. 思路 分治策略:Solve(l, r, st, ed)表示第l天到第r天,初始体力为st,结束体力为ed的最大收益.显然,我们想让这个区间中的收益最大的那天干的越多越好,于是分情况讨论: 如果从一开始就休息,一直休息到收益最大的那天,没有达到体力的上限,那就一直休息. 如果达到了体力上限,递归左半部分,将多余的体力用掉. 右边也是同理,如果在收益