拆点

拆点

拆点顾名思义就是把点进行拆分比如一个点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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值