Namomo Spring Camp 每日一题 Week 1Day2 No Crossing
Problem Statement
一个有 n n n个点 m m m条边的有向图, 你需要找到恰好经过 k k k个点的最短路径, 要求每次选的边不能越过之前已经经过的节点. 即对于路径中 x → y x\to y x→y. 不存在以前经过的点 t t t使得三者编号满足 min ( x , y ) ≤ t ≤ max ( x , y ) \min(x,y)\leq t\leq \max(x,y) min(x,y)≤t≤max(x,y).
Solution
首先我们考虑选择一条边 x → y x\to y x→y. 如果 x < y x<y x<y那么后续我们选择路径的时候不能经过点 k k k满足 k < x k<x k<x的点, 如果 x > y x>y x>y那么后续选择路径的时候不能经过点 k k k满足 k > x k>x k>x的点.
如果我们直接建图, 一开始我们能够经过 n n n上的任意节点, 然后没选择一条边就会减少可以到达的节点数目, 使得可以到达的节点减少, 并且我们可以知道可以到达节点一定是连续的.
如果我们之间进行DP, 我们进行分类讨论来确定可以到达那些节点, 非常地麻烦.
我们考虑反向建边, 我们建立反向边之后. 题意等价于我当前 x → y x\to y x→y后 x ≤ k ≤ y x\leq k\leq y x≤k≤y的点 k k k都是不能够再次达到的. 这样就避免了分类讨论的问题.
我们设 f i , v , l , r f_{i,v,l,r} fi,v,l,r表示选择 i i i条边, 当前处于 v v v点, 不可以再次到达 [ l , r ] [l,r] [l,r]这个区间内的点的最小花费.
那么我们可以简单得得到一个转移方程: f i + 1 , T o v , min { l , T o v } , max { r , T o v } = min { f i , v , l , r + W v } f_{i+1,To_v,\min\{l,To_v\},\max\{r,To_v\}}=\min\{f_{i,v,l,r}+W_v\} fi+1,Tov,min{l,Tov},max{r,Tov}=min{fi,v,l,r+Wv}.
时间复杂度 O ( 能 过 ) O(能过) O(能过).
优化方式:
- 为了避免无用的状态, 我们可以用 m a p map map或 u n o r d e r e d _ m a p unordered\_map unordered_map直接去维护可以达到的状态.
- 如果我们事先对每一个节点的出边 u u u, 其出边 ( u , v ) (u,v) (u,v)按 v v v排序后. 那么转移时候的可行边一定是一个前缀和一个后缀.
- 如果想用 u n o r d e r e d _ m a p unordered\_map unordered_map维护 D P DP DP值需要一个Hash建立一个双射区间 [ l , r ] [l,r] [l,r]映射到一个整数. 这里我们不妨用下列映射 [ l , r ] → l − 1 + r × n [l,r]\to l-1+r\times n [l,r]→l−1+r×n那么 K e y → [ K e y m o d N + 1 , K e y / N ] Key\to[Key\mod N+1,Key/N] Key→[KeymodN+1,Key/N].
Code
# define Fast_IO std::ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
# include "unordered_map"
# include "algorithm"
# include "iostream"
# include "cstdlib"
# include "cstring"
# include "cstdio"
# include "vector"
# include "bitset"
# include "queue"
# include "cmath"
# include "map"
# include "set"
using namespace std;
const int maxm=1e2+10;
const int INF=1<<30;
int N,K,M;
vector<pair<int,int> > Edge[maxm];
unordered_map<int,int> DP1[maxm],DP2[maxm];
int Hash(pair<int,int> Now){
return Now.first-1+Now.second*N;
}
pair<int,int> Inv_Hash(int H){
return {H%N+1,H/N};
}
int main(){
int i,j,k,A,B,C,L,R,Cost,W,To,Ans;
pair<int,int> Interval,Now_Edge;
scanf("%d%d%d",&N,&K,&M);
if(K==0 || K==1){
printf("0");
return 0;
}
for(i=1;i<=M;i++){
scanf("%d%d%d",&A,&B,&C);
Edge[B].push_back({A,C});
DP2[A][Hash({min(A,B),max(A,B)})]=DP2[A][Hash({min(A,B),max(A,B)})]==0?C:min(C,DP2[A][Hash({min(A,B),max(A,B)})]);
}
for(i=1;i<=N;i++) sort(Edge[i].begin(),Edge[i].end());
/*for(i=1;i<=N;i++){
for(auto To:Edge[i]){
printf("Edge:%d->%d:%d\n",i,To.first,To.second);
}
}*/
// printf("%d %d",Inv_Hash(Hash({1,2})).first,Inv_Hash(Hash({1,2})).second);
for(i=1;i<K-1;i++){
for(j=1;j<=N;j++){
swap(DP2[j],DP1[j]);
DP2[j].clear();
}
for(j=1;j<=N;j++){
for(auto Now:DP1[j]){
Interval=Inv_Hash(Now.first);
L=Interval.first,R=Interval.second;
Cost=Now.second;
for(k=0;k<Edge[j].size();k++){
Now_Edge=Edge[j][k];
To=Now_Edge.first,W=Now_Edge.second;
if(To>=L && To<=R) continue;
DP2[To][Hash({min(L,To),max(R,To)})]=DP2[To][Hash({min(L,To),max(R,To)})]==0?Cost+W:min(DP2[To][Hash({min(L,To),max(R,To)})],Cost+W);
}
}
}
}
Ans=INF;
for(i=1;i<=N;i++){
for(auto Now:DP2[i]){
Ans=min(Ans,Now.second);
}
}
printf("%d",Ans==INF?-1:Ans);
return 0;
}
Link
Link1: Daimayuan Online Judge 437.
Link2: CF793D Presents in Bankopolis.