记忆排列题目分析
<h2 id="题目概述">题目概述</h2><p>给你一个排列 <span class="math inline">\(p\)</span>,共有 <span class="math inline">\(n\)</span> 个元素,你可以选择两个数 <span class="math inline">\(i,j\)</span>,然后将 <span class="math inline">\(p_i\)</span> 移动到位置 <span class="math inline">\(j\)</span>,这个过程需要花费 <span class="math inline">\(i+j\)</span> 的代价,问你通过这些操作过后所能使 <span class="math inline">\(p\)</span> 变为降序的最小代价。</p>
<h2 id="思路">思路</h2>
<p>变成降序似乎不是我们所擅长的,我们先转化为变成升序,这个是容易的只需要令 <span class="math inline">\(p_i=n-p_i+1\)</span> 即可。</p>
<p>我们先考虑暴力的做法,总结出来一些性质:</p>
<ul>
<li>
<p>每个数显然只能移动一次,如果移动了两次还不如一步到位。</p>
</li>
<li>
<p>按照从大到小的顺序移动这些数比按照其他顺序移动更好。</p>
</li>
</ul>
<p>因此我们可以得到 <span class="math inline">\(\mathcal{O}(n2^n)\)</span> 的暴力。</p>
<p>然后我们继续思考,可以让一些点不动,然后进行 <span class="math inline">\(dp\)</span>。</p>
<p>假设我们从后往前处理到了权值为 <span class="math inline">\(i\)</span> 的点,令其原始位置为 <span class="math inline">\(pos_i\)</span>,现在我们假设移动前的位置为 <span class="math inline">\(x\)</span>,移动后的位置为 <span class="math inline">\(y\)</span>。</p>
<p>我们令 <span class="math inline">\(x=a + b\)</span>,其中 <span class="math inline">\(a\)</span> 表示在 <span class="math inline">\(i\)</span> 的前面(在原始序列当中),且权值比 <span class="math inline">\(i\)</span> 要小的点的个数加上 <span class="math inline">\(1\)</span>(需要求得是排名,根据题目代价),这些点没有处理过,一定是在 <span class="math inline">\(i\)</span> 移动前的位置的前面的。而 <span class="math inline">\(b\)</span> 表示在 <span class="math inline">\(i\)</span> 的前面(在原始序列当中),且权值比 <span class="math inline">\(i\)</span> 要大的点且是不移动的点的个数,这些点是处理过的,然而我们并不知道,所以先不管,之后处理。</p>
<p>同理,<span class="math inline">\(y\)</span> 也可以通过 <span class="math inline">\(a\)</span> 的类似计算得到。</p>
<p>通过这个分析,我们可以设 <span class="math inline">\(f_{i,j}\)</span> 表示处理到权值为 <span class="math inline">\(i\)</span> 的点,最小不动点的位置为 <span class="math inline">\(j\)</span> 的最小代价。</p>
<p>然后分以下情况讨论:</p>
<h3 id="当前我要移动这个-">当前我要移动这个 <span class="math inline">\(i\)</span></h3>
<p>那么显然的,无论我们是在 <span class="math inline">\(j\)</span> 的前面还是后面,一定至少挪到 <span class="math inline">\(j\)</span> 前面,否则就不可能使其升序。</p>
<p>那么我们就可以推出来其中一个的状态转移方程:</p>
<p></p><div class="math display">\+1+\sum_{k<j}+1
\]</div><p></p><p>为什么在 <span class="math inline">\(k<j\)</span> 时是 <span class="math inline">\(\)</span> 呢?</p>
<p>比如说:<span class="math inline">\(\)</span> 中,假设 <span class="math inline">\(5\)</span> 是不动点,那我处理到 <span class="math inline">\(2\)</span> 的时候算他要到达的目的地就是就是位置 <span class="math inline">\(2\)</span>,因为 <span class="math inline">\(3,4\)</span> 比他大先处理,所以就已经紧贴着 <span class="math inline">\(5\)</span> 了。</p>
<h3 id="我不移动这个-">我不移动这个 <span class="math inline">\(i\)</span></h3>
<p>显然 <span class="math inline">\(j\)</span> 一定大于 <span class="math inline">\(pos_i\)</span>,否则不可能成立。</p>
<p>那我们试图通过,两个不动点 <span class="math inline">\(pos_i,j\)</span> 能否求出 <span class="math inline">\(b\)</span>,使得其的费用提前。</p>
<p>我们发现当我们不移动 <span class="math inline">\(i\)</span> 时,那么位于 <span class="math inline">\(\)</span> 的比 <span class="math inline">\(i\)</span> 小的点一定要动!而且要动到 <span class="math inline">\(pos_i\)</span> 的前面。</p>
<p>设 <span class="math inline">\(k\in\)</span> 那么如果 <span class="math inline">\(p_k<i\)</span>,那么就要动,对其的贡献为 <span class="math inline">\(i-p_k\)</span>。</p>
<p>至于为什么呢?咳咳。</p>
<p>我们举个例子:<span class="math inline">\(\)</span>,然后我们就 <span class="math inline">\(7\)</span> 的位置讨论(如果比 <span class="math inline">\(6\)</span> 小的话肯定在前面),固定 <span class="math inline">\(6,10\)</span> 两个数字,讨论一下比 <span class="math inline">\(5\)</span> 小的 <span class="math inline">\(2,4\)</span>。</p>
<h4 id="假设--在--的前面">假设 <span class="math inline">\(5\)</span> 在 <span class="math inline">\(6\)</span> 的前面。</h4>
<p>那么类似于 <span class="math inline">\(\)</span>,那么我们的 <span class="math inline">\(4\)</span> 是不是没有算 <span class="math inline">\(5,6\)</span> 两个比他大的数到他的初始位置 <span class="math inline">\(x\)</span> 中,而 <span class="math inline">\(2\)</span> 是不是没有算 <span class="math inline">\(3,5,6\)</span> 三个比他大的数(这是原本在前面的)还有在他后面的 <span class="math inline">\(4\)</span>(由于先进行操作,所以 <span class="math inline">\(4\)</span> 又到他前面了)。</p>
<p>如果说 <span class="math inline">\(4\)</span> 在 <span class="math inline">\(10\)</span> 后面的话,那么已经算过贡献了,故不用再次计算。</p>
<h4 id="假设--在--后面">假设 <span class="math inline">\(5\)</span> 在 <span class="math inline">\(6\)</span> 后面。</h4>
<p>跟计算 <span class="math inline">\(2\)</span> 时一样,<span class="math inline">\(5\)</span> 就会先跑到前面去。</p>
<p>于是我们不难得出这里少了的贡献是 <span class="math inline">\(i-p_k\)</span>。</p>
<p>但是为什么要加到 <span class="math inline">\(f_{i,pos_i}\)</span> 之中呢?其实无所谓,到最后都会执行到 <span class="math inline">\(f_{1,x}\)</span>,然后前者只跟他的下一个有关,因此,不需要细究到某一次操作当中,只需要考虑总体情况的大小即可。</p>
<p>于是我们就得到了:</p>
<p></p><div class="math display">\}\max(i-p_k,0)
\]</div><p></p><p>两者结合便是正道题目的状态转移方程,其时间复杂度为 <span class="math inline">\(\mathcal{O}(n^2)\)</span>。</p>
<h2 id="代码">代码</h2>
<pre><code class="language-cpp">#include <iostream>
#include <cstdio>
#include <stdlib.h>
#include <cstring>
#include <algorithm>
#include <vector>
#define int long long
#define N 2005
using namespace std;
int n,p,f,pos;
signed main(){
cin >> n;
for (int i = 1;i <= n;i ++) scanf("%lld",&p),p = n + 1 - p,pos] = i;
memset(f,0x3f,sizeof f);
f = 0,p = n + 1;
pos = n + 1;
for (int i = n;i;i --) {
int c1 = 1,c2 = 1;
for (int j = 1;j < pos;j ++) c1 += (p < i);
for (int j = 1;j <= n + 1;j ++) {
c2 += (p < i);
f = min(f,f + c1 + c2);
}
int sum = 0;
for (int j = pos + 1;j <= n + 1;j ++) {
f] = min(f],f + sum);
if (i > p) sum += i - p;
}
}
int ans = 1e18 + 7;
for (int i = 1;i <= n + 1;i ++) ans = min(ans,f);
cout << ans;
return 0;
}
</code></pre><br><br>
来源:https://www.cnblogs.com/high-sky/p/19012663
頁:
[1]