【分层图】
【分层图】
(参考https://www.cnblogs.com/ldyzzz/p/17632253.html)
使用场合
求解特殊最短路
N个点,M条边,允许对K条边进行改变(对权值进行改变:花费/2,花费变0之类的)
板子题
https://www.luogu.com.cn/problem/P4822
基本方法
ps 其实就是和状态有关的暴力
建立多层相似或相同的图,并在图与图之间进行连边->实现图与图之间的转移
一般将决策前的状态和决策后的状态之间连接一条权值为决策代价的边,表示付出该代价后就可以进行此步状态转移
1.现将图分为k+1份(k为可操作的次数)
2.对于图中的每一条边<u,v>从u(i)到v(i+1)建立与题目或操作相对应的边(i=0,i=1………i=k)
如何建图?
物理建图:费空间
假设有k次机会可以使边权变0
主要是一维的,直接建k+1层(除了本身还有k层)
对于第i张图,起点就是 (i-1)*n
add(u,v,w);
add(v,u,w);
for(int i=1;i<=k;i++){
add(u+(i-1)*n,v+j*n,0);
add(v+(i-1)*n,u+j*n,0);
add(u+j*n,v+j*n,w);
add(v+j*n,u+j*n,w);
}
DP思想建图
将vis数组和dis数组多加一维
dis[i][j]
表示到达i用了j次免费机会的最小花费
vis[i][j]
表示到达i用了j次免费机会的最小花费是否出现
需要参与求解的实际问题的数据结构多加一维来模拟n层的效果
struct node{
int tier//层数
int d;//到达该点的最短路
int num;//当前节点
}a[k*n];
题目积累
[BJWC2012] 冻结
https://www.luogu.com.cn/problem/P4822
题目大意
n个点m条边,有边权,找从1到n的最短路
可以执行k次【减半】操作,每条边只能执行1次,可以不用消耗k次
思路
分层图
每层图之间的关系:u->v若要减半 则u在上一层 v在下一层 连一个time/2的边
跑一遍最短路就可以
找每一层为n的点的最小值
物理建图做法
const int N=1e3+10;
int n,m,k;
//用链式前向星建图:注意开的空间!m*k*4
int e[N*2010*4],h[N*2010*4],ne[N*2010*4],val[N*2010*4];
int idx;
//求最短路用
int dis[N*N];
bool st[N*N];
void add(int x,int y,int v){
e[idx]=y;
ne[idx]=h[x];
h[x]=idx;
val[idx++]=v;
}
//最短路
void dijstra(){
memset(dis,0x3f,sizeof dis);
memset(st,0,sizeof st);
dis[1]=0;
priority_queue<PII,vector<PII>,greater<PII>> q;
q.push({0,1});
while(q.size()){
auto t=q.top();
q.pop();
int pos=t.sc;
if(st[pos]) continue;
st[pos]=1;
for(int i=h[pos];i!=-1;i=ne[i]){
int j=e[i];
if(!st[j]){
if(dis[j]>dis[pos]+val[i]){
dis[j]=dis[pos]+val[i];
q.push({dis[j],j});
}
}
}
}
}
void solve(){
cin>>n>>m>>k;
memset(h,-1,sizeof h);
for(int i=1;i<=m;i++){
int a,b,time;
cin>>a>>b>>time;
//第0层
add(a,b,time);
add(b,a,time);
//k层:可以减半
for(int j=1;j<=k;j++){
add(a+(j-1)*n,b+j*n,time/2);//a->b用了/2的机会
add(b+(j-1)*n,a+j*n,time/2);//b->a用了/2的机会
add(a+j*n,b+j*n,time);//在该层不用
add(b+j*n,a+j*n,time);
}
}
dijstra();
//每一层都能到n->找最小值即可
//ps 其实就是一种暴力
int ans=inf_int;
for(int i=0;i<=k;i++){
//cout<<dis[n+i*n]<<endl;
ans=min(ans,dis[n+i*n]);
}
cout<<ans<<endl;
}
DP建图做法
难以控制的滑板火箭
https://qoj.ac/contest/2182/problem/12372
题目大意
给01迷宫图,在一分钟内必须移动[L,R]次,问最少需要多少分钟
思路
(1)求最短路:分层图BFS
奇数/偶数两种状态相互转移
(2)求最小分钟数:分类讨论!*很细致的一题分类讨论
注意不要漏了无最短路的情况!
※容易想到贪心:一定是优先走r更优
①若l!=r:无论是奇数还是偶数都能化解->[最短路/r]向上取整
②若l==r:每分钟走的步数是固定的->分别判断奇数/偶数最短路 设路线长度x
先算 到达终点最少要花费的时间k=[最短路/r]向上取整
i 若l为奇数: *总路线的奇偶性会由k决定 -> 其实无论是偶数或奇数都能化解*
(i) 若x为奇数 k为奇数 l*k为奇数 -> (l*k-x)偶数 -> 答案k
(ii) 若x为偶数 k为奇数 l*k为奇数 -> (l*k-x)奇数 -> k需要多动一次达成偶数-> 答案k+1
(iii) 若x为奇数 k为偶数 l*k为偶数 -> (l*k-x)奇数 -> k需要多动一次达成偶数-> 答案k+1
(iv) 若x为偶数 k为偶数 l*k为偶数 -> (l*k-x)偶数 -> 答案k
->奇偶性相同则答案为k 不同则为k+1
ii 若l为偶数: 总路线无论如何都会是偶数 -> 会产生-1!
(i) 若x为奇数 -> (l*k-x)奇数 -> 无法到达 为-1
(ii) 若x为偶数 -> (l*k-x)偶数 -> 答案k
代码
int n,m;
int l,r;
int dx[8]={0,1,0,-1,1,1,-1,-1},dy[8]={1,0,-1,0,1,-1,1,-1};
void solve(){
cin>>n>>m>>l>>r;
vector<string> s(n);
for(int i=0;i<n;i++) cin>>s[i];
vector<vector<int>> o(n,vector<int>(m,inf_int));
vector<vector<int>> j(n,vector<int>(m,inf_int));
vector<vector<bool>> ost(n,vector<bool>(m,0));
vector<vector<bool>> jst(n,vector<bool>(m,0));
queue<array<int,3>> q;
o[0][0]=0;
q.push({0,0,0});
while(!q.empty()){
auto t=q.front();
q.pop();
int x=t[0],y=t[1],se=t[2];
if(se==0){
if(ost[x][y]) continue;
ost[x][y]=1;
}
else{
if(jst[x][y]) continue;
jst[x][y]=1;
}
for(int i=0;i<8;i++){
int nx=x+dx[i],ny=y+dy[i];
if(nx<0 || nx>=n || ny<0 || ny>=m) continue;
if(s[nx][ny]!='1') continue;
if(se==0){
if(j[nx][ny]>o[x][y]+1){
j[nx][ny]=o[x][y]+1;
q.push({nx,ny,1});
}
}
else{
if(o[nx][ny]>j[x][y]+1){
o[nx][ny]=j[x][y]+1;
q.push({nx,ny,0});
}
}
}
}
int ans=inf_int;
auto check=[&](int x){
if(x==inf_int) return;
if(l==r){
int k=(x+l-1)/l;//满足向上取整
if(l%2==1){
//l是奇数->l*k奇偶性由k决定
//->注意这里可以通过次数来达成偶数次移动!!!
if((k%2)!=(x%2)) k++;
ans=min(ans,k);
}
else{
if(x%2==0){
ans=min(ans,k);
}
}
}
else{
int k=(x+r-1)/r;
ans=min(ans,k);
}
};
check(o[n-1][m-1]);
check(j[n-1][m-1]);
if(ans==inf_int) cout<<-1<<endl;
else cout<<ans<<endl;
}