对于0,1,…,N-1的N个整数,给定一个距离序列D0,D1,…,DN-1,定义一个变换序列T0,T1,…,TN-1使得每个i,Ti的环上距离等于Di。一个合法的变换序列应是0,1,…,N-1的一个排列,任务是要求出字典序最小的那个变换序列。
抽象成图论模型,建立一个二分图,X集合每个顶点代表0,1,…,N-1的N个整数,Y集合每个顶点为对应的N个整数。X集合的第i个顶点向其环上距离为Di的Y集合中的两个顶点连一条边。样例建图后如图1所示。
显然一个变换序列,就是二分图的一个完美匹配,关键在于如何保证字典序最小。求字典序最小解得一般方法就是尝试枚举,并转为化判定性问题。
于是方法就是,以此确定X集合每个顶点的对应点,首先尝试让其对应序号较小的顶点,然后判断剩下的图是否存在一个完美匹配(用匈牙利算法求最大匹配,判断最大匹配数是否等于X集合剩余顶点数)。如果存在,那么当前顶点对应点就是序号较小的顶点,否则就是另一个顶点。最初应先判断是否存在完美匹配,如果不存在,那么该情况无解。
假设还存在一个比当前方法求得的解T字典序更小的解V,那么对于0<=i<=N-1一定有Vi<=Ti,并且存在一个j使得Vj<Tj。因为Vj<Tj,所以X集合顶点j一定是对应的序号较大的顶点Tj,而Vj则是序号较小的顶点。由于如果j对应了Vj,剩余的顶点不存在完美匹配,所以V不是合法解,因而不存在一个比当前方法求得的解T字典序更小的解V,T是字典序最小的解。
复杂度分析
该图的边数是O(N)的,所以匈牙利算法的时间复杂度为O(N2)。由于对于每个点都要进行一次匈牙利算法,所以该算法的时间复杂度为O(N3)。在实际的测试数据中能拿到70分。
参考程序
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cmath>
using namespace std;
const int MAXN=20003,MAXM=MAXN*2;
struct edge
{
edge *next;
int t;
}*V[MAXN],ES[MAXM];
FILE *fi,*fo;
int N,EC;
int mat[MAXN],S[MAXN][2],T[MAXN];
bool vis[MAXN],lock[MAXN];
inline void addedge(int a,int b)
{
ES[++EC].next = V[a];
V[a] = ES+EC;
V[a]->t = b;
}
void init()
{
int i,t1,t2,d;
fi = fopen("transform.in","r");
fo = fopen("transform.out","w");
fscanf(fi,"%d",&N);
EC = 0;
for (i=1;i<=N;i++)
{
fscanf(fi,"%d",&d);
t1 = i + d;
if (t1>N) t1-=N;
t2 = i - d;
if (t2<1) t2+=N;
if (t1 < t2)
S[i][0] = t1,S[i][1] = t2;
else
S[i][0] = t2,S[i][1] = t1;
addedge(i,S[i][1]+N);
addedge(i,S[i][0]+N);
}
}
bool aug(int i)
{
for (edge *e=V[i];e;e=e->next)
{
int j = e->t;
if (!vis[j] && !lock[j])
{
vis[j] = true;
if (!mat[j] || aug(mat[j]))
{
mat[j] = i;
return true;
}
}
}
return false;
}
bool hungary()
{
memset(mat,0,sizeof(mat));
for (int i=1;i<=N;i++)
{
if (lock[i]) continue;
memset(vis,0,sizeof(vis));
if (!aug(i))
return false;
}
return true;
}
bool solve()
{
int i,j;
if (!hungary())
return false;
for (i=1;i<=N;i++)
{
lock[i] = true;
for (j=0;j<=1;j++)
{
if (!lock[S[i][j]+N])
{
lock[S[i][j]+N] = true;
if (hungary())
{
T[i] = S[i][j];
break;
}
lock[S[i][j]+N] = false;
}
}
}
return true;
}
void print(bool win)
{
if (win)
{
int i;
for (i=1;i<N;i++)
fprintf(fo,"%d ",T[i] - 1);
fprintf(fo,"%d\n",T[i] - 1);
}
else
fprintf(fo,"No Answer\n");
fclose(fi);
fclose(fo);
}
int main()
{
init();
print(solve());
return 0;
}