P3159 [CQOI2012]交换棋子 题解

Description

传送门

Solution

Part 1: 简化版

首先,考虑这样一个简化版的问题: 第 i i i 行第 j j j 列的点被经过不超过 l i m i , j lim_{i,j} limi,j 次时,求最少移动步数。

首先,我们把白棋看做没棋。特别的,若 a i , j = b i , j = 1 a_{i,j}=b_{i,j}=1 ai,j=bi,j=1(即对应位均为黑棋),那么我们也将其均看做没棋。

考虑网络流建模。为满足点的限制,将每一个 ( i , j ) (i,j) (i,j) 拆成入点 L i , j L_{i,j} Li,j 和出点 R i , j R_{i,j} Ri,j

  • ∀ ( i , j ) \forall (i,j) (i,j),若 a i , j = 1 a_{i,j}=1 ai,j=1,连边 ( s , L i , j , 1 , 0 ) (s,L_{i,j},1,0) (s,Li,j,1,0)
  • ∀ ( i , j ) \forall (i,j) (i,j),若 b i , j = 1 b_{i,j}=1 bi,j=1,连边 ( R i , j , t , 1 , 0 ) (R_{i,j},t,1,0) (Ri,j,t,1,0)
  • ∀ ( i , j ) \forall (i,j) (i,j),连边 ( L i , j , R i , j , l i m i , j , 0 ) (L_{i,j},R_{i,j},lim_{i,j},0) (Li,j,Ri,j,limi,j,0)
  • ∀ ( x 0 , y 0 ) \forall (x_0,y_0) (x0,y0),对于所有与其相邻的 ( x 1 , y 1 ) (x_1,y_1) (x1,y1),连边 ( R x 0 , y 0 , L x 1 , y 1 , ∞ , 1 ) (R_{x_0,y_0},L_{x_1,y_1},∞,1) (Rx0,y0,Lx1,y1,,1)

跑最小费用最大流即可。

Part 2: 原题

回到原题。

此时 l i m i , j lim_{i,j} limi,j 作为了交换次数的上界,这意味着什么呢?不难发现,对于某条形如 a → b → c → d a \to b \to c \to d abcd 的棋子移动路径, b , c b,c b,c 会额外参与两次交换 a , d a,d a,d 只会参与一次交换

因此,我们需要把 L i , j L_{i,j} Li,j R i , j R_{i,j} Ri,j 之间的边的流量修改为 ⌊ l i m i , j 2 ⌋ \lfloor \frac {lim_{i,j}} {2} \rfloor 2limi,j。特别的,若 a i , j a_{i,j} ai,j b i , j b_{i,j} bi,j 有二者之一为 0 0 0(注意在 Part 1 中我们已经把 a i , j = b i , j = 1 a_{i,j}=b_{i,j}=1 ai,j=bi,j=1 的情况给处理掉了),那么我们再附上一条流量为 1 1 1 的边。

时间复杂度 O ( M C M F ( n 2 , n 2 ) ) O(MCMF(n^2,n^2)) O(MCMF(n2,n2)),可以通过本题。

Code

#include <bits/stdc++.h>
#define int long long
#define inf 20000000000007
using namespace std;
const int maxn=1005,maxp=3005;

int read(){
	int s=0,w=1;char ch=getchar();
	while (ch<'0'||ch>'9'){if (ch=='-')  w=-w;ch=getchar();}
	while (ch>='0'&&ch<='9'){s=(s<<1)+(s<<3)+(ch^'0');ch=getchar();}
	return s*w;
}
int n,m,s,t,sum,cnt=1;
int head[maxp],a[maxn][maxn],b[maxn][maxn],lim[maxn][maxn];

struct edge{int nxt,to,f,d;}e[2000*maxp];

int dx[8]={-1,-1,-1, 0, 0, 1, 1, 1};
int dy[8]={-1, 0, 1,-1, 1,-1, 0, 1};

void add_edge(int u,int v,int f,int d){
	cnt++;
	e[cnt].to=v,e[cnt].f=f,e[cnt].d=d;
	e[cnt].nxt=head[u],head[u]=cnt;
}
void Add(int u,int v,int f,int d){
//	cout<<"ADD:"<<u<<' '<<v<<' '<<f<<' '<<d<<endl;
	add_edge(u,v,f,d),add_edge(v,u,0,-d);
}

namespace MCMF{
	int dis[maxp],flow[maxp],pre[maxp],last[maxp],vis[maxp];
	bool SPFA(){
		for (int i=s;i<=t;i++)  dis[i]=flow[i]=inf,vis[i]=0;
		
		queue<int> q;
		q.push(s),dis[s]=0,vis[s]=1;
		while (!q.empty()){
			int now=q.front();
			q.pop();vis[now]=0;
			
			for (int i=head[now];i;i=e[i].nxt){
				int y=e[i].to;
				if (dis[y]>dis[now]+e[i].d&&e[i].f>0){
					dis[y]=dis[now]+e[i].d;
					flow[y]=min(flow[now],e[i].f);
					pre[y]=now,last[y]=i;
					if (!vis[y])  q.push(y),vis[y]=1;
				}
			}
		}
		if (dis[t]>=inf)  return false;
		else return true;
	}
	int calc(){
		int mincost=0,maxflow=0;
		while (SPFA()){
			int now=t;
			maxflow+=flow[t];
			mincost+=flow[t]*dis[t];
			while (now!=s){
				e[last[now]].f-=flow[t];
				e[last[now]^1].f+=flow[t];
				now=pre[now];
			}
		}
		return mincost;
	}
}
namespace ducati{
	int pos(int x,int y,int d){return d*n*m+(x-1)*m+y;}
	void get_all_in(){
		n=read(),m=read();
		for (int i=1;i<=n;i++){
			for (int j=1;j<=m;j++){
				char x;cin>>x;
				a[i][j]=x-'0',sum-=a[i][j];
			}
		}
		for (int i=1;i<=n;i++){
			for (int j=1;j<=m;j++){
				char x;cin>>x;
				b[i][j]=x-'0',sum+=b[i][j];
			}
		}
		for (int i=1;i<=n;i++){
			for (int j=1;j<=m;j++){
				char x;cin>>x;
				lim[i][j]=x-'0';
			}
		}
	}
	void build(){
		s=0,t=2*n*m+1;
		for (int i=1;i<=n;i++){
			for (int j=1;j<=m;j++){
				if (a[i][j]&b[i][j])  a[i][j]=b[i][j]=0;
				
				int x=pos(i,j,0),y=pos(i,j,1);
				if (a[i][j])  Add(s,x,1,0);
				if (b[i][j])  Add(y,t,1,0);
				Add(x,y,lim[i][j]/2,0);
				if (a[i][j]|b[i][j]){
					if (lim[i][j]&1)  Add(x,y,1,0);
				}
				for (int k=0;k<8;k++){
					int ii=i+dx[k],jj=j+dy[k];
					if (ii>=1&&ii<=n&&jj>=1&&jj<=m)
					  Add(y,pos(ii,jj,0),inf,1);
				}
			}
		}
	}
	void solve(){
		get_all_in(),build();
		if (sum==0)  cout<<MCMF::calc()<<endl;
		else cout<<-1<<endl;
	}
}

signed main(){
	ducati::solve();
	return 0;
}
题目描述 有 $n$ 个人,每个人有一个编号 $i$,每个人都跳舞,但是每个人都只会一种舞蹈。现在要求他们排成一个圆圈跳舞,使得相邻两个人跳的舞蹈不同。求方案数。 输入格式 一个整数 $n$。 输出格式 一个整数,表示方案数,由于答案可能很大,输出对 $10^9+7$ 取模的结果。 数据范围 $1\leq n\leq 10^5$ 输入样例1: 5 输出样例1: 20 输入样例2: 10 输出样例2: 14684570 算法 数学,组合数学,动态规划 思路 题目要求的是排成一个圆圈跳舞,而且相邻两个人跳舞的舞蹈不同,这就意味着最后一个人的舞蹈类型必须和第一个人不同。因为它们是相邻的。 如果我们考虑将最后一个人的舞蹈类型和第一个人不同的方案数,那么实际上就是将 $n$ 个人分成两组: - 第一组是前 $n-1$ 个人,需要满足相邻两个人跳舞的舞蹈不同。 - 第二组是第 $n$ 个人,需要满足和第一个人跳舞的舞蹈不同。 对于第一组,我们可以定义 $f[i]$ 表示前 $i$ 个人,最后一个人和第一个人跳舞的舞蹈类型不同的方案数。因为需要满足相邻两个人跳舞的舞蹈不同,所以有两种情况: - 如果第 $i$ 个人和第 $i-1$ 个人跳舞的舞蹈类型不同,那么最后一个人的舞蹈类型可以是除了第 $i-1$ 个人和第一个人外的所有舞蹈类型,即共有 $n-2$ 种选择。 - 如果第 $i$ 个人和第 $i-1$ 个人跳舞的舞蹈类型相同,那么最后一个人的舞蹈类型只能是第 $i-1$ 个人和第一个人的舞蹈类型中的一种,即共有 $2$ 种选择。 综上所述,递推式为: $$f[i]=\begin{cases} (n-2)\times f[i-1] + 2\times f[i-2], & a[i]\neq a[i-1] \\ (n-1)\times f[i-1], & a[i]=a[i-1] \end{cases}$$ 对于第二组,最后一个人的舞蹈类型只能是除了第一个人的所有舞蹈类型,即共有 $n-1$ 种选择。 因此我们可以得到最终的方案数: $$ans=(n-1)\times f[n-1]$$ 代码 时间复杂度 $O(n)$ 空间复杂度 $O(n)$
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值