『动态规划·差分』队列

P r o b l e m \mathrm{Problem}Problem

在这里插入图片描述


S o l u t i o n \mathrm{Solution}Solution

首先考虑第一小问,问题转化为:

  • 每一行的问题互相独立。
  • c j = a i , j − a 1 , j c_j=a_{i,j}-a_{1,j}cj=ai,ja1,j,求将所有 c j c_jcj 变成一个数字的方案数。

考虑第二小问,问题转化为:

  • 固定每一列的第一个数字,并且保证这个数大于零且单调递增的情况下,将每一列的c i c_ici固定成一个数字。
  • 我们考虑DP来限制:设f i , j f_{i,j}fi,j 表示前 i iia 1 , j − a 1 , 1 = j a_{1,j}-a_{1,1}=ja1,ja1,1=j时,花费的最小值。则有:
    f i , j = f i − 1 , k + v a l ( c h a n g e   t h e   w h o l e   r o w   i n t o   t h e   s a m e ) f_{i,j}=f_{i-1,k}+val(\mathrm{change\ the\ whole\ row\ into\ the\ same})fi,j=fi1,k+val(change the whole row into the same)
    至于代价,我们只需要对差分序列求解中间的转折点,用前缀和的形式求解即可。其中该状态方程也用前缀min ⁡ \minmin 的方式优化即可。
  • 时间复杂度:O ( n V log ⁡ n ) O(nV\log n)O(nVlogn)

C o d e \mathrm{Code}Code

#include <bits/stdc++.h>

using namespace std;
const int N = 2100;

int n, m;
int a[N][N], b[N], c1[N];
int f[N][N], g[N][N], c[N];

int read(void)
{
	int s = 0, w = 0; char c = getchar();
	while (!isdigit(c)) w |= c == '-', c = getchar();
	while (isdigit(c)) s = s*10+c-48, c = getchar();
	return w ? -s : s;
}

void work1(void)
{
	long long res = 0;
	for (int i=2;i<=m;++i)
		if (a[1][i] <= a[1][i-1]) return puts("-1"), void();
	for (int i=2;i<=n;++i)
	{
		for (int j=1;j<=m;++j) b[j] = a[i][j] - a[1][j];
		sort(b+1, b+m+1);
		int mid = b[m/2+1];
		for (int i=1;i<=m;++i) res += 1LL * abs(mid - b[i]); 
	}
	cout << res << endl;
	return ;
}

void work2(void)
{
	for (int i=0;i<=max(n,m);++i)
		for (int j=0;j<=2000;++j)
			f[i][j] = g[i][j] = 1e9;
	f[1][0] = 0;
	for (int j=0;j<=2000;++j) g[1][j] = 0;
	for (int i=2;i<=m;++i)
	{
		for (int j=1;j<=n;j++)
		  c[j]=a[j][i]-a[j][1];
		sort(c+1,c+n+1);
		for (int j=1;j<=n;j++)
		  c1[j]=c1[j-1]+c[j];
		for (int j=1;j<=2000;++j)
		{
			int w = 0, k;
			if (c[n] <= j) k = n; 
			else k = upper_bound(c+1, c+n+1, j) - c - 1;
			w = j * k - c1[k] + c1[n] - c1[k] - j * (n - k);
			f[i][j] = g[i-1][j-1] + w;
			g[i][j] = min(g[i][j-1], f[i][j]);
		}
	}
	cout << g[m][2000] << endl;
	return;
}

int main(void)
{
	freopen("square.in","r",stdin);
	freopen("square.out","w",stdout);
	n = read(), m = read();
	for (int i=1;i<=n;++i)
		for (int j=1;j<=m;++j) 
			a[i][j] = read();
	work1();
	work2();
	return 0;
}

版权声明:本文为Ronaldo7_ZYB原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。