AcWing 369 DP + 尺取法

题意

传送门 AcWing 369 PKU ACM Team’s Excursion

题解

使用两个区间覆盖 D A G DAG DAG 中两段长度不超过 q q q 的部分,使 D A G DAG DAG 种未覆盖的必经边长度之和最小。

f s [ i ] , f t [ i ] fs[i],ft[i] fs[i],ft[i] 分别代表从 S , T S,T S,T 出发走到 i i i 的路径数,使用拓扑序 D P DP DP 分别在正图、反图上求解。枚举边集,对于一条有向边 ( x , y ) (x,y) (x,y),若满足 f s [ x ] × f t [ y ] = f s [ T ] fs[x]\times ft[y]=fs[T] fs[x]×ft[y]=fs[T],则 ( x , y ) (x,y) (x,y) D A G DAG DAG S S S T T T 的必经边。

考虑枚举两段路线的分界点,并使分界点两侧被覆盖的必经边总长度尽可能小。任一相邻必经边之间的路线,都应该尽可能小,以保证必经边被尽可能地覆盖;那么求解一条最短路,在这条路径上处理问题。可以观察到在必经边 ( x , y ) (x,y) (x,y) 上的单个覆盖区间的端点 z , z ∈ ( x , y ) z,z\in (x,y) z,z(x,y),向覆盖这条必经边更多区域的方向移动,不会使必经边总覆盖区域更小,那么考虑将覆盖区域的一侧端点置于必经边的端点上。

假设分界点在路径端点上 g s [ i ] , g t [ i ] gs[i],gt[i] gs[i],gt[i] 分别代表从 S , T S,T S,T i i i 覆盖一个长度不大于 q q q 的区间可得到的最小未覆盖必经边长度。预处理出路径上从 T T T S S S 的路径长度与危险程度的前缀和 s u m , d a n g sum,dang sum,dang,依次考虑是否选取路径 ( i − 1 , i ) (i-1,i) (i1,i) 进行覆盖。设 j j j 为满足 s u m [ i ] − s u m [ j ] ≤ q sum[i]-sum[j]\leq q sum[i]sum[j]q 的最小值 g t [ i ] = m i n { g t [ i − 1 ] + d a n g [ i ] − d a n g [ i − 1 ] , d a n g [ j ] − d } gt[i] = min\{gt[i - 1] + dang[i] - dang[i - 1], dang[j] - d\} gt[i]=min{ gt[i1]+dang[i]dang[i1],dang[j]d} d = { q − ( s u m [ i ] − s u m [ j ] ) ( j − 1 , j ) 为 必 经 边 0 o t h e r w i s e d=\begin{cases}q-(sum[i]-sum[j]) &(j-1,j)为必经边\\ 0& otherwise\\ \end{cases} d={ q(sum[i]sum[j])0(j1,j)otherwise 由于 j j j 满足随着 i i i 递增而单调不减的满足尺取法的性质,动态规划的同时使用一个指针进行维护。 g s gs gs D P DP DP 方法同理。最后枚举分界点 i i i,使用 g s [ i ] + g t [ i ] gs[i]+gt[i] gs[i]+gt[i] 更新答案。

假设分界点在路径内部。考虑两个区间的最优覆盖时,不一定能通过上述的移动方法,将它们在某个路径上的端点处分隔开,此时分界点所在的路径一定是必经边。若将两个区间在分界点处合并,不会使答案更大,则将两个区间的覆盖问题转化为单个长度不超过 2 q 2q 2q 的区间的覆盖问题,使用相同的 D P DP DP 方法求解即可。总时间复杂度 O ( N + M ) O(N+M) O(N+M)

实现上有向边按照无向边的方法连接,正边索引为偶数,反边索引为奇数,则可以方便的进行求解。路径数是一个指数级别的整数,多次对大质数取模进行计算。

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 100005, maxm = 200005, inf = 0x3f3f3f3f;
const int mod[2] = {
    
    998244353, 1000000007};
int L, N, M, S, T, Q;
int tot, head[maxn], to[2 * maxm], nxt[2 * maxm], cost[2 * maxm];
int ds[maxn], pre[maxn], in[maxn], out[maxn], ft[maxn][2], fs[maxn][2];
bool bg[2 * maxm];
int vs[maxn], es[maxn], sum[maxn], dang[maxn];
int gs[maxn], gt[maxn];

void init()
{
    
    
    tot = 1;
    memset(head, 0, sizeof(head)), memset(bg, 0, sizeof(bg));
    memset(fs, 0, sizeof(fs)), memset(ft, 0, sizeof(ft));
    memset(in, 0, sizeof(in)), memset(out, 0, sizeof(out));
}

inline void add(int a[2], int b[2], int c[2])
{
    
    
    for (int i = 0; i < 2; ++i)
        a[i] = (b[i] + c[i]) % mod[i];
}

inline void mul(int a[2], int b[2], int c[2])
{
    
    
    for (int i = 0; i < 2; ++i)
        a[i] = ((ll)b[i] * c[i]) % mod[i];
}

inline bool equal(int a[2], int b[2])
{
    
    
    for (int i = 0; i < 2; ++i)
        if (a[i] != b[i])
            return 0;
    return 1;
}

inline void add(int x, int y, int z) {
    
     to[++tot] = y, cost[tot] = z, nxt[tot] = head[x], head[x] = tot; }

void topsort(int s, int fs[maxn][2], int in[maxn], int rev)
{
    
    
    fs[s][0] = fs[s][1] = 1;
    if (!rev)
        memset(ds, 0x3f, sizeof(ds)), ds[s] = 0;
    queue<int> q;
    for (int i = 0; i < N; ++i)
        if (!in[i])
            q.push(i);
    while (q.size())
    {
    
    
        int x = q.front();
        q.pop();
        for (int i = head[x]; i; i = nxt[i])
        {
    
    
            if ((i & 1) != rev)
                continue;
            int y = to[i], z = cost[i];
            add(fs[y], fs[x], fs[y]);
            if (!rev && ds[x] + z < ds[y])
                ds[y] = ds[x] + z, pre[y] = i;
            if (!--in[y])
                q.push(y);
        }
    }
}

int main()
{
    
    
    scanf("%d", &L);
    while (L--)
    {
    
    
        init();
        scanf("%d%d%d%d%d", &N, &M, &S, &T, &Q);
        for (int i = 1, x, y, z; i <= M; ++i)
            scanf("%d%d%d", &x, &y, &z), add(x, y, z), add(y, x, z), ++out[x], ++in[y];
        topsort(S, fs, in, 0), topsort(T, ft, out, 1);
        if (ds[T] == inf)
        {
    
    
            puts("-1");
            continue;
        }
        for (int i = 2; i <= tot; i += 2)
        {
    
    
            int x = to[i ^ 1], y = to[i], t[2] = {
    
    0};
            mul(t, fs[x], ft[y]);
            if (equal(t, fs[T]))
                bg[i] = 1;
        }
        int p = 0;
        sum[1] = dang[1] = 0;
        for (int x = T; x != S; x = to[pre[x] ^ 1])
        {
    
    
            vs[++p] = x, es[p] = pre[x];
            sum[p + 1] = sum[p] + cost[pre[x]];
            dang[p + 1] = dang[p] + (bg[pre[x]] ? cost[pre[x]] : 0);
        }
        vs[++p] = S;
        gt[1] = 0;
        for (int i = 2, j = 1; i <= p; ++i)
        {
    
    
            while (sum[i] - sum[j] > Q)
                ++j;
            int d = (j - 1 >= 1 && bg[es[j - 1]]) ? Q - (sum[i] - sum[j]) : 0;
            gt[i] = min(gt[i - 1] + dang[i] - dang[i - 1], dang[j] - d);
        }
        gs[p] = 0;
        for (int i = p - 1, j = p; i; --i)
        {
    
    
            while (sum[j] - sum[i] > Q)
                --j;
            int d = (j < p && bg[es[j]]) ? Q - (sum[j] - sum[i]) : 0;
            gs[i] = min(gs[i + 1] + dang[i + 1] - dang[i], dang[p] - dang[j] - d);
        }
        int res = inf;
        for (int i = 1; i <= p; ++i)
            res = min(res, gt[i] + gs[i]);
        gt[1] = 0, Q <<= 1;
        for (int i = 2, j = 1; i <= p; ++i)
        {
    
    
            while (sum[i] - sum[j] > Q)
                ++j;
            int d = (j - 1 >= 1 && bg[es[j - 1]]) ? Q - (sum[i] - sum[j]) : 0;
            gt[i] = min(gt[i - 1] + dang[i] - dang[i - 1], dang[j] - d);
        }
        res = min(res, gt[p]);
        printf("%d\n", res);
    }
    return 0;
}

猜你喜欢

转载自blog.csdn.net/neweryyy/article/details/115028325