拆点
拆点顾名思义就是把点进行拆分比如一个点i就可以划分为i1,i2,i3他们都代表着i但是是不同状态下的i,本来可能只有一条与i相连的边但是通过拆点之后可能会有很多不同状态的连线,代表了i在不同的方案中的位置,拆点后的图相比于原来的图会变得更大(因为他将每种的状态的连线都画了出来)。
比如网络流中的有流量限制的点的问题就可以用到拆点,可以将有限制的点拆成两点,在他们中间加上一条边,权值为限制值,就可以直接套用板子了。(还没学到网络流不展开讲了
另外一个应用就是分层最短路。
例题:
迷路
#include<bits/stdc++.h>
#define endl '\n'
using namespace std;
const int maxn = 1e6+10;
const int INF = 1e8;
const int mod = 2009;
int n,t;
struct Matrix {
int m[250][250];
void clear() {
memset(m,0,sizeof(m));
}
};
int p(int i,int j) {
return (i-1)*10+j;
}
Matrix multi(Matrix a,Matrix b) {
Matrix res;
res.clear();
for(int i=1;i<=n;i++) {
for(int j=1;j<=n;j++) {
for(int k=1;k<=n;k++) {
res.m[i][j] = (res.m[i][j] + a.m[i][k] * b.m[k][j])%mod;
}
}
}
return res;
}
Matrix ksm(Matrix a,int b) {
Matrix res;
res.clear();
for(int i=1;i<=n;i++) {
res.m[i][i] = 1;
}
while(b) {
if(b&1) {
res = multi(res,a);
}
a = multi(a,a);
b>>=1;
}
return res;
}
int main()
{
cin>>n>>t;
int N = n;
n *= 10;
Matrix A;
for(int i=1;i<=N;i++) {
for(int j=1;j<10;j++) {
A.m[p(i,j)][p(i,j+1)] = 1;
}
for(int j=1;j<=N;j++) {
int x;
scanf("%1d",&x);
if(x) A.m[p(i,x)][p(j,1)] = 1;
}
}
A = ksm(A,t);
cout<<A.m[1][n-9]<<endl;
return 0;
}
分层图最短路
不同于常规最短路的是有k次0代价通过一条路径的机会,所以选择哪几次0代价就是关键了,每条路径都可以进行选择的话,就有很大的状态基数,可以想到用dp来进行状态转移,设dis(i,j)的含义为走到 i 点用了 j 次0代价机会的花费,很明显要走到 i 点,肯定是从他的父亲节点集合fa走过来的,他的父亲节点的状态有dis(fa,j)或dis(fa,j-1),所以:
dis(i,j) = min{min{dis(fa,j-1)},min{dis(fa,j)+边权(fa,i)}}
即从父亲那走过来选择0代价或不选择0代价,当j-1>=k时,dis(fa,j)的值会是正无穷。
但这和拆点有什么关系呢?其实在进行状态转移的时候我们是将每个点都拆成了k+1个点来看的(每个点都有k+1种状态,到这个点进行了0,1,2…k+1次0代价选择),这也是为什么叫做分层图最短路的原因(分了k+1层)。
例题:
飞行路线
#include<bits/stdc++.h>
using namespace std;
typedef pair<int,int> pii;
const int INF = 0x3f3f3f3f;
const int maxn = 5e6+10;
int edge[maxn],ver[maxn],head[maxn],nxt[maxn],dist[maxn],vis[maxn];
int n,m,k,s,t,cnt;
void add(int x,int y,int z) {
ver[++cnt] = y,edge[cnt] = z,nxt[cnt] = head[x],head[x] = cnt;
}
void Dijkstra(int s) {//单源最短路板子
memset(dist,INF,sizeof(dist));
dist[s] = 0;
priority_queue<pii>q;
q.push({0,s});
while(!q.empty()) {
auto p = q.top();q.pop();
int x = p.second;
if(vis[x]) continue;
vis[x] = 1;
for(int i=head[x];i;i = nxt[i]) {
int y = ver[i],z = edge[i];
if(vis[y]) continue;
if(dist[y]>dist[x]+z) {
dist[y] = dist[x] +z;
q.push({-dist[y],y});//存负的权值可以使得使优先队列中将权值从大到小排序。
}
}
}
}
int main()
{
cin>>n>>m>>k;
cin>>s>>t;
for(int i=1;i<=m;i++) {
int a,b,c;
cin>>a>>b>>c;
add(a,b,c);
add(b,a,c);
for(int j=1;j<=k;j++) {//将1-k层的图建起来
add(a+(j-1)*n,b+j*n,0);//上下两层的连接
add(b+(j-1)*n,a+j*n,0);
add(a+j*n,b+j*n,c);//第j层的连接
add(b+j*n,a+j*n,c);
}
}
for(int i=1;i<=k;i++) {//防hack数据
add(t+(i-1)*n,t+i*n,0);
}
Dijkstra(s);
cout<<dist[t+k*n]<<endl;
}