大狗狗也AK了!

220 篇文章 2 订阅
122 篇文章 0 订阅

题目

题目背景
原来的那条小狗狗长成了大狗狗,令人欣慰!

题目描述
F i r e W i n g B i r d \sf FireWingBird FireWingBird 是我们最好的 f a m i l y    p e t \rm family\;pet familypet 。它今天要在学校里四处逛逛!其实是因为很久没有人来遛它了

学校有 n n n 个值得去的地方。对于任意两个互相可以直接到达的地点, S i s t e r \color{black}S\color{red}ister Sister 已经测出了它们之间的距离(因为她的工作流动性较高),用三元组 ⟨ a , b , w ⟩ \langle a,b,w\rangle a,b,w 表示, F i r e W i n g B i r d \sf FireWingBird FireWingBird a a a(或 b b b)时,花费 w w w 秒就可以到达 b b b(或 a a a)了。

虽然 F i r e W i n g B i r d \sf FireWingBird FireWingBird 的跑动速度是普通质量人类的两倍,它还是很难跑完整个学校。于是它决定使用自己的翅膀!

当炽烈的翅膀伸展之时, F i r e W i n g B i r d \sf FireWingBird FireWingBird 将撕裂虚空而来,光耀四海,鸟兽草木莫不惧伏!——《山海经》

F i r e W i n g B i r d \sf FireWingBird FireWingBird 在某个地方的时候,它可以放置一个 p r i n c i p a l \rm principal principal 。之后的时间里, F i r e W i n g B i r d \sf FireWingBird FireWingBird 可以给 p r i n c i p a l \rm principal principal 发消息,然后 p r i n c i p a l \rm principal principal 念动咒语:

当黑暗逼近,向天高呼 M e i n    G o t t ,    w a s    i s t    d a s \it Mein\; Gott,\; was\; ist\; das MeinGott,wasistdas,你便会见到那神圣的光明使者。——《山海经》

然后 F i r e W i n g B i r d \sf FireWingBird FireWingBird 就会立刻到达 p r i n c i p a l \rm principal principal 所在的地方!撕裂虚空是不花费时间的。

注意 p r i n c i p a l \rm principal principal 不能自己移动,因为他走路是不摆手的。只有 F i r e W i n g B i r d \sf FireWingBird FireWingBird 做出不雅行为时,他会瞬移到它旁边,表达 “不成体统” 的咒骂。显然 F i r e W i n g B i r d \sf FireWingBird FireWingBird 可以在任意地点做出不雅行为,有电线杆就行

注意 F i r e W i n g B i r d \sf FireWingBird FireWingBird 有一种高级操作:在 p r i n c i p a l \rm principal principal 呼唤它的时候,同时做出不雅动作。我就直说了:它会直接和 p r i n c i p a l \rm principal principal 交换位置!

现在, F i r e W i n g B i r d \sf FireWingBird FireWingBird 在地点 1 1 1,准备依次到达 k k k 个地点,分别是 a 1 , a 2 , … , a k a_1,a_2,\dots,a_k a1,a2,,ak 。在到达 a i a_{i} ai 后、到达 a i + 1 a_{i+1} ai+1 前,可以经过其他任意地点,也就是说,将最终 F i r e W i n g B i r d \sf FireWingBird FireWingBird 经过的地点按照时间顺序排列(一个地点可以出现多次),则 a 1 , a 2 , … , a k a_1,a_2,\dots,a_k a1,a2,,ak 是一个子序列。

请问 F i r e W i n g B i r d \sf FireWingBird FireWingBird 所需要的时间最小是多少?

数据范围与提示
n , k ⩽ 300 n,k\leqslant 300 n,k300,三元组的数量不超过 4 × 1 0 4 4\times 10^4 4×104 。学校很大,所以 1 ⩽ w ⩽ 1 0 9 1\leqslant w\leqslant 10^9 1w109吐槽:这个题面魔改的挺多啊……原题是两个传送门,二者之间相当于有一条零权边。可以远程关掉一个,然后在当前位置放置。考虑到大家都一眼看出只需要记录一个传送门的位置,我就略去了。

补记:听狗狗说,可能只有人类会一眼看出?但想起 这 道 题 \color{white}{这道题} 的时候,似乎只有一个传送门是理所应当啊……

思路

显然是 d p \tt dp dp,用 f ( i , j ) f(i,j) f(i,j) 表示当前在 a i a_i ai,传送门(也就是我们亲爱的 p r i n c i p a l \rm principal principal 校长)在 j j j,最小时间花费。

假如接下来要走到 a i + 1 a_{i+1} ai+1,且传送门变动到了 p p p,我们有什么策略?分步考虑。

首先一定要经过 p p p 。对于 a i → p a_i\rightarrow p aip 的过程,显然不需要在过程中新建传送门(如果只为了当前过程),因为传送门的作用是返回一个走过的点。而传送到 j j j 则应该在最初进行。所以只有两个策略:从 a i a_i ai 直接走到 p p p,和从 j j j 走到 p p p

然后考虑 p → a i + 1 p\rightarrow a_{i+1} pai+1 的过程。同理,本次行走过程中,不应该放置传送门。但是是否有可能使用上一步中,留下的一个传送门呢?

假设需要用。那么第一个过程变成了:从 a i a_i ai(或 j j j,二者相似,故下文默认为 a i a_i ai)走到 x x x 再走到 p p p 。注意由于 x x x 处会放置传送门,所以 a i a_i ai x x x p p p 的过程中都不能放传送门。从 p p p 传送到 x x x 之后,肯定也不会再放传送门(否则 p p p 就没了)。这是一个全步行的过程,如图:

sample
显然我们可以 O ( n ) \mathcal O(n) O(n) 枚举这个点,三个最短路相加。然而这就是 O ( k n 3 ) \mathcal O(kn^3) O(kn3) 了,做锤子哦!反正我当时在这里停了挺久的。

仔细想想,这种情况到底合不合理呢?我们在 p p p 留下一个传送门,不过是为了之后能够立刻到达此处。如果我们 改为在 x x x 处留下传送门 呢?之后要传送到 p p p 处时,就先传送到 x x x 处,再走到 p p p 处。代价相同!

于是我们规避了使用先前路径上留下的传送门。那么从 p p p a i + 1 a_{i+1} ai+1 就只有三种方案:直接走;传送回 j j j(如果第一轮是 a i → p a_i\rightarrow p aip,那么 j j j 并不是路径上的点,不能排除);传送回 a i a_i ai(与上面传送回 j j j 同理)。

所以我们做到了 O ( 1 ) \mathcal O(1) O(1) 转移。每个 f ( i , j ) f(i,j) f(i,j) O ( n ) \mathcal O(n) O(n) 个后继状态,总复杂度 O ( k n 2 ) \mathcal O(kn^2) O(kn2)

代码

#include <cstdio>
#include <iostream>
#include <cstring>
#include <vector>
#include <algorithm>
using namespace std;
# define rep(i,a,b) for(int i=(a); i<=(b); ++i)
# define drep(i,a,b) for(int i=(a); i>=(b); --i)
typedef long long int_;
inline int readint(){
	int a = 0; char c = getchar(), f = 1;
	for(; c<'0'||c>'9'; c=getchar())
		if(c == '-') f = -f;
	for(; '0'<=c&&c<='9'; c=getchar())
		a = (a<<3)+(a<<1)+(c^48);
	return a*f;
}
void writeint(int x){
	if(x > 9) writeint(x/10);
	putchar((x-x/10*10)^48);
}
inline void getMin(int_&x,const int_&y){
	if(y < x) x = y;
}

const int MaxN = 305;
const int_ infty = (1ll<<60)-1;
int a[MaxN<<1], n, k;
int_ g[MaxN][MaxN];
int_ dp[MaxN<<1][MaxN];

int main(){
	n = readint();
	rep(i,1,n) rep(j,1,n)
		if(i != j) g[i][j] = infty;
	int m = readint();
	k = readint()<<1;
	for(int x,y,v; m; --m){
		x = readint(), y = readint();
		if((v = readint()) < g[x][y])
			g[x][y] = g[y][x] = v;
	}
	rep(p,1,n) rep(i,1,n) rep(j,1,n)
		getMin(g[i][j],g[i][p]+g[p][j]);
	rep(i,1,k) a[i] = readint();
	a[0] = 1; // start
	rep(i,1,n) dp[0][i] = g[a[0]][i];
	rep(i,1,k) rep(j,1,n){
		dp[i][j] = infty; // init
		rep(p,1,n){
			int_ t = min(g[a[i]][j],g[a[i]][p]);
			getMin(t,g[a[i-1]][a[i]]); // three ways
			t += min(g[a[i-1]][j],g[j][p]); // two ways
			getMin(dp[i][j],dp[i-1][p]+t);
		}
	}
	printf("%lld\n",dp[k][a[k]]);
	return 0;
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值