zoukankan      html  css  js  c++  java
  • 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% 的数据,保证(1leq nleq 2 imes10^5)(1leq k,w_ileq 10^6),(0leq 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
    ",-1);//路径边数比 n 还大,直接判断无解
        else printf("%lld
    ",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
    ");
        else printf("%d
    ", ans);
        return 0;   
    }
    

    Tethys 最可爱了

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

  • 相关阅读:
    Java NIO使用及原理分析(三) 分类: Java 2014-03-12 17:56 447人阅读 评论(0) 收藏
    Java NIO使用及原理分析(二) 分类: Java 2014-03-12 17:55 465人阅读 评论(0) 收藏
    Java NIO使用及原理分析 ( 一 ) 分类: Java 2014-03-12 17:52 637人阅读 评论(0) 收藏
    网络编程中的Socket详解---Socket重要参数详解 分类: Android 2014-03-11 16:25 2066人阅读 评论(0) 收藏
    网络编程中的Socket详解---Socket编程实践详解 分类: Java 2014-03-11 16:19 549人阅读 评论(0) 收藏
    网络编程中的Socket详解---Delayed Ack(Ack确认延迟) && Nagle Algorithm(纳格算法) 分类: Java 2014-03-11 15:46 2316人阅读 评论(0) 收藏
    关于ThreadLocal的一道面试题(酷我公司) 分类: Android 2014-03-04 11:33 2124人阅读 评论(5) 收藏
    Java中的并发库学习总结 分类: Java 2014-03-03 15:12 3115人阅读 评论(3) 收藏
    深入解析Java中volatile关键字的作用 分类: Java 2014-03-03 13:47 1441人阅读 评论(3) 收藏
    Android源码的git下载地址 分类: Android 2014-02-27 15:26 1111人阅读 评论(0) 收藏
  • 原文地址:https://www.cnblogs.com/genshy/p/13617572.html
Copyright © 2011-2022 走看看