zoukankan      html  css  js  c++  java
  • bzoj1341 名次排序问题rank sorting(dp,考虑到对未来的贡献)

    QWQ啊

    这个题可以说是我目前碰到过的最难理解的dp之一了。

    题目大意:

    已知参赛选手的得分,你的任务是按照得分从高到底给出选手的排名。遗憾的是,保存选手信息的数据结构只支持

    一种操作,即将一个选手从位置i移动到位置j,该移动不改变其他选手的相对位置,即如果i>j,位置j和位置i-1

    之间的选手的位置都比原来加1,相反如果i<j,则位置i+1和位置j之间的选手的位置都比原来减一。上述移动的操

    作的代价定义为i+j,这里,位置编号从1开始。请你编程确定一个移动选手的步骤,将选手按照得分从高到低排序

    ,并使整个移动过程的代价最小。

    其中

    第一行为一个整数n(2<=n<=1000),表示选手的人数;

    接下来的n行,每行一个非负整数Si( 0<=Si<=1000000),表示一个选手的得分。你可以认为每人的得分是不同的。

    求交换次数

    QAQ啊 一看这个题目,基本是没有任何思路;

    唯一想到的就是问题转换了

    把原序列按照从大到小的顺序排序,然后将他们的值,赋值为排序完数组的下标

    那么这个题就转化为用最小的代价将序列转化成1~n的排列

    然后.....gg

    这里强势安利一位dalao的论文!

    https://wenku.baidu.com/view/83d0a76925c52cc58bd6bea8.html###

    徐源盛-算法合集之《对一类动态规划问题的研究》

    首先,我们要可以先大胆猜想一下!

    1>一个人最多移动一次,如果移动则必然移动到编号比他大1的数的前面(因为往后移动会使部分人的位置减 1,从而减少此人未来的移动费用,所以编号越大的越先移动。)

    2>按标号从大到小移动(这个,,,,,显然呀感性理解一下固定好了编号大的 不就不用移动这些了呀~)

    那么,

    按编号从大到小进行操作,对于每个人 x 有两种选择:

    1. 移动到 x+1 前一个位置。

    2. 如果 x 在 x+1 前,则不移动,以后再将所有处于 x 和 x+1 之间的移动到 x 之前。

    同时,我们又能得到两个推论

    所有小于等于 x 的数的相对位置与最初的一样

    所有比 x+1 大的数都位于 x+1 后面

    这两条非常关键!!!!

    我们考虑dp状态是

    f[i][j]表示i这个数移动到原序列的j位置且i+1~n都在i后面的最小代价

    对于一个i i+1的位置我们并不能确定,所以是需要枚举的!

    首先首先

    我们定义一个count函数 count(p,x)表示在原序列的第1~p个数中,有多少个比x小的数

    那么由刚才那两个推论,就可以得知:

    x 的实际位置为最开始 1 到 p1 中比 x 小的数的个数加 1(加上自己)

    接下来,我们就可以分类讨论了

    对于一个数x,一定是主动移动(移到x+1前面),或者被动移动(将x和x+1之间的数都移走)

    主动移动的话:

    pos[i]是表示i这个数在原序列的位置是多少

    首先考虑当   j>pos[i]

    f[i][j]=f[i+1][j]+count(pos[i],i)+count(j,i+1)-1 (-1是因为要移动到x+1的前一位的代价)

    而当 j<pos[i]时

    f[i][j]=f[i+1][j]+count(pos[i],i)+1 + count(j,i+1)-1  (这里+1是因为因为i+1在i前,所以i的实际位置就是count(pos[i],i)+1)

    (反正我是这么理解的QWQ论文里也没给出一个详细的解释)

    int aa=count(pos[i],i);
            int bb=1;
            for(int j=1;j<=pos[i]-1;j++)
            {
                if (a[j].x<i+1) bb++;
                if (f[i+1][j]<f[1005][1005]) f[i][j]=f[i+1][j]+aa+bb; //之所以不用减1是因为此时i的位置是aa+1并非aa 然后+1和-1正好抵消了 
            }
            bb++;
            for (int j=pos[i]+1;j<=n;j++)
            {
                if (a[j].x<i+1) bb++;
                if (f[i+1][j]<f[1005][1005]) f[i][j]=f[i+1][j]+aa+bb-1;
            }
    
    

    被动移动的话:

    由于一个性质就是:

    当前决策对未来

    “行动”的费用影响只与当前决策有关

    比如在两男两女中选一男一女去执行任务,已知每个人的效率,希望总效率最高。并且男 A 如果被选,所有女生的效率加 7,如果男 B 被选,所有女生的效率减 7。在第一阶段选男 A 还是男 B 对第二阶段女生的效率有不同的影响,可以将对女生的影响当做男生的“自身魅力”,即把男 A 的效率加 7,男 B 的效率减7。而我们实际上是把第二阶段的费用在第一阶段计算了。对于这类问题我们往往将对未来“行动”的影响一并算做当前决策的费用。

    所以,当x这个数不动,那么所有位置在pos[i]+1到j-1之间的数,都需要跨越i

    那么也就是

    在j>pos[i]的基础上:

    f[i][pos[i]]=min{f[i][pos[i]], f[i+1][j]+sigma(i-s[k]) (i>s[k]) pos[i]<k<j

    因为对于一个数x(x必定小于i,可以根据推论得知),x需要跨过它而多增加的代价就是i-x(可以感性理解一下,就好比5 3 4如果5不动 那么4需要多增加1的代价,3增加2,因为3既要跨越5,又要跨越4)

    如果这个数不动的话,需要记录一下i+1的位置是哪,用一个pre去记录,便于计算最终答案

    首先看一下f[1][i]的min值 (QAQ虽然我也不知道这个是为什么)

    然后dfs进行求解

    啊,其实最后这个求解的部分,我至今都不是太理解QWQ

    上代码

    #include<iostream>
    #include<cstdio>
    #include<algorithm>
    #include<cstring>
    #include<cmath>
     
    using namespace std;
     
    inline int read()
    {
       int x=0,f=1;char ch=getchar();
       while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
       while (isdigit(ch)){x=(x<<1)+(x<<3)+ch-'0';ch=getchar();}
       return x*f;
    }
     
    const int maxn = 1010;
     
    struct Node{
        int x,id;
    };
    int f[maxn][maxn];
    int pos[maxn];
    int n;
    int pre[maxn];
    Node a[maxn];
    int cnt;
     
    bool cmp1(Node a,Node b)
    {
        return a.x>b.x;
    }
     
    bool cmp2(Node a,Node b)
    {
        return a.id<b.id;
    }
     
    int count(int p,int x)
    {
        int cnt=0;
        for (int i=1;i<p;i++)
        {
           if (a[i].x<x) cnt++;
        }
        return cnt+1;
    }
     
    void dfs(int x,int now)
    {
        if (x==n) return;
        if (pos[x]!=now)
        {
            cnt++;
            dfs(x+1,now);
        }
        else
         dfs(x+1,pre[x]);
         //cout<<1;
    }
    int main()
    {
        scanf("%d",&n);
        for (int i=1;i<=n;i++)
        {
            a[i].x=read();
            a[i].id=i;
        }
        sort(a+1,a+1+n,cmp1);
        for (int i=1;i<=n;i++){
            pos[i]=a[i].id;
            a[i].x=i;
        }
        sort(a+1,a+1+n,cmp2);
        ++n;
        a[n].x=n;
        memset(f,127/3,sizeof(f));
        f[n][n]=0;
        for (int i=n-1;i>=1;i--)
        {
            int aa=count(pos[i],i);
            int bb=1;
            for(int j=1;j<=pos[i]-1;j++)
            {
                if (a[j].x<i+1) bb++;
                if (f[i+1][j]<f[1005][1005]) f[i][j]=f[i+1][j]+aa+bb; //之所以不用减1是因为此时i的位置是aa+1并非aa 然后+1和-1正好抵消了 
            }
            bb++;
            for (int j=pos[i]+1;j<=n;j++)
            {
                if (a[j].x<i+1) bb++;
                if (f[i+1][j]<f[1005][1005]) f[i][j]=f[i+1][j]+aa+bb-1;
            }
            int tmp =0;
            for (int j=pos[i]+1;j<=n;j++)
            {
              if (f[i][pos[i]]>f[i+1][j]+tmp){
                 f[i][pos[i]]=f[i+1][j]+tmp;
                 pre[i]=j;
              }
              if (a[j].x<i) tmp+=i-a[j].x;
            }
        }
      int ans=1e9;
      int poss=0;
      for (int i=1;i<=n;i++)
      {
         if (f[1][i]<ans){
            ans=f[1][i];
            poss=i;
         }
      }
      dfs(1,poss);
      cout<<cnt;
      return 0;
    }
    
  • 相关阅读:
    (1)李宏毅深度学习-----机器学习简介
    Git命令之不得不知的git stash暂存命令
    Http2升级方案调研
    神奇的 SQL 之别样的写法 → 行行比较
    熔断机制
    限流算法
    状态机
    布隆过滤器
    负载均衡算法
    K8S Ingress
  • 原文地址:https://www.cnblogs.com/yimmortal/p/10160630.html
Copyright © 2011-2022 走看看