[Luogu 1963] NOI2009 变换序列

<题目链接>


先%Dalao's Blog

建图相当玄学,不过理解大约也那么奇怪。

题里面对D(x,y)的定义那一长句,一开始没看明白,但实际会发现是一个环,而对于每一个点u,符合给定距离的点都有且只有2个(v1 && v2),连u->v1 && u->v2。

对于链式前向星选手,连边的时候注意先连终点序号大的,这样才能保证遍历时从小到大。

为什么要做这个操作呢?因为要求输出字典序最小的解,就必须保证较小的点优先匹配较小的点。

匈牙利算法的过程,总是通过调整先前匹配的点,而使当前点尽量不动

所以,匈牙利算法倒序跑,增广时优先选小的点,这样就能够保证「越小的点越能匹配较小的点」,从而实现字典序最小化。(贪心思想)

最终输出X部每个点的匹配点即可。

#include <algorithm>
#include <cstdio>
#include <cstring>
using namespace std;
const int MAXN=20010,MAXM=20010;
bool vis[MAXN];
int n,cnt,ans,head[MAXN],match[MAXN];
struct edge
{
int nxt,to;
}e[MAXM];
int Solve(int x)
{
if(x<0)
x+=n;
if(x>=n)
x-=n;
return x+n;
}
void AddEdge(int u,int v)
{
e[++cnt].nxt=head[u],e[cnt].to=v,head[u]=cnt;
}
void AddEdges(int u,int x)
{
int v1=Solve(u+x),v2=Solve(u-x);
if(v1<v2)
swap(v1,v2);
AddEdge(u,v1),AddEdge(u,v2);
}
bool DFS(int u)
{
for(int i=head[u],v;i;i=e[i].nxt)
if(!vis[v=e[i].to])
{
vis[v]=1;
if(!match[v] || DFS(match[v]))
{
match[u]=v,match[v]=u;
return 1;
}
}
return 0;
}
bool Hungary(void)
{
for(int i=n-1;i>=0;--i)
if(!match[i])
{
memset(vis,0,sizeof vis);
ans+=DFS(i);
}
return n==ans;
}
int main(int argc,char *argv[])
{
scanf("%d",&n);
for(int i=0,x;i<n;++i)
{
scanf("%d",&x);
AddEdges(i,x);
}
if(Hungary())
for(int i=0;i<n;++i)
printf("%d ",match[i]-n);
else
printf("No Answer");
putchar('\n');
return 0;
}
05-06 17:56