Topcoder 14447 ColorfulPath

题目链接

Topcoder 14447 ColorfulPath

题目大意

给定一张 \(n+1\) 个节点的有向图,节点标号为 \(0,1,...,n\),边有边权 \(w_i\),边的端点 \(u_i,v_i\) 满足一些特殊限制:

  • \(u_i<v_i\) (图是一张 \(DAG\)
  • 不存在 \(i,j\) 满足 \(u_i<u_j<v_i<v_j\)

另外节点 \(1\)\(n-1\) 有颜色 \(color_i\),现在要求出 \(0\)\(n\) 的一条最短路径,满足对于每类颜色的点,要么全被访问过,要么都没有被访问过,求最短路径长度。

\(1\leq n,color_i\leq 1000\)\(1\leq w_i\leq 10^6\)

思路

可以发现图是一个数轴上点向后连的形式,而边之间不相交。所以我们从 \(0\) 开始每次尽量往后跳,一直到 \(n\) 经过的点,在所有路径中都会出现,是必经点。

一个重要的观察是图中存在子问题结构,考虑图上两个必经点之间的部分,这一块的点能到达的区域完全在内部,而两必经点间若不走直连的边,则会变成一个和原问题形式完全一样的子结构,而这些子问题够成一个树形的归属关系。

考虑颜色的限制,我们将相同颜色的点排成环状,若一个点 \(i\) 被访问了,那么强制要求 \(nxt_i\) 也要被访问,这样的强制关系传递下去,恰好可以使得一整类点的访问情况相同。

于是建图,开始先将子问题的树形结构建出来,子问题指向父问题,表示若子问题被访问(即不走父问题中必经点间直连的边),则父问题也必须被访问。然后子问题和其中的必经点之间连边,两者互相依赖,从而连双向边。最后是同色点之间的约束关系,\(i\) 连向 \(nxt_i\) 。子问题具有点权,表示选择递归子问题后路径长度的变化值,即「走必经点的路径长度」与「上层直连边的 \(w_e\) 」的差值。

此时问题转化为最小权闭合子图。经典问题「最大权闭合子图」可以通过最小割解决。我们将原图中的边容量设为 \(\infty\)\(S\) 连向正权点,负权点连向 \(T\),边权为原图点权的绝对值,而「正权点点权之和 \(-\) 最小割」即所求最大权,这里求最小权时将点权置为相反数即可。

这个网络流建图方式很好理解,对于任一条通路,要么保留,权值加上其中的负点权,要么放弃,权值减去其中的正点权,这即对应着最小割,最终和 \(S\) 相连的点即选取的封闭子图。

时间复杂度 \(O(n^2+dinic(n))\)

实现细节

可能存在不位于任一子问题中的点,所以需另建一个子问题,边权为 \(\infty\),将这些点连向此子问题,这节点访问不到,所以 \(ans=\infty\) 时对应无解。

Code

#include<iostream>
#include<vector>
#include<algorithm>
#include<cstring>
#include<queue>
#define mem(a,b) memset(a, b, sizeof(a))
#define rep(i,a,b) for(int i = (a); i <= (b); i++)
#define per(i,b,a) for(int i = (b); i >= (a); i--)
#define N 2100
#define PII pair<int, int>
#define fr first
#define sc second
#define Inf 0x3f3f3f3f
using namespace std;

class ColorfulPath{
    public:
    int n, cnt;
    vector<PII> conn[N], node[N*2];
    vector<int> group[N];
    bool vis[N];

    int head[3*N], nxt[8*N], to[8*N], w[3*N];
    int now[3*N], cap[8*N], dis[3*N];
    int id, S, T;
    queue<int> q;

    void init(){ mem(head, -1), cnt = -1; }
    void add_e(int a, int b, int w, bool id){
        nxt[++cnt] = head[a], head[a] = cnt, to[cnt] = b, cap[cnt] = w;
        if(id) add_e(b, a, 0, 0);
    }

    bool bfs(){
        mem(dis, 0), dis[S] = 1;
        q.push(S);
        while(!q.empty()){
            int cur = q.front(); q.pop();
            now[cur] = head[cur];
            for(int i = head[cur]; ~i; i = nxt[i]) if(cap[i])
                if(!dis[to[i]]) dis[to[i]] = dis[cur]+1, q.push(to[i]);
        }
        return dis[T];
    }

    int dfs(int x, int flow){
        if(x == T) return flow;
        int flown = 0;
        for(int i = now[x]; ~i; i = nxt[i]){
            now[x] = i;
            if(dis[to[i]] != dis[x]+1 || cap[i] <= 0) continue;
            int t = dfs(to[i], min(cap[i], flow-flown));
            cap[i] -= t, cap[i^1] += t;
            flown += t;
            if(flow == flown) break;
        }
        return flown;
    }

    void build(int l, int r, int pre, int fa){
        int x = l, sum = 0;
        ++id;
        while(x != r){
            int pos = upper_bound(conn[x].begin(), conn[x].end(), PII{r, Inf}) - conn[x].begin();
            if(x == l) pos = lower_bound(conn[x].begin(), conn[x].end(), PII{r, 0}) - conn[x].begin();
            if(pos == 0 || x == conn[x][pos-1].fr){ node[id--].clear(); return; }
            int nod = conn[x][pos-1].fr, e = conn[x][pos-1].sc;
            while(pos > 0 && conn[x][--pos].fr == nod) e = min(e, conn[x][pos].sc);
            sum += e, x = nod;
            node[id].push_back({x, e});
        }
        if(~fa) add_e(id, fa, Inf, 1);
        w[id] = sum - pre;
        for(PII p : node[id]) if(p.fr != r) 
            add_e(id, p.fr, Inf, 0), add_e(p.fr, id, Inf, 0), vis[p.fr] = true;
        int lst = l, tmp = id;
        if(node[id].size() > 1) 
            for(PII p : node[id]) build(lst, p.fr, p.sc, tmp), lst = p.fr;
    }

    int shortestPath(vector<int> a, vector<int> b, vector<int> cost, vector<int> color){
        n = color.size()+1;
        rep(i,0,(int)a.size()-1) conn[a[i]].push_back({b[i], cost[i]});
        rep(i,1,n-1) group[color[i-1]].push_back(i);

        init();
        rep(i,0,n) sort(conn[i].begin(), conn[i].end());
        int cross = Inf;
        per(i,(int)conn[0].size()-1,0) 
            if(conn[0][i].fr == n) cross = min(cross, conn[0][i].sc);
        id = n;
        build(0, n, cross, -1);
        if(id == n && cross == Inf) return -1;
        ++id, w[id] = Inf;
        rep(i,1,n-1) if(!vis[i]) add_e(i, id, Inf, 1);
        rep(i,1,N-1){
            int siz = group[i].size();
            rep(j,0,siz-1) add_e(group[i][j], group[i][(j+1)%siz], Inf, 1);
        }
        S = ++id, T = ++id;
        int tot = 0;
        rep(i,1,id){
            if(w[i] < 0) add_e(S, i, -w[i], 1), tot -= w[i];
            if(w[i] > 0) add_e(i, T, w[i], 1);
        }

        int flow = 0;
        while(bfs()) flow += dfs(S, Inf);
        int ans = cross - tot + flow;
        return ans < Inf ? ans : -1;
    }
} solve;
posted @ 2021-12-27 16:53  Neal_lee  阅读(72)  评论(0编辑  收藏  举报