ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

P4149 [IOI2011]Race

2020-09-06 08:01:42  阅读:310  来源: 互联网

标签:Race int IOI2011 路径 edge P4149 ans now dis


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 的代码好短啊,自己拉行实锤了

标签:Race,int,IOI2011,路径,edge,P4149,ans,now,dis
来源: https://www.cnblogs.com/genshy/p/13617572.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有