「COCI 2009」POSLOZI - 题解

  • 前言

    原题链接 link

    这题数据有那么点水(?)。


  • 题目大意

    给一个长度为 \(N\) 的排列 \((1\le N\le 12)\) 。有 \(M\) 种允许的修改方式 \((1\le M\le \frac{N\times (N-1)}{2})\),保证修改方式不重复,每种方式用 \(L,R\) 来表示,意为你可以将下标为 \(L\) 的数与下标为 \(R\) 的数交换。

    你可以修改该排列若干次,请给出一种修改方案,使原排列变为 \(1,2,3,\ldots,N\) 。如果有多种方案,输出修改次数最少的方案。如果还有多种方案,输出任意一组即可。


  • 分析

    直接 IDA* 会被一些精心构造的数据卡掉,虽然这题没有,我们尝试换一种搜索方式。

    对交换方式建图,发现每次操作相当于交换两个相邻的点。

    考虑贪心,若一个点 \(i\) 已经到达了他该在的位置,我们贪心的希望他不在移动,把他标记住。

    固定了第 \(i\) 个点,就意味着其他点移动时候不能经过这个点。但这样可能会导致结果无解或者结果不是最优的情况。

    这意味着固定第 \(i\) 个点前,一定要先固定另外若干个点。

    所以我们考虑暴力枚举每个点固定的顺序,按照这个顺序让每个点到他应该到的地方即可。这里显然跑最短路就行。

    这样上限是 \(12!\) 的,但我们可以一边跑一边维护是否有解的情况,可以大力剪枝。

    然后加个 IDA* 就跑的飞快了。

    需要注意的是,对于每个点移动过程中,可能会出现多个最短路,此时每次优先跑第一个最短路即可。

    简单证明一下,对于路径 \(x\)\(y\) ,若有多条最短路,优先跑第一条路径。但一定存在一个排列顺序,使在第一条路径上存在一个点被提前标记,这样第一条路径就不能走了。按照之前优先程度,那个排列此时会跑第二个最短路。

    第三个最短路或以上同理。

    但这个做法常数有点大,在这里需要开 O2 才能通过。


  • 代码
#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
#include<queue>
using namespace std;
const int N=15*15*15,M=15*15*15;
const int inf=1e9;
struct node{
	int x,y;
}b[M];
struct edge{
	int v,nx;
	int id;
}e[M];
int n,m,ne,f[N],a[N],p[N],d[N],lst[N],nxt[N],iid[N];
int ansn,tot,ans[M],t[M],dis[16][16];
bool vis[N];
queue<int> q;
int solve(int s,int t)
{	
	for(int i=1;i<=n;i++)d[i]=inf,lst[i]=0;
	while(!q.empty())q.pop();
	d[s]=0;q.push(s);
	while(!q.empty())
	{	
		int u=q.front();
		q.pop();
		for(int i=f[u];i;i=e[i].nx)
		{	
			int v=e[i].v;
			if(d[v]!=inf)continue;
			if(vis[v])continue;
			d[v]=d[u]+1;
			lst[v]=u;
			iid[v]=e[i].id;
			q.push(v);
		}
	}
	return d[t];
}
void getans(int res)
{	
	if(res>=ansn)return;
	ansn=res;
	for(int i=1;i<=res;i++)
		ans[i]=t[i];
}
int geth()
{	
	int res=0;
	for(int i=1;i<=n;i++)
		res+=dis[i][p[i]];
	return res;
}
void dfs(int x,int res,int deep)
{	
	if(x==n+1){getans(res);return;}
	int h=geth();
	if(res>=deep)
	{	
		if(h==0){getans(res);return;}
		return;
	}
	if(h/2+res>=ansn)return;
	if(res>=ansn)return;
	for(int i=1;i<=n;i++)
	{	
		if(vis[i])continue;
		int cnt=solve(i,p[i]);
		if(cnt==inf)continue;
		vis[i]=1;
		for(int u=p[i];u!=i&&lst[u]!=0;u=lst[u])
		{	
			swap(p[a[u]],p[a[lst[u]]]);swap(a[u],a[lst[u]]);
			t[++tot]=iid[u];
		}
		dfs(x+1,tot,deep);
		vis[i]=0;
		solve(a[i],i);
		while(tot>res)
		{	
			swap(p[a[b[t[tot]].x]],p[a[b[t[tot]].y]]);
			swap(a[b[t[tot]].x],a[b[t[tot]].y]);
			tot--;
		}
	}
}
void read(int u,int v,int w)
{	
	e[++ne].v=v;
	e[ne].nx=f[u];
	f[u]=ne;
	e[ne].id=w;
}
int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++)
	{	
		scanf("%d",&a[i]);
		p[a[i]]=i;
	}
	for(int i=1;i<=n;i++)
		for(int j=1;j<=n;j++)
			if(i!=j)dis[i][j]=inf;
	for(int i=1;i<=m;i++)
	{	
		scanf("%d%d",&b[i].x,&b[i].y);
		if(b[i].x==b[i].y)continue;
		read(b[i].x,b[i].y,i);
		read(b[i].y,b[i].x,i);
		dis[b[i].x][b[i].y]=dis[b[i].y][b[i].x]=1;
	}
	for(int k=1;k<=n;k++)
		for(int i=1;i<=n;i++)
			for(int j=1;j<=n;j++)
				dis[i][j]=min(dis[i][j],dis[i][k]+dis[k][j]);
	int deep=0;
	while(deep<=n*n)
	{	
		ansn=inf;
		dfs(1,0,deep);
		if(ansn!=inf)break;
		deep++;
	}
	printf("%d\n",ansn);
	for(int i=1;i<=ansn;i++)
		printf("%d\n",ans[i]);
	return 0;
}
posted @ 2021-11-05 22:42  Rainy7  阅读(125)  评论(0编辑  收藏  举报