题解 [JOI 2021 Final] ロボット
Analysis
本题本质上就是一个最短路问题,关键在于考虑改变道路的颜色。我们计算新的花费以重建一张图来跑最短路。
-
仅有一条与当前路口相连的路是要走的颜色,那么直接走过去即可,花费为 \(0\)。
-
否则分为两种情况,记当前与当前路口相连的颜色为 \(c\) 的道路的总花费为 \(sum_c\),要去的点为 \(y\),花费为 \(p_y\)。
-
将要走的边改为独有的颜色,单次花费为 \(p_y\)。
-
改变与当前路口相连且与要走的路颜色相同的其他道路,单次花费为 \(sum_c-p_y\)。
-
为了简便我们可以直接用 std::map
记录每种颜色对应的花费 (给了 2s 这不随便乱用),后面不再赘述。
自然地,以上朴素想法引发了一些问题。
Q1:是否存在一种情况,使得不能将一条边改为独有的颜色?
显然不存在。注意到一共有 \(m\) 条边,\(m\) 种颜色,那么显然每条边都可以改为独有的颜色。或者考虑菊花图的情况,这时一个点占有的颜色种类最多。但此时无需改变颜色。
Q2:一条边会不会被重复改变颜色?
不会。一条边走两次显然不是最优解。
Q3:何时无法到达?
当点 \(1\) 与点 \(n\) 不连通时。由于原图所有边都是无向边,且不存在无法抵达相邻点的情况,因此只有不连通时才无法到达。
明确以上三个基本问题以后就可以开始做题了。
对于 \(\text{subtask 1}\),直接暴力拆点,将每个点拆成 \(m\) 个点对应 \(m\) 种不同颜色,然后重新计算边权即可。
对于 \(\text{subtask 2}\),我们发现无论怎么跑每条边的花费一定是 \(1\),因此直接当最短路跑就行了。
对于 \(\text{subtask 3}\),我们发现基于原有的边直接跑最短路是有问题的。简单的说,在边权不同时,通过修改一条边抵达另一个点的操作存在“后效性”。
考虑一种最简单的图形。
4 3
1 2 1 5
1 3 1 4
3 4 1 1
我们发现输出是 \(5\),而手模发现答案是 \(4\),即改变 \(1 \leftrightarrow 4\) 这一条边。
每次修改边的颜色,会对与目标点相连的边的颜色状态产生影响。且影响的边是和被改变边原来颜色相同的边。也就是说,如果我们改变当前边,那么从目标点 \(y\) 继续走颜色为 \(c\) 的道路,相当于跳过中间点,直接抵达另一个点。
对每个点已拥有的颜色建立虚点,增加每个点对相应虚点的虚边即可。这里同样使用 std:set
记录。
因为对每条边至多建两个虚点,加上初始的 \(n\) 个点,所以整张图最多有 \(n+2\times m\) 个点。每两个通过非虚边连接的点 \(x,y\) 及其对应颜色的虚点 \(x',y'\) 至多连三条无向边,即 \(x \leftrightarrow y\),\(x\leftrightarrow y'\),\(x'\leftrightarrow y\),因此最多有 \(3\times m\) 条边。由于链式前向星连的是单项边,因此要开 \(6\times m\) 条边。
最后跑个最短路即可。时间复杂度 \(\mathcal{O}((n+m)\times \log(n+m))\)。
Code:
#include <bits/stdc++.h>
#define ld double
#define eps (1e-8)
#define ll long long
#define lld long double
#define ull unsigned long long
#define Min(x, y) ((x) > (y) and ((x) = (y)))
#define Max(x, y) ((x) < (y) and ((x) = (y)))
#define Add(x,y) (((x)+=(y))>=mod and ((x)-=mod))
#define F(i, a, b) for (int i = (a); i <= (b); ++i)
#define PF(i, a, b) for (int i = (a); i >= (b); --i)
#define For(i, x) for (int i = head[(x)]; i; i = net[(i)])
using namespace std;
bool beginning;
inline int read();
const int N=5e5+5,E=N<<3;//赛时防挂,不要在意
int n,m;
int ver[E],net[E],head[N],tot;
ll edge[E];
inline void add(int x,int y,ll z) {
ver[++tot]=y,net[tot]=head[x],edge[head[x]=tot]=z;
}
struct P {
int to,c;
ll p;
P(int x=0,int y=0,ll z=0) {
to=x,c=y,p=z;
}
};
vector<P>f[N];
ll dis[N];
struct Hp {
int d;
ll dis;
bool operator <(const Hp &a)const {
return dis>a.dis;
}
};
bool vis[N];
inline void dij() {
memset(dis,0x3f,sizeof(dis));
priority_queue<Hp>q;
dis[1]=0;
q.push({1,0});
Hp t;
while(!q.empty()){
t=q.top();
q.pop();
if(vis[t.d])continue;
vis[t.d]=1;
For(i,t.d){
int &y=ver[i];
if(dis[y]>dis[t.d]+edge[i]){
dis[y]=dis[t.d]+edge[i];
q.push({y,dis[y]});
}
}
}
printf("%lld\n",dis[n]==dis[0]?-1:dis[n]);
}
map<int,ll>mp,vtl[N];
bool ending;
int main() {
cerr<<1.0*(&beginning-&ending)/1024/1024<<"MB\n----------\n";
n=read(),m=read();
int a,b,c,p;
F(i,1,m) {
a=read(),b=read(),c=read(),p=read();
f[a].emplace_back(P<%b,c,p%>);
f[b].emplace_back(P<%a,c,p%>);
}
int cnt=n;
F(i,1,n){
for(P y:f[i])if(!mp[y.c])vtl[i][y.c]=++cnt,mp[y.c]=1;
for(P y:f[i])mp[y.c]=0;
}
mp.clear();
F(i,1,n) {
for(P y:f[i])mp[y.c]+=y.p;
for(P y:f[i]) {
add(i,y.to,min(y.p,mp[y.c]-y.p));
add(i,vtl[y.to][y.c],0);
add(vtl[i][y.c],y.to,mp[y.c]-y.p);
}
for(P y:f[i])mp[y.c]=0;
}
dij();
return 0;
}
inline int read() {
int x = 0;
bool f = 1;
char c = getchar();
while (!isdigit(c)) {
f = c ^ 45;
c = getchar();
}
while (isdigit(c)) x = (x << 3) + (x << 1) + (c ^ 48), c = getchar();
return f ? x : -x;
}
新建的边切记要开 long long
。