首页 > 其他分享 >Solution-P7650 [BalticOI 2007 Day 1] Ranklist Sorting(DP)

Solution-P7650 [BalticOI 2007 Day 1] Ranklist Sorting(DP)

时间:2022-11-04 22:45:42浏览次数:48  
标签:P7650 Sorting cur int 位置 pos Solution 不动点 define

容易发现一条性质:每个人最多只会被移动一次。说明人只有两种:移动的和不移动的。

考虑枚举所有不移动的人,并最优化其它人的移动顺序。最开始第 \(i\) 个人的起点为 \(i\),终点为 \(p_i\),经过操作之后第 \(i\) 个人的起点和终点位置都有可能变化,我们希望操作之后起点终点编号尽可能小。可以想到一个贪心:每次选取起点最小的点并进行操作。这样我们得到了一个 \(\mathcal{O}(n2^n)\) 的做法。一个与之对称的方法是每次选取终点最大的点,下面会提到它的用处。

尝试用动态规划优化掉指数。从前往后扫不太可行,考虑以值域为阶段。此时上面提到的第二种贪心就派上用场了,因为终点位置和值域有关,所以依照上面的贪心,可以考虑从 \(n\) 到 \(1\) 倒序排序。

定义 \(f_{i,j}\) 表示倒序考虑到 \(i\),位置最小的不动点位置为 \(j\) 的最优解。在讨论具体转移之前,我们先讨论经过若干次操作之后,权值为 \(i\) 的人的实际位置。一个事实是:所有没有被操作的人的相对顺序是不变的,这部分的贡献是权值为 \(i\) 的人的位置前面所有未被操作的点。另一部分的贡献是位置在 \(i\) 之前的所有不动点的前的连续递增子段的长度之和(因为我们在排序过程中会将与不动点值域连续的所有动点接在不动点的前面)。

接下来我们讨论如何将贡献在状态转移 \(f_{i,j}\) 的状态中表示。按照 \(i\) 是否为不动点,分两种情况讨论:

若权值为 \(i\) 的人是动点,则贡献是它的实际位置加上以 \(j\) 为结尾的连续子段的起始位置(因为我们会将动点接在不动点的前面)的实际位置。依照我们上面的思路计算贡献。后者是简单的,因为后者的贡献没有受到到不动点的影响(因为 \(j\) 是位置最前的不动点),只需要计算 \(j\) 前所有还未被考虑过的点的个数即可,写成公式是 \(1+\sum\limits_{k<j}[p_k<i]\)。前者则比较复杂,因为除了未被考虑过的点,它还受到位置在其之前的不动点的影响。由于不动点的放置情况在状态中被抹去了,所以在此时计算这个贡献是困难的,我们不妨将它提前,在确定不动点的过程中确定这部分的贡献。

若权值为 \(i\) 的人是不动点,那么足以确定的是,假设 \(i\) 在原排列中的位置是 \(pos_i\),那么在区间 \([pos_i+1,j-1]\) 内的所有点都是动点,即它们都需要计算上面提到的不动点的贡献。对于 \(k\in[pos_i+1,j-1]\),所有权值在区间 \([p_k+1,i]\) 的人都在它前面的不动点的连续子段中出现过,可以得到这部分的贡献为 \(\max\{0,i-p_k\}\)。

综上,不难得到转移方程:

  • \(f_{i,j}\gets f_{i+1,j}+c(k,i)+c(pos_i,i)\),其中 \(c(x,y)\) 表示 \(1+\sum\limits_{i<x}[p_i<y]\)。
  • \(f_{i,pos_i}\gets f_{i+1,j}+\sum\limits_{pos_i<k<j}\max\{i-p_k,0\}\)。

直接转移即可,时间复杂度 \(\mathcal{O}(n^2)\)。

#include<bits/stdc++.h>
#define ll long long
#define ull unsigned long long
#define db double
#define ldb long double
#define pb push_back
#define mp make_pair
#define pii pair<int, int>
#define FR first
#define SE second
using namespace std;
inline int read() {
    int x = 0; bool op = 0;
    char c = getchar();
    while(!isdigit(c))op |= (c == '-'), c = getchar();
    while(isdigit(c))x = (x << 1) + (x << 3) + (c ^ 48), c = getchar();
    return op ? -x : x;
}
const int N = 1e3 + 10;
const int INF = 1e9;
int n;
int a[N], f[N][N], p[N], pos[N], nxt[N], inc[N];
bool chkmin(int &a, int b) {return (b < a ? a = b, true : false);}
vector<pii> st;
void dfs(int cur, int now) {
    if(cur > n)return ;
    if(now != pos[cur]) {
        dfs(cur + 1, now);
        int p1 = 1, p2 = 1;
        for(int i = 1; i < pos[cur]; i++)p1 += (p[i] < cur);
        for(int i = 1; i <= now; i++)p2 += (p[i] < cur);
        st.pb(mp(p1 + inc[cur], p2));
    }
    else {
        dfs(cur + 1, nxt[cur]);
        for(int i = pos[cur] + 1; i < nxt[cur]; i++) {
            if(p[i] < cur)inc[p[i]] += cur - p[i];
        }
    }
    return ;
}
int main() { 
    freopen("in.txt", "r", stdin);
    freopen("out.txt", "w", stdout);
    n = read();
    for(int i = 1; i <= n; i++)a[i] = read();
    for(int i = 1; i <= n; i++)pos[i] = i;
    sort(pos + 1, pos + 1 + n, [&](int x, int y) {
        return a[x] > a[y];
    });
    for(int i = 1; i <= n; i++)p[pos[i]] = i;
    memset(f, 0x3f, sizeof(f));
    p[n + 1] = n + 1; pos[n + 1] = n + 1; f[n + 1][n + 1] = 0;
    for(int i = n; i; i--) {
        int p1 = 1, p2 = 1;
        for(int j = 1; j < pos[i]; j++)p1 += (p[j] < i);
        for(int j = 1; j <= n + 1; j++) {
            p2 += (p[j] < i);
            chkmin(f[i][j], f[i + 1][j] + p1 + p2);
        }
        int sum = 0;
        for(int j = pos[i] + 1; j <= n + 1; j++) {
            if(chkmin(f[i][pos[i]], f[i + 1][j] + sum))nxt[i] = j;
            sum += max(0, i - p[j]);
        }
    }
    int ans = INF, now = 0;
    for(int i = 1; i <= n + 1; i++) {
        if(chkmin(ans, f[1][i]))now = i;
    }
    dfs(1, now);
    printf("%d\n", st.size());
    for(auto x : st)printf("%d %d\n", x.FR, x.SE);
    return 0;
}

标签:P7650,Sorting,cur,int,位置,pos,Solution,不动点,define
From: https://www.cnblogs.com/yllcm/p/16859297.html

相关文章