
题解
把每个位置上的人都看做是一个节点,然后如果我们能把这种换位置的关系表示出来,我们就可以把每个点拆成两个x,一个从S流进1,一个流出1到T
那么这么表示出这种关系呢
首先对于一个环上的移动,我们可以把相邻的两个点之间连一条费用1的双向边
那么桌子之间的关系呢
我们可以将每个桌子再创一个新点,这个点向这个桌子上的每个位置连一条边,然后呢,我们构造两*m个线段树,表示这个位置上的,往左/右,来辅助建图(为什么必须分开呢?因为在建边的时候的求值是跟左右相关的)
然后就这样了。
这个费用流最好是用原始对偶算法
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
#define lch x<<1
#define rch x<<1|1
using namespace std;
typedef long long ll;
const int N=60005,M=600005;
const int INF=0x3f3f3f3f;
struct node{
int u,v,cost,cap,nxt;
}edge[M];
int head[N],mcnt=1;
int cur[N];
void add_edge(int u,int v,int cap,int cost){
mcnt++;
edge[mcnt].u=u;
edge[mcnt].v=v;
edge[mcnt].cap=cap;
edge[mcnt].cost=cost;
edge[mcnt].nxt=head[u];
head[u]=mcnt;
}
void add(int u,int v,int cap,int cost){
add_edge(u,v,cap,cost);
add_edge(v,u,0,-cost);
}
queue<int>q;
int dist[N];
bool inqueue[N],vis[N];
int ans;
int S,T;
int ncnt;
bool spfa(){
memset(dist,0x3f,sizeof dist);
memcpy(cur,head,sizeof cur);
dist[T]=0;
q.push(T);
inqueue[T]=true;
while(!q.empty()){
int u=q.front();
q.pop();
inqueue[u]=0;
for(int i=head[u];i;i=edge[i].nxt){
int v=edge[i].v,w=edge[i].cost;
if(edge[i^1].cap){
if(dist[v]>dist[u]-w){
dist[v]=dist[u]-w;
if(!inqueue[v]){
inqueue[v]=1;
q.push(v);
}
}
}
}
}
return dist[S]<INF;
}
int dfs(int u,int C){
if(u==T)
return C;
vis[u]=true;
int flow=0;
for(int &i=cur[u];i;i=edge[i].nxt){
int v=edge[i].v,w=edge[i].cost,c=edge[i].cap;
if(c&&!vis[v]&&dist[v]==dist[u]-w){
int f=dfs(v,min(C,c));
edge[i].cap-=f;
edge[i^1].cap+=f;
C-=f;
flow+=f;
ans+=f*w;
if(!C)
break;
}
}
vis[u]=false;
return flow;
}
int Mincost_maxflow(){
int flow=0;
while(spfa()){
flow+=dfs(S,INF);
}
return flow;
}
int num[305][15];
struct Segtree{
int id[N];
void Build(int x,int l,int r,int f,int q){
id[x]=++ncnt;
if(l==r){
add(ncnt,num[l][q],INF,0);
return ;
}
int mid=(l+r)>>1;
Build(lch,l,mid,f,q);
Build(rch,mid+1,r,f,q);
if(f)
add(id[x],id[lch],INF,2*(r-mid)),
add(id[x],id[rch],INF,0);
else
add(id[x],id[lch],INF,0),
add(id[x],id[rch],INF,2*(mid-l+1));
}
void Query(int x,int l,int r,int pl,int pr,int f,int z,int o){
if(pl<=l&&r<=pr){
if(f)
add(z,id[x],1,2*(o-r));
else
add(z,id[x],1,2*(l-o));
return ;
}
int mid=(l+r)>>1;
if(pl<=mid)
Query(lch,l,mid,pl,pr,f,z,o);
if(pr>mid)
Query(rch,mid+1,r,pl,pr,f,z,o);
}
}T1[11],T2[11];
int n,m;
int L[305][15],R[305][15];
int main()
{
scanf("%d%d",&n,&m);
S=++ncnt,T=++ncnt;
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++){
num[i][j]=++ncnt;
add(ncnt,T,1,0);
}
for(int j=1;j<=m;j++){
T1[j].Build(1,1,n,0,j);
T2[j].Build(1,1,n,1,j);
}
if(m>1)
for(int i=1;i<=n;i++){
for(int j=1;j<m;j++)
add(num[i][j],num[i][j+1],INF,1),
add(num[i][j+1],num[i][j],INF,1);
add(num[i][m],num[i][1],INF,1),
add(num[i][1],num[i][m],INF,1);
}
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++){
scanf("%d",&L[i][j]);
L[i][j]++;
}
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++){
scanf("%d",&R[i][j]);
R[i][j]++;
}
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++){
ncnt++;
add(S,ncnt,1,0);
if(R[i][j]<=i)
T2[j].Query(1,1,n,L[i][j],R[i][j],1,ncnt,i);
else if(L[i][j]>=i)
T1[j].Query(1,1,n,L[i][j],R[i][j],0,ncnt,i);
else{
T2[j].Query(1,1,n,L[i][j],i,1,ncnt,i);
T1[j].Query(1,1,n,i+1,R[i][j],0,ncnt,i);
}
}
int flow=Mincost_maxflow();
if(flow!=n*m)
puts("no solution");
else
printf("%d\n",ans);
}