看到路径有一定要求的路径,想到分层最短路。
在思考分层最短路的时候,注意转化,不要直线思维。
将题目上特殊的操作转换成一种简单的对所有边可以进行的操作,通过DP的设计实现题目的要求。
在CF1473E中,题目说路径和删去最大边加上最小边。设计DP时,dp[i][0/1][0/1]表示在点i的路径和,1/0代表这种情况是/否选择一条边作为加上/减去的边。我们在操作的时候,减去的可能不是最大的,加上的也可能不是最小的,但是通过DP的转移,最优的情况一定会被我们计算出来。
也要注意题目里设置的特殊性,一条路径可以任意减去一条边,那显然减去最大边的路径和最小;一条路径可以任意加上一条边,那显然加上最小边的路径和最小。那么,每一条路径加上一条便减去一条边的最小值显然就是题目上定义的路径长。
题目上要求减去最大的,加上最小的,那转化成每一条边都可以减去或加上。转移的值为最小的边权和。
#include <cstdio>
#include <iostream>
#include <vector>
#include <string>
#include <queue>
#include <algorithm>
#include <cmath>
#include <set>
#include <map>
#include <iomanip>
#include <cstdlib>
#include <stack>
#include <cstring>
#define rep(i,a,b) for(int i=(a);i<=(b);i++)
#define repp(i,a,b) for(int i=(a);i<(b);i++)
#define lep(i,a,b) for(int i=(a);i>=(b);i--)
#define lepp(i,a,b) for(int i=(a);i>(b);i--)
#define sci(x) scanf("%d",&(x))
#define scl(x) scanf("%lld",&(x))
#define scs(x) scanf("%s",(x))
#define pri(x) printf("%d\n",(x))
#define prl(x) printf("%lld\n",(x))
#define prs(x) printf("%s\n",(x))
#define pii pair<int,int>
#define pll pair<long long,long long>
#define mp make_pair
#define pb push_back
#define fi first
#define se second
#define All(x) x.begin(),x.end()
#define ms(a,b) memset(a,b,sizeof(a))
#define INF 0x3f3f3f3f
#define INFF 0x3f3f3f3f3f3f3f3f
#define multi int T;scanf("%d",&T);while(T--)
using namespace std;
typedef long long ll;
typedef double db;
const int N=2e5+5;
const int mod=10007;
const db eps=1e-6;
const db pi=acos(-1.0);
int n,m,vis[N][2][2];
struct edge{
int v;
ll w;
};
vector<edge>tr[N];
struct E{
int v,x,y;
ll dis;
bool operator < (const E &A)const{
return dis>A.dis;
}
};
priority_queue<E>q;
ll dis[N][2][2];
int main(){
#ifndef ONLINE_JUDGE
freopen("D:\\work\\data.in","r",stdin);
#endif
cin>>n>>m;
rep(i,1,m){
int u,v;
ll w;
cin>>u>>v>>w;
tr[u].push_back({v,w});
tr[v].push_back({u,w});
}
ms(dis,INFF);
q.push({1,0,0,0});
dis[1][0][0]=0;
while(q.size()){
E tmp=q.top();
q.pop();
if(vis[tmp.v][tmp.x][tmp.y]) continue;
vis[tmp.v][tmp.x][tmp.y]=1;
ll curdis=tmp.dis;
for(auto v:tr[tmp.v]){
ll *d=&dis[v.v][tmp.x][tmp.y];
if(curdis+v.w<*d){
*d=curdis+v.w;
q.push({v.v,tmp.x,tmp.y,*d});
}
d=&dis[v.v][1][tmp.y];
if(tmp.x==0&&curdis<*d){
*d=curdis;
q.push({v.v,1,tmp.y,*d});
}
d=&dis[v.v][tmp.x][1];
if(tmp.y==0&&curdis+2LL*v.w<*d){
*d=curdis+2LL*v.w;
q.push({v.v,tmp.x,1,*d});
}
d=&dis[v.v][1][1];
if(tmp.x==0&&tmp.y==0&&curdis+v.w<*d){
*d=curdis+v.w;
q.push({v.v,1,1,*d});
}
}
}
rep(i,2,n){
cout<<dis[i][1][1]<<" ";
}
}
在POJ3662中,题目要求我们使路径中第k+1的边尽量小。但设计DP时,dp[i][j]表示在点i已经指定j条边免费的最大边权。而不是表示到点i路径中第j大的边。在前一种情况中,可能我们设置免费的j条边不一定是前j大的边,但是通过状态转移,最优的情况一定会出来;而如果使用后者,状态转移方程显然没有前者简单。
可以这样理解,对于每一条路径,虽然我可能选的k条免费路不同,但一定是选前K大的时候留下的最大边最小。再对于每一条路径留下的最大边进行比较得到答案。
题目上要求只能取出前K大,那么转换成任取K条边。转移的值是剩下的最大边权。
#include <cstdio>
#include <iostream>
#include <vector>
#include <string>
#include <queue>
#include <algorithm>
#include <cmath>
#include <set>
#include <map>
#include <iomanip>
#include <cstdlib>
#include <stack>
#include <cstring>
#define rep(i,a,b) for(int i=(a);i<=(b);i++)
#define repp(i,a,b) for(int i=(a);i<(b);i++)
#define lep(i,a,b) for(int i=(a);i>=(b);i--)
#define lepp(i,a,b) for(int i=(a);i>(b);i--)
#define sci(x) scanf("%d",&(x))
#define scl(x) scanf("%lld",&(x))
#define scs(x) scanf("%s",(x))
#define pri(x) printf("%d\n",(x))
#define prl(x) printf("%lld\n",(x))
#define prs(x) printf("%s\n",(x))
#define pii pair<int,int>
#define pll pair<long long,long long>
#define mp make_pair
#define pb push_back
#define fi first
#define se second
#define All(x) x.begin(),x.end()
#define ms(a,b) memset(a,b,sizeof(a))
#define INF 0x3f3f3f3f
#define multi int T;scanf("%d",&T);while(T--)
using namespace std;
typedef long long ll;
typedef double db;
const int N=1e3+5;
const int mod=1e9+7;
const db eps=1e-6;
const db pi=acos(-1.0);
int n,p,k,vis[N][N],dis[N][N];
struct edge{
int v,w;
};
vector<edge>tr[N];
struct E{
int v,k,w;
bool operator < (const E &A) const{
return w>A.w;
}
};
priority_queue<E>q;
void upd(int tmp,int v,int nx){
if(tmp<dis[v][nx]){
dis[v][nx]=tmp;
q.push({v,nx,tmp});
}
}
int main(){
#ifndef ONLINE_JUDGE
freopen("D:\\work\\data.in","r",stdin);
#endif
ms(dis,INF);
cin>>n>>p>>k;
rep(i,1,p){
int u,v,w;
cin>>u>>v>>w;
tr[u].push_back({v,w});
tr[v].push_back({u,w});
}
q.push({1,0,0});
while(q.size()){
E tmp=q.top();
q.pop();
if(vis[tmp.v][tmp.k]) continue;
vis[tmp.v][tmp.k]=1;
repp(i,0,tr[tmp.v].size()){
edge e=tr[tmp.v][i];
if(tmp.k<=k) upd(tmp.w,e.v,tmp.k+1);
upd(max(e.w,tmp.w),e.v,tmp.k);
}
}
cout<<(dis[n][k]==INF?-1:dis[n][k])<<endl;
}
因为要的时最大的最小值,可以用二分答案。大于mid的边权设为1,小于等于mid的边权设为0。这个01图可以用双端队列BFA求最短路。如果最后dis[n]>k,说明比mid大的边超过k个了,mid太小了;否则就是太大了。
#include <cstdio>
#include <iostream>
#include <vector>
#include <string>
#include <queue>
#include <algorithm>
#include <cmath>
#include <set>
#include <map>
#include <iomanip>
#include <cstdlib>
#include <stack>
#include <cstring>
#define rep(i,a,b) for(int i=(a);i<=(b);i++)
#define repp(i,a,b) for(int i=(a);i<(b);i++)
#define lep(i,a,b) for(int i=(a);i>=(b);i--)
#define lepp(i,a,b) for(int i=(a);i>(b);i--)
#define sci(x) scanf("%d",&(x))
#define scl(x) scanf("%lld",&(x))
#define scs(x) scanf("%s",(x))
#define pri(x) printf("%d\n",(x))
#define prl(x) printf("%lld\n",(x))
#define prs(x) printf("%s\n",(x))
#define pii pair<int,int>
#define pll pair<long long,long long>
#define mp make_pair
#define pb push_back
#define fi first
#define se second
#define All(x) x.begin(),x.end()
#define ms(a,b) memset(a,b,sizeof(a))
#define INF 0x3f3f3f3f
#define INFF 0x3f3f3f3f3f3f3f3f
#define multi int T;scanf("%d",&T);while(T--)
using namespace std;
typedef long long ll;
typedef double db;
const int N=1e3+5;
const int mod=1e9+7;
const db eps=1e-6;
const db pi=acos(-1.0);
int n,m,w[N][N],p,k,u,v,vis[N],dis[N];
int main(){
#ifndef ONLINE_JUDGE
freopen("D:\\work\\data.in","r",stdin);
#endif
ms(w,INF);
cin>>n>>p>>k;
rep(i,1,p){
cin>>u>>v;
cin>>w[u][v];
w[v][u]=w[u][v];
}
int l=0,r=INF,flag=1;
while(l<r){
int mid=(l+r)>>1;
rep(i,1,n) vis[i]=0,dis[i]=INF;
deque<int>de;
de.push_back(1);
dis[1]=0;
while(de.size()){
int tmp=de.front();
de.pop_front();
if(vis[tmp]) continue;
vis[tmp]=1;
rep(i,1,n){
if(w[tmp][i]==INF) continue;
if(w[tmp][i]>mid&&dis[i]>dis[tmp]+1){
dis[i]=dis[tmp]+1;
de.push_back(i);
}
if(w[tmp][i]<=mid&&dis[i]>dis[tmp]){
dis[i]=dis[tmp];
de.push_front(i);
}
}
}
if(dis[n]==INF){
flag=0;
break;
}else if(dis[n]>k) l=mid+1;
else r=mid;
}
cout<<(flag?l:-1)<<endl;
}