• AcWing 369. 北大ACM队的远足


    ( ext{Update on 2020.3.25})

    我之前的做法也有问题,讨论还是不够严谨,导致又有几组(见 打卡评论区( ext{Hack})

    此题数据极水,这里有几种错误写法:

    Type 1

    这 4 个数据,层层递进地告诉了我们一条边可能覆盖两次,并且覆盖的不一定是连续的桥,解决方案详情见下面的评论区 / 真实的分类讨论,# 包括Github上的标程好像也挂了(目前题解和打卡的代码貌似大多都是错的)。

    Input1:

    1
    2 1 0 1 1
    0 1 2
    

    Ans1:

    0
    

    Input2:

    1
    2 1 0 1 2
    0 1 3
    

    Ans2:

    0
    

    Input3:

    1
    4 3 0 3 3
    0 1 1
    1 2 5
    2 3 1
    

    Ans3:

    1
    

    Input4:

    1
    5 5 0 4 3
    0 1 1
    1 2 1
    1 2 1
    2 3 3
    3 4 1
    

    Ans4:

    0
    

    Type 2

    没有特判没有路径的无解情况,一开始我没写也过了。但是 OpenJudge 有卡这个。

    Input5:

    1
    2 0 0 1 1
    

    Ans5:

    -1
    

    Type 3

    这是一种错误的拓扑排序求最短路方式,即只把 (s) 起点塞到队列里进行拓扑排序,若有一些零度点指向了该路径上的一些点,由于零度点并没有进入刷新,所以那些点的度不会被减到 (0),从而就炸裂了,被错判成无解。

    奇妙的是,这么写可以 AC 并且过 OpenJudge。

    正确的做法是按照往常一个做拓扑排序,只不过维护距离 (d) 数组的时候,初始化时将 (d[s] = 0),其余均赋值为正无穷,之后每条边该转移时更新一下即可。

    Input6:

    1
    4 3 0 2 1
    0 1 1
    1 2 1
    3 1 1
    

    Ans6:

    0
    

    题目转化

    如书中所述,在任何一条最短路中桥、每段桥之间的距离都是一定的。即若将一条路径看做一个区间,每个桥看做一条区间,桥区间的分布是一定的。题目转化为了用两条长为 (Q) 的线段覆盖尽可能多的区间,

    如何检查是否为桥边

    正反跑一次最短路,记录方案数,考虑一条边两边的方案数是否等于全局的即可。

    书中未提到的 DP 方程

    由于这题区间的值域很大 (le 10^9),我们没办法开这么大的空间,但是可以贪心考虑。考虑找到一组最优解,将其向右平移,使他的右端点是一个桥边的右端点,这样答案不会变差(因为向右平移的过程都在右边答案贡献 (+1),左边贡献减 (0)(1)),同理左端点也可以这么考虑。所以我们 (DP) 每次的决策只需考虑在每条桥边的端点开始 (/) 结束坐车即可。

    一些定义

    (d[i]) 为 从 (S) 到第 (i) 个节点的距离

    (g[i]) 为 从 (S) 到第 (i) 个节点的桥的距离

    (bri[i])([i - 1, i]) 这段是不是桥

    这些玩意都是可以跑最短路后把最短路抽出来变成一条链 (O(n + m)) 处理的。

    DP

    (ds[i]) 为从 (S) 到第 (i) 个节点的最小危险程度

    (dt[i]) 为从 (i) 到第 (T) 个节点的最小危险程度

    处理 ds

    如果 (d[i] - d[i - 1]) 这段是桥:

    找到一个最小的 (k) 满足 (d[i] - d[k] <= q)。显然 (i) 增大的时候,(k) 也是不降的,具有单调性可以用双指针 (O(n))

    如果 (bri[k])

    (ds[i] = g[k] - (q - (d[i] - d[k])))

    否则 (ds[i] = g[k])

    处理 dt

    (dt[i]) 类似

    找到一个最大的 (k) 使得 (d[k] - d[i] <= q)

    如果 (bri[k])

    (dt[i] = g[tot] - g[k] - (q - (d[k] - d[i])))

    否则 $dt[i] = g[tot] - g[i] $

    真实的分类讨论

    其他题解讨论的都不是很严谨。

    • 假设一条桥边上没有覆盖两次,那么必然可以用书中的放法,将桥边画一条界,两边分别取最优。
    • 否则,假设最优解是一条桥边上覆盖两次的情况,那么存在一种方式是紧挨着的最优解,因为将他们平移至紧挨着答案不会变差,然后问题变成了 (2 * Q) 长度的一个覆盖,然后这个覆盖和之前类似,都是把线段平移到端点不会变差,这里就不再赘述。
    #include <cstdio>
    #include <iostream>
    #include <cstring>
    #include <algorithm>
    using namespace std;
    const int N = 100005, M = 200005, P = 1e9 + 7, INF = 0x3f3f3f3f;
    typedef long long LL;
    typedef pair<int, int> PII;
    int n, m, S, T, Q;
    int head[N], bhead[N], pre[N], d[N], dis[N], g[N], ds[N], dt[N], degS[N];
    int a[N], tot, q[N], degT[N], fs[N], ft[N], numE;
    bool st[M << 2], bri[N];
    struct E{
    	int next, v, w;
    } e[M << 2];
    void inline add(int u, int v, int w, int h[]) {
    	e[++numE] = (E) { h[u], v, w };
    	h[u] = numE;
    }
    void toposort(int s, int t, int h[], int cnt[], int deg[], int opt) {
        for (int i = 0; i < n; i++) cnt[i] = 0;
    	cnt[s] = 1;
    	int hh = 0, tt = -1; 
    	for (int i = 0; i < n; i++) if (!deg[i]) q[++tt] = i;
    	while (hh <= tt) {
    		int u = q[hh++];
    		for (int i = h[u]; ~i; i = e[i].next) {
    			int v = e[i].v;
    			if (opt && dis[u] + e[i].w < dis[v]) 
    				dis[v] = dis[u] + e[i].w, pre[v] = i;
    			(cnt[v] += cnt[u]) %= P;
    			if (--deg[v] == 0) q[++tt] = v;
    		}
    	}
    }
    void inline clear() {
        numE = -1, tot = 0;
        for (int i = 0; i < n; i++) {
        	head[i] = bhead[i] = -1, dis[i] = INF;
        	degS[i] = degT[i] = 0, bri[i] = false;
    	}
    	for (int i = 0; i < 2 * m; i++) st[i] = false;
    }
    int main() {
    	int Case; scanf("%d", &Case);
    	while (Case--) {
    		scanf("%d%d%d%d%d", &n, &m, &S, &T, &Q); clear();
    		for (int i = 1, u, v, w; i <= m; i++) {
    			scanf("%d%d%d", &u, &v, &w);
    			add(u, v, w, head); add(v, u, w, bhead);
    			degS[v]++, degT[u]++;
    		}
    		dis[S] = 0;
    		toposort(S, T, head, fs, degS, 1);
    		if (dis[T] == INF) { puts("-1"); continue; }
    		toposort(T, S, bhead, ft, degT, 0);
    		a[++tot] = T; 
    		while (a[tot] != S) a[tot + 1] = e[pre[a[tot]] ^ 1].v, tot++;
    		reverse(a + 1, a + 1 + tot);
    		for (int i = 1; i <= tot; i++) d[i] = dis[a[i]];
    		for (int u = 0; u < n; u++) {
    		    for (int i = head[u]; ~i; i = e[i].next) {
    		        int v = e[i].v;
    		        if ((LL)fs[u] * ft[v] % P == fs[T]) st[i] = true;
    		    }
    		}
    		for (int i = 2; i <= tot; i++) bri[i] = st[pre[a[i]]], g[i] = g[i - 1] + (bri[i] ? e[pre[a[i]]].w : 0);
    		int k = 1;
    		for (int i = 2; i <= tot; i++) {
    		    ds[i] = min(g[i], ds[i - 1] + g[i] - g[i - 1]);
    		    while (k + 1 <= i && d[i] - d[k] > Q) k++;
    		    ds[i] = min(ds[i], g[k] - (bri[k] ? Q - (d[i] - d[k]) : 0));
    		}
    		ds[1] = dt[tot] = 0, k = tot;
    		for (int i = tot - 1; i; i--) {
    		    dt[i] = min(g[tot] - g[i], dt[i + 1] + g[i + 1] - g[i]);
    		    while (k - 1 >= i && d[k] - d[i] > Q) k--;
    		    dt[i] = min(dt[i], g[tot] - g[k] - (bri[k + 1] ? Q - (d[k] - d[i]): 0));
    		}
    		int ans = 2e9;
    		for (int i = 1; i <= tot; i++) ans = min(ans, ds[i] + dt[i]);
    		k = 1;
    		for (int i = 2; i <= tot; i++) {
    		    while (k + 1 <= i && d[i] - d[k] > 2 * Q) k++;
    		    ans = min(ans, g[tot] - g[i] + g[k] - (bri[k] ? 2 * Q - (d[i] - d[k]) : 0));
    		}
    		printf("%d
    ", ans);
    	}
    	return 0;
    }
    
  • 相关阅读:
    转+更新 .NET中实践TDD
    解决方案:.net 4.0 下 Virtual Directory下如何部署一个作为Virtual Directory的Web Service
    knockout.js在线教程
    asp.net viewstate的最新理解
    转:什么是DIP、IoC、DI
    Common Infrastructure Libraries for .NET(1)Common.Logging Framework
    用Quartz.NET实现任务调度
    Common Infrastructure Libraries for .NET(2)ELMAH
    webots自学笔记(一)软件界面和简单模型仿真
    hdu 1753 大明A+B
  • 原文地址:https://www.cnblogs.com/dmoransky/p/12567739.html
Copyright © 2020-2023  润新知