「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;
}