题意:据我们所知,DZY喜欢玩游戏,一天DZY决定去玩一个\(n * m\)的矩阵。为了更加精准,他决定去修改这个矩阵k次。每次修改是如下的两个操作:
1.选择矩阵中的一行,获得这一行中的数,将这一行上的每一个数减去p。
2.选择矩阵中的一列,获得这一列中的数,将这一列上的每一个数减去p。

分析:经典题。有道类似的贪心题目和这个很像。两个决策会相互影响,我们分开来结算。如果选择了行i次,那么选择了列则是k - i次,我们枚举这个i,那么就可以结算出来,我们先全部选行,再全部选列,那么就需要减去i * (k - i) * p这个有影响的贡献,因为我们选择了i行,那么行上的每一格数都会被减去p,对于列则是i * p,然后乘以(k - i)。我们怎么算出选行和选列的贡献呢?我们可以用两个优先队列去维护,每次取出一行的数q,然后再插入q - m * p,表示新生成的状态。(状态生成)

#include <iostream>
#include <cstdio>
#include <cstring>
#include <vector>
#include <queue>
#include <algorithm>

using namespace std;
using LL = long long;
const int N = 2005;
const int M = 1000005;
LL a[N][N];

//大根堆
priority_queue<LL> r, c;

LL maxc[M], maxr[M];
int main()
{	
	LL n, m, k, p;
	cin >> n >> m >> k >> p;

	for (int i = 1; i <= n; ++i)
	{
		LL sumr = 0;
		for (int j = 1; j <= m; ++j)
		{
			scanf("%lld", &a[i][j]);
			sumr += a[i][j];
		}
		r.push(sumr);
	}

	for (int i = 1; i <= m; ++i)
	{
		LL sumc = 0;
		for (int j = 1; j <= n; ++j)
		{
			sumc += a[j][i];
		}
		c.push(sumc);
	}

	for (int i = 1; i <= k; ++i)
	{
		LL q = r.top();
		r.pop();
		maxr[i] = maxr[i - 1] + q;
		//生成q - m * p的新状态
		r.push(q - m * p);
	}

	for (int i = 1; i <= k; ++i)
	{
		LL q = c.top();
		c.pop();
		maxc[i] = maxc[i - 1] + q;
		//生成q - n * p的新状态
		c.push(q - n * p);
	}

	LL res = -1e15;
	for (int i = 0; i <= k; ++i)
	{
		LL tmp = maxc[i] + maxr[k - i] - (LL)i * (k - i) * p;
		res = max(res, tmp);
	}

	printf("%lld\n", res);


	return 0;
}