1562: [NOI2009]变换序列
Description
Input
Output
Sample Input
5
1 1 2 2 1
Sample Output
1 2 4 0 3
【解题报告】
题目大意:有一个n的排列,对其中每个元素进行加di mod n或者是减di加n后mod n的操作后,变成一个新的序列T,要你求最小字典序的T。
如果只要判断合法性,这就是个裸的二分图匹配问题,将A中的每个Ai与T中的数字(Ai+di) mod n,(Ai−di+n) mod n连边,然后判断二分图是否能完全匹配即可。不过如果要求字典序最小的T也很简单,我们只需要保证邻接表中的每条边按照它们的终点升序排序,那么在二分图匹配时,如果没有增广的情况,每个x侧点选择的y侧点一定都是最小的。我们再从x侧点的最后一个点开始,从后往前进行增广,便能使得前面的点找不到匹配的y侧点时,后面的点会由最小的y侧对应匹配点变成大一点点的匹配点,而前面的点就能选择更小的那个匹配点了,这样保证前面的点找到的匹配点比后面的点找到的匹配点更优,便能满足最小字典序的要求了
注意卡输出。
代码如下:
/**************************************************************
Problem: 1562
User: onepointo
Language: C++
Result: Accepted
Time:40 ms
Memory:1368 kb
****************************************************************/
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
#define N 20010
int n;
int cnt,head[N],vis[N],match[N];
struct Edge{int to,nxt;}e[N<<1];
void adde(int u,int v)
{
e[++cnt].to=v;
e[cnt].nxt=head[u];
head[u]=cnt;
}
bool dfs(int u,int flag)
{
for(int i=head[u];~i;i=e[i].nxt)
{
int v=e[i].to;
if(vis[v]==flag) continue;
vis[v]=flag;
if(match[v]==-1||dfs(match[v],flag))
{
match[v]=u;
match[u]=v;
return 1;
}
}
return 0;
}
int main()
{
cnt=0;
memset(head,-1,sizeof(head));
memset(match,-1,sizeof(match));
scanf("%d",&n);
for(int i=0;i<n;++i)
{
int d;scanf("%d",&d);
int t1=max((i+d)%n,(i-d+n)%n);
int t2=min((i+d)%n,(i-d+n)%n);
adde(i+n,t1);adde(i+n,t2);
}
for(int i=n-1;i>=0;--i)
{
if(match[i+n]==-1&&!dfs(i+n,i+n))
{
puts("No Answer");
return 0;
}
}
for(int i=n;i<(n<<1);++i)
{
printf("%d",match[i]);
printf((i==(n<<1)-1)?"\n":" ");
}
return 0;
}