[题解]CF1525D Armchairs

Source

[题解]CF1525D Armchairs dp

有一排座位,编号 1   n 1~n 1 n,其中不超过 n 2 \frac{n}{2} 2n的座位被人坐上了。
现在需要调整每个人的座位,每次把坐在i的人调到空位j的代价是 ∣ i − j ∣ |i-j| ij
现在需要把每个人调一次,使得原来坐了人的位置都空出来。问最小的花费是多少。
刚开始想的是贪心。用 a , b a,b a,b分别记录人和空位的下标,然后升序排序,最后从左到右依次遍历。可惜的是,这种算法只对 n n n是偶数并且出现的人正好有 n 2 \frac{n}{2} 2n时才适用。虽然错了,但这个思路很好。
从左到右,对于每一个 a i a_i ai,我们匹配 b i b_i bi(即当下最小的)。这样的话,对于每一个 a i < a j a_i<a_j ai<aj b i < b j b_i<b_j bi<bj,都会出现4种情况。
① . ①. .在这里插入图片描述
② . ②. .
在这里插入图片描述
③ . ③. .
在这里插入图片描述
这三种情况显然满足。
④ . ④. .在这里插入图片描述
可以看出,此处对于每个 a a a,选择最小的 b b b不会排除最优解。
虽然这个算法对此题不适用,但是我们仍然能够得到一些有用的结论。
对于 a i < a j a_i<a_j ai<aj a i a_i ai所选择的 b t i b_{t_i} bti必然比 a j a_j aj所选择的 b t j b_{t_j} btj小。
因此我们可以用动态规划。
d p [ i ] [ j ] dp[i][j] dp[i][j]表示用前 i i i个人去匹配前 j j j个空位的最小代价,那么我们容易得出 d p [ i ] [ j ] = m i n ( d p [ i − 1 ] [ k ] ) + ∣ a [ i ] − b [ j ] ∣ , 1 ≤ k < b [ j ] dp[i][j] = min\left(dp\right[i-1][k]) + \left|a[i]-b[j]\right|,1\le k<b[j] dp[i][j]=min(dp[i1][k])+a[i]b[j],1k<b[j]
对于 m i n ( d p [ i − 1 ] [ k ] ) min\left(dp[i-1][k]\right) min(dp[i1][k]),它是一个前缀最小值,我们可以用一个 m i n x minx minx数组记录。这样时间复杂度就从 O ( n 3 ) O(n^3) O(n3)降到了 O ( n 2 ) O(n^2) O(n2)
有些小的坑点都写在代码段里了。

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<string>
#include<cmath>
#include<algorithm>
#include<vector>
#include<map>
#include<queue>
#include<stack>
#define ll long long
#define pii pair<int,int>
using namespace std;

const double eps = 1e-10;
const double pi = acos(-1.0);
const int maxn = 5010;

int n;
int x[maxn];
int a[maxn],b[maxn];
ll dp[maxn][maxn];
ll minx[maxn][maxn];
void solve(){
    
      
	scanf("%d",&n);
	for(int i = 1; i <= n; i++){
    
      
		scanf("%d",&x[i]);
		if(x[i]) a[++a[0]] = i;
		else b[++b[0]] = i;
	}
	sort(a+1,a+a[0]+1);
	sort(b+1,b+b[0]+1);
	for(int i = 1; i <= a[0]; i++){
    
      
		for(int j = i; j <= b[0]; j++){
    
      
		//注意,这里j至少从i开始,因为如果选到更加前面的数,那么就会重复,产生错误。
			dp[i][j] = minx[i-1][j-1] + (ll)abs(a[i]-b[j]);
			if(j == i) minx[i][j] = dp[i][j];
			else minx[i][j] = min(minx[i][j-1],dp[i][j]);
		}
	}
	ll ans = 1145141919810;
	for(int i = 1; i <= n; i++) if(dp[a[0]][i]) ans = min(ans,dp[a[0]][i]);
	if(ans == 1145141919810) ans = 0;//有可能一个人也没有
	printf("%lld\n",ans);
}

int main()
{
    
      
	solve();
	return 0;
}

另外,对于这个式子,其实还有其他的转移方法。我们考虑最后一个人 i i i与当前这个空位 j j j是否匹配。如果匹配,那么答案贡献就是 d p [ i − 1 ] [ j − 1 ] + ∣ a [ i ] − b [ j ] ∣ dp[i-1][j-1] + \left|a[i]-b[j]\right| dp[i1][j1]+a[i]b[j];否则不选,我们就在前 j − 1 j-1 j1个空位中选择,答案贡献即为 d p [ i ] [ j − 1 ] dp[i][j-1] dp[i][j1],故
d p [ i ] [ j ] = m i n ( d p [ i − 1 ] [ j − 1 ] + ∣ a [ i ] − b [ j ] ∣ , d p [ i ] [ j − 1 ] ) dp[i][j] = min\left(dp[i-1][j-1] + \left|a[i]-b[j]\right|, dp[i][j-1]\right) dp[i][j]=min(dp[i1][j1]+a[i]b[j],dp[i][j1])
感觉就是个LCS,为什么考场上没有想到呢(悲)