题解 [JOI 2021 Final] ロボット

题解 [JOI 2021 Final] ロボット

Analysis

本题本质上就是一个最短路问题,关键在于考虑改变道路的颜色。我们计算新的花费以重建一张图来跑最短路。

  • 仅有一条与当前路口相连的路是要走的颜色,那么直接走过去即可,花费为 \(0\)。

  • 否则分为两种情况,记当前与当前路口相连的颜色为 \(c\) 的道路的总花费为 \(sum_c\),要去的点为 \(y\),花费为 \(p_y\)。

    1. 将要走的边改为独有的颜色,单次花费为 \(p_y\)。

    2. 改变与当前路口相连且与要走的路颜色相同的其他道路,单次花费为 \(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

题解 [JOI 2021 Final] ロボット

我们发现输出是 \(5\),而手模发现答案是 \(4\),即改变 \(1 \leftrightarrow 4\) 这一条边。

题解 [JOI 2021 Final] ロボット

每次修改边的颜色,会对与目标点相连的边的颜色状态产生影响。且影响的边是和被改变边原来颜色相同的边。也就是说,如果我们改变当前边,那么从目标点 \(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

上一篇:「JOI 2021 Final」机器人


下一篇:差分约束系统