IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 数据结构与算法 -> [ZJOI2010] 贪吃的老鼠(二分+差分+神仙建图网络流) -> 正文阅读

[数据结构与算法][ZJOI2010] 贪吃的老鼠(二分+差分+神仙建图网络流)

problem

luogu-P2570

solution

卧槽网络流尼玛神题

首先这个最小延长时间 T T T ,套路地考虑二分,将问题转化为判定性问题。

其次 n , m n,m n,m 和奶酪存在时间 [ l , r ] [l,r] [l,r] 的量级差很大,我们肯定会猜想一段时间内选择吃奶酪的老鼠是一样,套路地考虑离散化。

n n n 个奶酪的出现、消失时间放进时间轴 t i [ ] ti[] ti[] 中。

有序化,将时间轴割裂成若干段不相交且并集为全集的的小区间,一段时间一段时间地考虑。

像这种贪心贪不动, d p ? p dp\ p dp?p 不动的题目,我们就可以往网络流上靠了 。

其实是感觉是个网络流那它多半就是网络流。

现在就当大家已经想(猜)到这是道网络流了。

这道题问题就在于如何建立一个和原问题等价的网络流。

满足:任一时刻,一只老鼠最多吃一块奶酪,一块奶酪最多被一只老鼠吃。

因为网络流是同时多路径流,所以我们很难在网络流上面直观理解一对一。


本版块是对建图的描述。

刚开始,很容易想到。 s , t s,t s,t 源汇点, 1 ~ n 1\sim n 1n 的奶酪,源点 s s s 分别与这 n n n 块奶酪连边,容量为对应奶酪的大小 p p p

然后后面就开始 尼玛 神起来了。

  • 首先要将老鼠按吃的 速度从大到小 排序,最后面插个速度 0 0 0,再 差分 一下,得到新的速度序列 { v } \{v\} {v}

    e.g. : 5 ? 4 ? 2 ? 9 → 9 ? 5 ? 4 ? 2 ? 0 → 4 ? 1 ? 2 ? 2 \text{e.g.}:5\ 4\ 2\ 9\rightarrow 9\ 5\ 4\ 2\ 0\rightarrow 4\ 1\ 2\ 2 e.g.:5?4?2?99?5?4?2?04?1?2?2

    后面建图部分的第 i i i 个老鼠点若未明确指出原老鼠 / 差分老鼠,则均指已经差分后的第 i i i 个老鼠点。

差分过后,按划分的每个时间段重复以下的操作:

假设这个时间段 j j j 的长度为 t i m e = t i [ j + 1 ] ? t i [ j ] time=ti[j+1]-ti[j] time=ti[j+1]?ti[j]

  • 枚举 1 ~ m 1\sim m 1m 老鼠,给其一个新编号 t o t + + tot++ tot++,与汇点连边。

    i i i 个老鼠连边容量为 v i ? i ? time v_i·i·\text{time} vi??i?time

    你肯定会疑惑这个容量太奇怪了。但先别着急。

    看图:在这里插入图片描述

    v i ? i : v_i·i: vi??i: 差分值乘上它的差分数组内的编号。

    我们观察得到,假设差分数组为 d d d,原数组为 o o o,则有 ∑ d i ? i = ∑ o i \sum d_i·i=\sum o_i di??i=oi?

    那么乘上 t i m e time time 就是这个老鼠点在这个时间段能吃的奶酪限制。

    这个差分相当于将原老鼠切成了若干块,按相同块分类合并。

    这个在差分数组内的编号 i i i 意思就是有 i i i 只原老鼠能划分出这个 v [ i ] v[i] v[i] 块,统称一只差分老鼠。

  • 枚举 1 ~ n 1\sim n 1n 奶酪,对完全在这个时间段出现的奶酪继续操作:

    与每个老鼠连边,容量 v i ? t i m e v_i·time vi??time

    表示一只老鼠最多在一块奶酪上付出的努力。

    看图:在这里插入图片描述

你可以将不同颜色的蓝线当作坐标轴的 x , y x,y x,y 基准轴,然后划分出每个速度老鼠小方格,一个小方格一个小方格地分配。

从上往下从左往右第 ( x , y ) (x,y) (x,y) 的方格可以理解为该时间段会有第 y y y 只原老鼠吃掉第 x x x 个奶酪的 v y ? t i m e v_y·time vy??time 大小的部分。

每个奶酪会分配到某些行中的一个小方格,然后拼凑出来,相当于是吃掉了这个奶酪的一部分。


本板块是证明此种 奇怪神奇但是对的 网络流建图方式不会出现不合法的情况,即不存在某一时刻一只老鼠同时吃多个奶酪 / 一个奶酪同时被多只老鼠吃。

PS:本版块若未强调差分老鼠,则均指是原老鼠。

  • 先证明不存在一只老鼠同时吃多个奶酪的情况。

    假设排序后第 k k k 只老鼠在该时间段吃了 x x x 块奶酪,第 i i i 块奶酪吃了时间 t i t_i ti?

    如果 ( ∑ t i ) > t i m e (\sum t_i)> time (ti?)>time,则表示存在一只老鼠同时吃多个奶酪(不合法)。

    首先第 k k k 个差分老鼠至少产生的流量都是 ( ∑ t i ) ? v k (\sum t_i)·v_k (ti?)?vk?,而容量为 v k ? k ? t i m e v_k·k·time vk??k?time

    此时有两种情况:

    • 有速度更快的老鼠能够帮吃超额部分(引起老鼠必须同时吃奶酪的部分),那么就可以分担。

      就不会存在 ( ∑ t i ) > t i m e (\sum t_i)>time (ti?)>time。(也就是说看似不合法的流法可以通过调整变成合法流法)

    • 不能帮吃完所有超额部分。那么这些更快的老鼠肯定是吃过了,也就是它们在这个时间段每时每刻都在吃。

      在差分上会体现成前 k ? 1 k-1 k?1 个差分老鼠对 k k k 差分老鼠造成流量负担,加上原本流量, ( ∑ t i ) ? v k + ( k ? 1 ) ? v k ? t i m e = k ? v k ? t i m e + v k ? ( ( ∑ t i ′ ) ? t i m e ) > k ? v k ? t i m e (\sum t_i)·v_k+(k-1)·v_k·time=k·v_k·time+v_k·\big((\sum t_i')-time\big)>k·v_k·time (ti?)?vk?+(k?1)?vk??time=k?vk??time+vk??((ti?)?time)>k?vk??time,即流量大于容量,显然不可能,所以在网络流图上不可能跑成这样。(如果调整不了那么一开始就不可能跑成这种情况)

  • 再证明不存在一个 i i i 奶酪同时被多只老鼠吃的情况。

    假设有 x x x 只老鼠吃了该奶酪,吃了时间 t i t_i ti?

    如果 ( ∑ t i ) > t i m (\sum t_i)>tim (ti?)>tim 则表示存在一个奶酪同时被多只老鼠吃。

    由于排名靠前的老鼠会对排名靠后的老鼠造成影响。即吃了同一个奶酪的 排名最后面的 老鼠流量为 ( ∑ t i ) ? v k > t i ? v k (\sum t_i)·v_k>t_i·v_k (ti?)?vk?>ti??vk?(奶酪向每个差分老鼠的连边容量)。

    也不存在跑出这种情况的可能。


在建图板块,是从差分到原来;而在证明板块,是从原来到差分。可多思考一下。

因为这个建图是差分后的建图,所以如果要输出方案(即还原每只老鼠吃的时间)很有可能遇到差分还原后某只老鼠的时间是负数。
还原方法就是第一只差分老鼠的流量 / v /v /v 就是时间,然后把编号比它大的所有差分老鼠减去这个老鼠造成的流量贡献,以此类推。
网络流的流法很多种,我们只是知道最后流满没有。所以有可能某种流法看上去是不对的,但是它一定可以被调整成一种合法流法(优先满足编号小的差分老鼠)。
所以考察输出方案的话就显得不可做了。

code

#include <bits/stdc++.h>
using namespace std;
#define int long long
#define maxn 100000
#define maxm 500000
#define eps 1e-6
#define inf 1e9

int s, t, cnt, T, n, m;
int head[maxn], cur[maxn], dep[maxn];
struct edge { int to, nxt; double flow; }E[maxm];
namespace NetworkFlow {
    queue < int > q;
    void init() { s = 0, t = n * m * 2 + 1, cnt = -1, memset( head, -1, sizeof( head ) ); }
    void addedge( int u, int v, double w ) {
        E[++ cnt] = { v, head[u], w }, head[u] = cnt;
        E[++ cnt] = { u, head[v], 0 }, head[v] = cnt;
    }
    bool bfs() {
        memset( dep, 0, sizeof( dep ) );
        memcpy( cur, head, sizeof( head ) );
        dep[s] = 1; q.push( s );
        while( ! q.empty() ) {
            int u = q.front(); q.pop();
            for( int i = head[u];~ i;i = E[i].nxt ) {
                int v = E[i].to;
                if( ! dep[v] and E[i].flow > eps ) {
                    dep[v] = dep[u] + 1;
                    q.push( v );
                }
            }
        }
        return dep[t];
    }
    double dfs( int u, double cap ) {
        if( u == t or cap < eps ) return cap;
        double flow = 0;
        for( int i = cur[u];~ i;i = E[i].nxt ) {
            int v = E[i].to; cur[u] = i;
            if( dep[v] == dep[u] + 1 ) {
                double w = dfs( v, min( cap, E[i].flow ) );
                if( w < eps ) continue;
                E[i ^ 1].flow += w;
                E[i].flow -= w;
                flow += w;
                cap -= w;
                if( cap < eps ) break;
            }
        }
        return flow;
    }
    double dinic() {
        double ans = 0;
        while( bfs() ) ans += dfs( s, inf );
        return ans;
    }
}

struct node { double p, l, r; }c[maxn];
double v[maxn], ti[maxn];
double sum;
bool check( double delta ) {
    NetworkFlow :: init();
    for( int i = 1;i <= n;i ++ ) {
        NetworkFlow :: addedge( s, i, c[i].p );
        ti[i] = c[i].l, ti[i + n] = c[i].r + delta;
    }
    sort( ti + 1, ti + (n << 1 | 1) );
    int tot = n;
    for( int i = 1;i <= m;i ++ )
        for( int j = 1;j < (n << 1);j ++ ) {
            if( ti[j + 1] - ti[j] < eps ) continue;
            tot ++, NetworkFlow :: addedge( tot, t, ( ti[j + 1] - ti[j] ) * v[i] * i );
            for( int k = 1;k <= n;k ++ )
                if( c[k].l <= ti[j] and  ti[j + 1] <= delta + c[k].r )
                    NetworkFlow :: addedge( k, tot, ( ti[j + 1] - ti[j] ) * v[i] );
        }
    return sum - NetworkFlow :: dinic() < eps; 
}

signed main() { 
    scanf( "%lld", &T );
    while( T -- ) {
        scanf( "%lld %lld", &n, &m );
        for( int i = 1;i <= n;i ++ ) 
            scanf( "%lf %lf %lf", &c[i].p, &c[i].l, &c[i].r );
        sum = 0; 
        for( int i = 1;i <= n;i ++ ) 
            sum += c[i].p;
        for( int i = 1;i <= m;i ++ ) 
            scanf( "%lf", &v[i] );
        sort( v + 1, v + m + 1, []( int x, int y ) { return x > y; } ); 
        for( int i = 1;i < m;i ++ ) 
            v[i] = v[i] - v[i + 1];
        double l = 0, r = 5e7, ans;
        while( l + eps < r ) {
            double mid = ( l + r ) / 2;
            if( check( mid ) ) ans = mid, r = mid;
            else l = mid;
        }
        printf( "%.4f\n", ans );
    }
    return 0;
}
  数据结构与算法 最新文章
【力扣106】 从中序与后续遍历序列构造二叉
leetcode 322 零钱兑换
哈希的应用:海量数据处理
动态规划|最短Hamilton路径
华为机试_HJ41 称砝码【中等】【menset】【
【C与数据结构】——寒假提高每日练习Day1
基础算法——堆排序
2023王道数据结构线性表--单链表课后习题部
LeetCode 之 反转链表的一部分
【题解】lintcode必刷50题<有效的括号序列
上一篇文章      下一篇文章      查看所有文章
加:2022-03-04 15:50:05  更:2022-03-04 15:53:53 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/27 12:39:21-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码