HDU7134 Public Transport System
传送门
题意:给你一张\(n\)个点,\(m\)条边的有向图,每条边有两个权值\(a_i\)和\(b_i\),对于一条路径\(e_1,e_2,\cdots,e_k\),每条边的长度是这样计算的:
- \(e_1\)的长度是\(a_{e_1}\);
- 对于边\(e_i(i>1)\),如果\(a_{e_i} > a_{e_{i-1}}\),那么长度为\(a_{e_i} - b_{e_i}\),否则为\(a_{e_i}\)。
求节点\(1\)到其他点的最短路。(\(2 \leqslant n \leqslant 10^5, 1\leqslant m \leqslant 2 \times 10^5,a_i > b_i\))
这个是ccpc2021网络赛重赛的1009题,比赛上拿线段树+最短路乱搞,tle了。
感觉图论的很多题都是在考建图,建出正确的图后,跑一个比较裸的图论算法就过了。至于原因,我也不是很清楚,总之得有这么个意识吧。
首先的一点在于,一条边两种边权不好整,就拆成两条边权分别只有\(a_i\)和\(a_i-b_i\)的边。边权为\(a_i\)的边任何情况下都可以走,因为最优解一定比全走\(a_i\)的边优,而走\(a_i-b_i\)的边需要满足一定的条件。那现在问题在于如何建图,使其当且仅当满足条件时,才会走\(a_i-b_i\)的边。
考虑拆点。一种暴力的拆点方法是将点\(u\)拆成\(u\)的出度个,对于每个拆开的点\(u_i\),考虑哪些入边能接下来走\(a_{u_i} - b_{u_i}\).那么这一定是所有满足\(a_v < a_{u_i}\)的入边,于是将这些边连到\(u_i\)上。但这样连边的复杂度特别高,要想办法优化。
观察到这个条件是有单调性的,即如果\(a_v < a_{u_i}\),那么对于所有\(a_{u_j} \geqslant a_{u_i}\)的边,必然也能满足要求。因此我们可以把所有点按出边边权从大到小排序,对于一条入边\(a_{v}\),只向\(u_i(a_{u_i} > a_v\textrm{且最小})\)连边,而\(u\)内部,从\(u_i\)向\(u_{i+1}\)连一条边权为\(0\)的边。
这里借用官方题解的图:
原来是这样的:
优化后就变成了这样:
这样拆点后总点数\(n+m\),总边数\(3m\),用dijkstra的时间复杂度\(O(m\log n)\).
赛后因为数组越界发生了非常奇怪的错误,debug了半天……
#include<bits/stdc++.h>
using namespace std;
#define enter puts("")
#define space putchar(' ')
#define Mem(a, x) memset(a, x, sizeof(a))
#define In inline
#define forE(i, x, y) for(int i = head[x], y; ~i && (y = e[i].to); i = e[i].nxt)
typedef long long ll;
typedef double db;
const ll INF = 0x3f3f3f3f3f3f3f3f;
const db eps = 1e-8;
const int maxn = 3e5 + 5;
const int maxe = 6e5 + 5;
In ll read()
{
ll ans = 0;
char ch = getchar(), las = ' ';
while(!isdigit(ch)) las = ch, ch = getchar();
while(isdigit(ch)) ans = (ans << 1) + (ans << 3) + ch - '0', ch = getchar();
if(las == '-') ans = -ans;
return ans;
}
In void write(ll x)
{
if(x < 0) x = -x, putchar('-');
if(x >= 10) write(x / 10);
putchar(x % 10 + '0');
}
In void MYFILE()
{
#ifndef mrclr
freopen("random.in", "r", stdin);
freopen("ac.out", "w", stdout);
#endif
}
int n, m;
struct edges
{
int to, a, b;
In bool operator < (const edges& oth)const
{
return a < oth.a || (a == oth.a && b < oth.b);
}
};
vector<edges> E[maxn];
int sumd[maxn];
In int num(int x, int p) {return sumd[x - 1] + p + 1;}
struct Edge
{
int nxt, to, w;
}e[maxe];
int head[maxn], ecnt = -1;
In void addEdge(int x, int y, int w)
{
e[++ecnt] = (Edge){head[x], y, w};
head[x] = ecnt;
}
bool in[maxn];
ll dis[maxn];
#define pr pair<ll, int>
#define mp make_pair
#define F first
#define S second
In void dijkstra(int s)
{
priority_queue<pr, vector<pr>, greater<pr> > q;
dis[s] = 0;
q.push(mp(dis[s], s));
while(!q.empty())
{
int now = q.top().S; q.pop();
if(in[now]) continue;
in[now] = 1;
forE(i, now, v)
{
if(dis[v] > dis[now] + e[i].w)
{
dis[v] = dis[now] + e[i].w;
q.push(mp(dis[v], v));
}
}
}
}
In void init()
{
ecnt = -1;
for(int i = 1; i <= n + m; ++i)
{
E[i].clear();
head[i] = -1;
dis[i] = INF, in[i] = 0;
}
}
int main()
{
// MYFILE();
int T = read();
while(T--)
{
n = read(), m = read();
init();
for(int i = 1; i <= m; ++i)
{
int x = read(), y = read(), a = read(), b = read();
E[x].push_back((edges){y, a, b});
}
for(int i = 1; i <= n; ++i)
{
sumd[i] = sumd[i - 1] + E[i].size() + 1;
sort(E[i].begin(), E[i].end());
}
for(int i = 1; i <= n; ++i)
{
int siz = E[i].size();
if(siz) addEdge(num(i, siz - 1), num(i, siz), 0);
for(int j = 0; j < siz; ++j)
{
if(j) addEdge(num(i, j - 1), num(i, j), 0);
int v = E[i][j].to;
int pos = upper_bound(E[v].begin(), E[v].end(), (edges){0, E[i][j].a, (int)1e9}) - E[v].begin();
addEdge(num(i, j), num(v, pos), E[i][j].a - E[i][j].b);
addEdge(num(i, siz), num(v, pos), E[i][j].a);
}
}
dijkstra(sumd[1]);
for(int i = 1; i <= n; ++i)
{
ll Min = INF;
for(int j = 0; j <= (int)E[i].size(); ++j) Min = min(Min, dis[num(i, j)]);
write(Min == INF ? -1 : Min), (i == n ? enter : space);
}
}
return 0;
}