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;