zoukankan      html  css  js  c++  java
  • 【NOI2009】Bzoj1562&Codevs1843 变换序列

    Position:


    List

    Description

      bzoj1562

    Input

    输入文件 transform.in 的第一行包含一个整数 N,表示序列的长度。接下来的
    一行包含 N 个整数 D i ,其中 D i 表示 i 和 T i 之间的距离。

    Output

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

    Sample Input

     5
    1 1 2 2 1

    Sample Output

    1 2 4 0 3

    HINT

    30%的数据中 N≤50;
    60%的数据中 N≤500;
    100%的数据中 N≤10000。

    Solution

    观察题中给的式子,发现每个位置可以填两个数,但只能填一个数,求是否每个位置可以填上一个不重复的0~n-1的数,并保证字典序最小,也就是填在前面的数要尽量小。
    最小字典序的匈牙利,贪心发现可以倒着来跑匈牙利,保证对于当前位置的数尽量小,替换掉的是后面的数,后面的数也保证了尽量小。而正着来后面的数可能会替换掉之前的数,不满足字典序最小。

    官方题解%莫队

    Transform解题报告
    湖南省长郡中学 莫涛
    【题目大意】
    给定n和{Ti},求一个字典序最小的0..n-1的排列{Pi},使{Pi}满足{,或指出无解。
    n<=10000。
    【解法分析】
     不难看出,所给限制即i与Pi的环上距离为Ti,故对于任意i,至多存在两个Pi(从i顺时针或逆时针移动Ti距离)满足限制。
     构建二分图模型,以0,1..n-1为X节点,0,1..n-1为Y节点,若Pi=j满足限制则由X的i节点向Y的j节点连一条边,则问题转化为判断是否存在完备匹配,若存在则求出字典序最小的匹配方案。
    【算法一】
    首先使用匈牙利算法求解,若不存在完备匹配则返回无解,否则按如下流程调整使得求得字典序最小解:

    for do
    ·if i与ai,bi(ai>bi)有边且i与ai匹配 then
    ·删去匹配边(i,ai)并暂时禁用该边
    ·令j为原来与bi匹配的点
    ·将i与bi匹配
    ·if 从j出发可以找到增广路 then
    ·保留该次增广,并在以后的增广中禁止改变匹配边(i,bi)
    ·else 还原上述改动

     该算法的核心思想即利用字典序比较的特性,从前往后尝试改小每一位,若更改后剩余部分存在可行解则永久保留该次更改,否则撤销。
    由于每个点至多有两条出边,故只在取了匹配了编号较大的点时尝试匹配编号较小的点,而剩下部分有可行解的条件为存在完备匹配,若直接用匈牙利算法求解则时间复杂度为O(n^3),但我们知道:
    对于任意残量网络,使用增广路算法均可求得最大流(最大匹配)。
    而对于任意k(k>i),k均已匹配,故只需从i出发寻找增广路即可,这样时间复杂度就是O(n^2)了,可以通过测试数据。
    【算法二】
    考虑到该图非常特殊(X节点出度至多为2),可以从图论的角度求解。
    首先,如果存在度为1的点,那么可以直接确定匹配,故可以使用队列持续删去度为1的点及其确定的匹配点及于它们相关的边,直到不存在度为1的点。
    然后,如果此时存在度为0的解,那么可以直接返回无解。
    最后,由于所有的X节点度大于1且不大于2,故度均为2;而所有Y节点度大于1且∑d(X)=∑d(Y),故所有Y节点度均为2。
    一个所有点度均为2的图必然是若干不相交的环。而一个环中只存在两种匹配方案,直接选择较小者即可。
    该算法时间复杂度仅为O(n)。
    【算法三】
    算法二虽然优秀但实现较繁琐,不妨将其与算法一结合起来。
    考虑直接使用二分图匹配,对于算法二种在第一步删去的点,必然能求出正确的匹配,解不优只会发生在环中。尝试改进匹配算法:
    从大到小增广点i,寻找增广路时优先走向编号较小的点。
    证明:
    1.对于任一环,匹配的最后结果由最后增广的点决定,由上述算法的特性知,一定会找到该环内的最优解。
    2.对于环外一点,若存在经过该环的增广路,则该环内某X节点存在连向环外的边,那么它的度数超过2,矛盾,故不会因为环外某点的增广使得该环的匹配方案改变。
    虽然该算法时间复杂度为O(n^2),但思维复杂度与编程复杂度均很低,不失为考场上的好算法。
    【小结】
    算法一具有普适性,其核心思想也广泛应用于求字典序最小方案的各种问题中;算法二充分挖掘了本题中图的特殊性,时间复杂度达到理论下界;算法三主要利用了贪心思想,非常简洁,但其证明却不是那么显然。
    值得一提的是,算法三的正确性是基于该图的特殊性的,因而缺少可推广性,即不能解决一般二分图的字典序最小的匹配,下图即一个反例:
    e 使用算法三将求出(1,1)(2,3)(3,2),而最优解显然是(1,1)(2,2)(3,3)。
    此外,本题还可以利用2-SAT模型求解,但该模型在各方面均不优于二分图模型,故略去不提。

    Code

    // <transform.cpp> - Fri Sep 23 08:09:06 2016
    // This file is made by YJinpeng,created by XuYike's black technology automatically.
    // Copyright (C) 2016 ChangJun High School, Inc.
    // I don't know what this program is.
    
    #include <iostream>
    #include <vector>
    #include <algorithm>
    #include <cstring>
    #include <cstdio>
    #include <cstdlib>
    #include <cmath>
    #define MOD 1000000007
    #define INF 1e9
    using namespace std;
    typedef long long LL;
    const int MAXN=100010;
    const int MAXM=100010;
    inline int max(int &x,int &y) {return x>y?x:y;}
    inline int min(int &x,int &y) {return x<y?x:y;}
    inline int gi() {
    	register int w=0,q=0;register char ch=getchar();
    	while((ch<'0'||ch>'9')&&ch!='-')ch=getchar();
    	if(ch=='-')q=1,ch=getchar();
    	while(ch>='0'&&ch<='9')w=w*10+ch-'0',ch=getchar();
    	return q?-w:w;
    }
    int n,k,t[5],match[MAXN];
    vector<int>b[MAXN];bool f[MAXN];
    void work(int i,int a){
        int b=i+a;
        if(b<n)t[++k]=n+b;b=i-a;
        if(b>=0)t[++k]=n+b;
    }
    inline void add(int v,int u){
        b[u].push_back(v);b[v].push_back(u);
    }
    inline bool dfs(register int x){
        if(f[x])return 0;
        int num=b[x].size();f[x]=true;
        for(int i=0;i<num;i++){
            int nex=b[x][i];
            if(match[nex]==-1||dfs(match[nex])){
                match[x]=nex;match[nex]=x;f[x]=0;return 1;
            }
        }f[x]=0;
        return 0;
    }
    void pri(){printf("No Answer");exit(0);}
    int main()
    {
    	freopen("transform.in","r",stdin);
    	freopen("transform.out","w",stdout);
        n=gi();
        for(int i=0;i<n;i++){
            k=0;int a=gi();work(i,a);
            work(i,n-a);
            sort(t+1,t+1+k);for(int o=1;o<=k;o++)add(i,t[o]);
        }
        for(int i=0;i<MAXN;i++)match[i]=-1;
        for(int i=n-1;i>=0;i--)
            if(match[i]==-1)if(!dfs(i))pri();
        for(int i=0;i<n-1;i++)printf("%d ",match[i]-n);
        printf("%d",match[n-1]-n);
    	return 0;
    }
    
    
  • 相关阅读:
    对WEB标准以及W3C的理解与认识
    px和em的区别
    数组去重的方法
    什么是CSS Hack?
    在网页中应该使用奇数还是偶数的字体大小?为什么?
    ajax的封装过程
    WebService简介及使用
    SQL 不能插入重复键-错误 主键列是自动增长
    SqlServer语句执行
    正则表达式
  • 原文地址:https://www.cnblogs.com/YJinpeng/p/5907436.html
Copyright © 2011-2022 走看看