[BZOJ 1562][NOI 2009]变换序列(二分图匹配)

题目链接

http://www.lydsy.com/JudgeOnline/problem.php?id=1562

题目大意

有一个 n 序列 A ,对于任意的 Ai1<=i<=n,1<=Ai<=n,1<=i,j<=n,i!=jAi!=Aj 。对其中每个元素进行加 di mod n 或者是减 di n mod n 的操作后,变成一个新的序列 T ,要你判断这样的 T 是否存在,以及最小字典序的 T

题目思路

如果只要判断合法性,这就是个裸的二分图匹配问题,将 AAi T(Ai+di) mod n , (Aidi+n) mod n 连边,然后判断二分图是否能完全匹配即可。不过如果要求字典序最小的 T 也很简单,我们只需要保证邻接表中的每条边按照它们的终点升序排序,那么在二分图匹配时,如果没有增广的情况,每个x侧点选择的y侧点一定都是最小的。我们再从x侧点的最后一个点开始,从后往前进行增广,便能使得前面的点找不到匹配的y侧点时,后面的点会由最小的y侧对应匹配点变成大一点点的匹配点,而前面的点就能选择更小的那个匹配点了,这样保证前面的点找到的匹配点比后面的点找到的匹配点更优,便能满足最小字典序的要求了(说起来很绕口额。。。大家自己意识流一下便能明白为什么这是正确的了)
然后很无脑地贡献了4发PE,坑爹的BZOJ居然不能输出行末空格和文末换行

代码

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <algorithm>
#include <vector>

#define MAXE 1100000
#define MAXV 31000

using namespace std;

int n;
int pre[MAXV];
bool visit[MAXV];
vector<int>G[MAXV];

bool dfs(int u)
{
    for(int i=0;i<(int)G[u].size();i++)
    {
        int v=G[u][i];
        if(!visit[v])
        {
            visit[v]=true;
            if(pre[v]==-1||dfs(pre[v]))
            {
                pre[v]=u;
                pre[u]=v;
                return true;
            }
        }
    }
    return false;
}

int main()
{
    memset(pre,-1,sizeof(pre));
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
    {
        int d;
        scanf("%d",&d);
        int a=i-d,b=i+d;
        if(a<=0) a+=n;
        if(b>n) b-=n;
        a+=n,b+=n; //与原有的1~n的数字区分开
        if(a>b) swap(a,b); //保证a<b,这样邻接表里的边的终点才能是字典序
        G[i].push_back(a),G[i].push_back(b);
    }
    for(int i=n;i>=1;i--) //注意是从后往前做二分图匹配
    {
        memset(visit,false,sizeof(visit));
        if(!dfs(i))
        {
            printf("No Answer");
            return 0;
        }
    }
    for(int i=1;i<n;i++)
        printf("%d ",pre[i]-n-1);
    printf("%d",pre[n]-n-1);
    //printf("\n");
    return 0;
}

你可能感兴趣的:([BZOJ 1562][NOI 2009]变换序列(二分图匹配))