P1963 [NOI2009]变换序列

对于\(N\)个整数\(0, 1, \cdots, N-1,\)一个变换序列\(T\)可以将\(i\)变成\(T_i\),其中 \(T_i \in \{ 0,1,\cdots, N-1\}\)\(\bigcup_{i=0}^{N-1} \{T_i\} = \{0,1,\cdots , N-1\}\)\(\forall x,y \in \{0,1,\cdots , N-1\}\),定义\(x\)\(y\)之间的距离\(D(x,y)=min\{|x-y|,N-|x-y|\}\)。给定每个\(i\)\(T_i\)之间的距离\(D(i,T_i)\),你需要求出一个满足要求的变换序列T。如果有多个满足条件的序列,输出其中字典序最小的一个。

说明:对于两个变换序列\(S\)\(T\),如果存在\(p<Np<N\),满足对于\(i=0,1,\cdots p-1\)\(S_i=T_i\)\(S_p<T_p\),我们称\(S\)\(T\)字典序小。

输入格式:

第一行包含一个整数\(N\),表示序列的长度。接下来的一行包含\(N\)个整数\(D_i\),其中\(D_i\)表示\(i\)\(T_i\)之间的距离。

输出格式:

如果至少存在一个满足要求的变换序列\(T\),则输出文件中包含一行\(N\)个整数,表示你计算得到的字典序最小的\(T\);否则输出No Answer(不含引号)。注意:输出文件中相邻两个数之间用一个空格分开,行末不包含多余空格。

输入样例#1:
5
1 1 2 2 1
输出样例#1:
1 2 4 0 3

说明

对于\(30\%\)的数据,满足:\(N<=50\)

对于\(60\%\)的数据,满足:\(N<=500\)

对于\(100\%\)的数据,满足:\(N<=10000\)


这个题目相当优秀,它能够很好的帮你理解匈牙利算法的本质。

首先看到这个题,可以很显然的发现这是一个裸的二分图匹配问题。但是牵涉到字典序最小的话,就需要考虑其他的操作了。

最开始我考虑的方法是从前到后匹配:

  • 如果两个都没有匹配,那么选数字小的那个
  • 如果都匹配了,选择如果匹配,会产生的影响最早数字最靠后的那个
  • 如果一个匹配一个没匹配,先选没匹配的那个

写了一堆特判之后WA成沙雕,还满的一批,因为我要在匹配之前提前模拟一遍第二种,然后就多了一堆奇奇怪怪的东西。

相比之下,正解的想法就相当有趣。

匈牙利算法本身就是从前向后依次尝试匹配。所以如果想要保证在前面的数字尽可能小,那么只需要让它优先匹配标号小的节点就好。但是这里存在一个问题:如果从前到后依次匹配的话,为了保证不把前面的最优选择替换,你就必须特判很多东西。实际上,只需要从后向前匹配,前面的尽可能选最小数字,如果可以替换,把后面选择的直接替换即可。这样得到的一定是最优解。

Code:

#include<queue>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define MAXN 10010
#define _mod(x) ((x)%n+n)%n
using namespace std;
int n,T[MAXN],vis[MAXN<<1],v[MAXN][2],match[MAXN<<1];

bool dfs(int x){
//  寻找x的匹配
    for(int i=0;i<2;++i){
        if(!vis[n+v[x][i]]){
            vis[n+v[x][i]]=true;
            if(match[n+v[x][i]]==-1 || dfs(match[n+v[x][i]])){
                match[x]=n+v[x][i];
                match[n+v[x][i]]=x;
                return true;
            }
        }
    } 
    return false;
}

int main(){
    scanf("%d",&n);
    memset(match,-1,sizeof(match));
    for(int i=0;i<n;++i){
        scanf("%d",&T[i]);
        v[i][0]=_mod(i+T[i]);
        v[i][1]=_mod(i-T[i]);
        if(v[i][0]>v[i][1]){
            swap(v[i][0],v[i][1]);
        }
        //临时储存两个终点
    }
    //按位匹配
    for(int i=n-1;i>=0;--i){
        memset(vis,0,sizeof(vis));
        if(!dfs(i)){
            puts("No Answer");
            return 0;
        }
    }
    for(int i=0;i<n;++i){
        printf("%d ",match[i]-n);
    } 
} 

转载于:https://www.cnblogs.com/maomao9173/p/10017933.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值