P4149 [IOI2011]Race

Link

题目描述

给一棵树,每条边有权。求一条简单路径,权值和等于 \(k\) ,且边的数量最小。

输入格式

第一行包含两个整数 \(n,k\),表示树的大小与要求找到的路径的边权和。

接下来 \(n−1\) 行,每行三个整数 \(u_i\), \(v_i\) ,\(w_i\),代表有一条连接 \(u_i\) 与 \(v_i\),边权为 \(w_i\) 的无向边。

注意:点从 0 开始编号

输出格式

输出一个整数,表示最小边数量。

如果不存在这样的路径,输出 −1。

输入输出样例

输入 #1

4 3
0 1 1
1 2 2
1 3 4

输出 #1

2

说明/提示

对于 100% 的数据,保证\(1\leq n\leq 2\times10^5\),\(1\leq k,w_i\leq 10^6\),\(0\leq u_i,v_i<n\)。

题解

庆祝一下,人生第一道 IOI 的题(之前写的题都太水了,不算

第一个条件求树上长度为 \(k\) 的路径,一眼就能断定是点分治没跑了(好像学过点分治的都一眼秒了)。

第二个条件要求经过的边的数量最少,就记录一下每条路径经过的边的数量(感觉好水啊)。

之后暴力匹配长度为 \(k\) 的路径,看能否更新答案,如果经过的边的数量更少,就可以来更新答案。

其他的直接套点分治的模板就能 A 了此题啦。

至于怎么匹配,这里有两种写法:

  • 双指针法(常数比较小的写法)
  • 桶排序法(好写但注意的点比较多)

双指针法Code:

#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
#define int long long
const int N = 2e5+10;
int n,m,k,tot,cnt,root,sum_siz,u,v,w,ans = 2147483647;//ans 初值赋大一些
int head[N],siz[N],max_siz[N],dis[N];
bool vis[N];
struct bian
{
    int to,net,w;
}e[N<<1];
struct node
{
    int d,num,who;
    node() {}
    node(int a,int b,int c){num = a; d = b; who = c;}
}a[N<<1];
inline int read()
{
    int s = 0,w = 1; char ch = getchar();
    while(ch < '0' || ch > '9'){if(ch == '-') w = -1; ch = getchar();}
    while(ch >= '0' && ch <= '9'){s =s * 10+ch - '0'; ch = getchar();}
    return s * w;
}
bool comp(node a,node b)//按边的长度排序,如果长度相同按经过的边的数量排序
{
	if(a.d == b.d) return a.num < b.num;
	return a.d < b.d;
}
void add(int x,int y,int w)
{
    e[++tot].w = w;
    e[tot].to = y;
    e[tot].net = head[x];
    head[x] = tot;
}
void get_root(int x,int fa)//找重心
{
    siz[x] = 1; max_siz[x] = 0;
    for(int i = head[x]; i; i = e[i].net)
    {
        int to = e[i].to;
        if(to == fa || vis[to]) continue;
        get_root(to,x);
        siz[x] += siz[to];
        max_siz[x] = max(max_siz[x],siz[to]);
    }
    max_siz[x] = max(max_siz[x],sum_siz-siz[x]);
    if(max_siz[x] < max_siz[root]) root = x;
}
void get_dis(int x,int fa,int num,int who)//找距离,num 记录经过的边的数量,who 记录他来自那颗子树
{
    a[++cnt] = node(num,dis[x],who);//把路径信息存入结构体中
    for(int i = head[x]; i; i = e[i].net)
    {
        int to = e[i].to;
        if(vis[to] || to == fa) continue;
        dis[to] = dis[x] + e[i].w;
        get_dis(to,x,num+1,who);
    }
}
int find(int x)//二分函数
{
    int L = 1, R = cnt, ans = 0;
    while(L <= R)
    {
        int mid = (L + R)>>1;
        if(a[mid].d >= x) 
        {
            ans = mid;
            R = mid - 1;
        }
        else L = mid + 1;
    }
    return ans;
}
int calc(int x,int d)
{
    cnt = 0; dis[x] = 0;
    int res = 2147483647;
    for(int i = head[x]; i; i = e[i].net)//找出所有子树中的路径
    {
        int to = e[i].to;
        if(vis[to]) continue;
        dis[to] = dis[x] + e[i].w;
        get_dis(to,x,1,to);
    }
    a[++cnt] = node(0,0,0);
    sort(a+1,a+cnt+1,comp);
    int L = 1, R = cnt;//暴力双指针匹配
    while(L <= cnt && a[L].d + a[R].d < k) L++;
    while(L <= cnt)
    {
        if(k - a[L].d < a[L].d) break;
        int tmp = find(k-a[L].d);
        while(a[L].d + a[tmp].d == k && a[L].who == a[tmp].who) tmp++;//路径不能来自同一颗子树
        if(a[L].d  + a[tmp].d == k) 
	{
	     res = min(res,a[L].num+a[tmp].num);
	}
        L++;
    } 
    return res;
}
void slove(int x)//点分治
{
    ans = min(ans,calc(x,0));//统计一下这个点的答案·
    vis[x] = 1;
    for(int i = head[x]; i; i = e[i].net)
    {
        int to = e[i].to;
        if(vis[to]) continue;//刚开始这里写挂了,嘤嘤嘤
        max_siz[0] = n; sum_siz = siz[to]; root = 0;
        get_root(to,0); slove(root);
    }
}
signed main()
{
    n = read(); k = read();
    for(int i = 1; i <= n-1; i++)
    {
        u = read() + 1; v = read() + 1; w = read();
        add(u,v,w); add(v,u,w);
    }
    max_siz[0] = sum_siz = n; root = 0;
    get_root(1,0); slove(root);
    if(ans > n) printf("%d\n",-1);//路径边数比 n 还大,直接判断无解
    else printf("%lld\n",ans);
    return 0; 
}

我的桶排序的方法写挂了,就从可爱的 Tethys 那里扒了一份(感性理解一下吧

Code:

#include<bits/stdc++.h>
using namespace std;
const int N = 2e5 + 5, INF = 1e9 + 5;
struct ed{
    int u, v, nxt, w;
}edge[N << 1];
int n, m, cnt, k, a[N], head[N], siz[N], dis[N], mxsiz[N], root, tot, sum, ans, tt[1000006];
bool vis[N];//tt[i] 数组 表示长度为 i 的路径经过的边的数量最少是多少
void add(int u, int v, int val){
    edge[++ cnt].u = u;
    edge[cnt].v = v;
    edge[cnt].w = val;
    edge[cnt].nxt = head[u];
    head[u] = cnt;
}
void get_root(int now, int fa){
    siz[now] = 1; mxsiz[now] = 0;
    for(int i = head[now]; i; i = edge[i].nxt){
        int to = edge[i].v;
        if(to == fa || vis[to]) continue;
        get_root(to, now);
        siz[now] += siz[to];
        mxsiz[now] = max(mxsiz[now], siz[to]);
    }
    mxsiz[now] = max(mxsiz[now], sum - siz[now]);
    if(mxsiz[now] < mxsiz[root]) root = now;
}
void get_dis(int now, int fa, int x, int y){
    if(x > k) return ;//大于k的话直接返回,防止爆桶
    dis[++ tot] = x;   
    a[tot] = y;
    for(int i = head[now]; i; i = edge[i].nxt){
        int to = edge[i].v;
        if(to == fa || vis[to]) continue;
        get_dis(to, now, x + edge[i].w, y + 1);
    }
}
void calc(int now){
    tt[0] = tot = 0;
    for(int i = head[now]; i; i = edge[i].nxt){
        int to = edge[i].v; 
        if(vis[to]) continue;
        int l = tot; 
        get_dis(to, now, edge[i].w, 1);
        for(int j = l + 1; j <= tot; j ++) ans = min(ans, tt[k - dis[j]] + a[j]); //先和别的子树中的边匹配来更新答案
        for(int j = l + 1; j <= tot; j ++) tt[dis[j]] = min(tt[dis[j]], a[j]);//更新一下tt数组
    }
    for(int i = 1; i <= tot; i ++) tt[dis[i]] = INF;//每次计算完一个点的贡献都要把tt 数组赋为极大值
}
void solve(int now){
    vis[now] = 1;
    calc(now);
    for(int i = head[now]; i; i = edge[i].nxt){
        int to = edge[i].v;
        if(vis[to]) continue;
        mxsiz[0] = INF; sum = siz[to]; root = 0;
        get_root(to, 0); solve(root);
    }
}
int main(){
    scanf("%d %d", &n, &k);
    ans = INF;  
    for(int i = 1, x, y, z; i <= n - 1; i ++){
        scanf("%d %d %d", &x, &y, &z);
        add(x + 1, y + 1, z); add(y + 1, x + 1, z);
    }    
    mxsiz[0] = INF; sum = n; root = 0;
    memset(tt, 0x3f, sizeof(tt));
    get_root(1, 0); solve(root);
    if(ans > n) printf("-1\n");
    else printf("%d\n", ans);
    return 0;   
}

Tethys 最可爱了

为什么 Tethys 的代码好短啊,自己拉行实锤了

上一篇:使用Promise并发限制


下一篇:625words可以走遍天下