AtCoder - ABC 163 - E(思维 + 区间dp)

E - Active Infants

题意:

有 N 个小孩,第 i 个孩子的位置为 i,活跃值为 Ai,现在将 N 个小孩重新排列,每个小孩获得的开心值为 Ai 与重新排列前后位置差的乘积,求最大可能的开心值总和。

数据范围:

 2 ≤ N ≤ 2000

1 ≤ Ai​ ≤ 10^{9}

思路:

最优方案:
先考虑贪心(实际是我根本不知道咋考虑),我们应该让活跃度高的先选择位置,要使得幸福度越高,则一定分配为目前唯一的未站孩子的区间的左端点或右端点。

证明:
1. 为什么先移动最大数最优?
这是考虑到同向冲突(因为如果都是反向就可以满的话,选大小数都可以是独立的,但关键就在于同向冲突),比如 1 5 6 1 1 1 ,5和6都要向着最右边排,设6移动x,5移动y,x+y=6,所获得的收益为:6x+5y。两式结合,得出收益为 30+x 或 36-y 。
即较大数移动距离越大,较小数移动距离越小,所获得的的收益就越大,所以较大数要先与较小数移动。

2. 确定了先移动最大数最优没错后,第一思路一般是将大数移动到最远的那一端。那么如何证明只需判断大数移动到最左端/最右端即可判断出最优选法?
因为要想找与某点位置最远,该点一定在边界点上,左端点/右端点即是边界点。

区间DP:
因为每次选位置时都是选端点处,紧贴着边界放的,所以未被选择的区间有且仅有一个:区间dp。
因为先放大数是最优的,所以先将数按照活跃度倒序排列,下面所说的第 i 个数指的是排序后的下标为 i 对应的活跃值。
为了更好表示,我们不直接用区间,而是用左边已分配的数的个数和右边已分配数的个数来间接得出区间边界。

状态表示:dp[i,j] 表示将已经将 i 个数分配在左边,j 个数分配在右边时的所有方案。属性:幸福值Max。

状态计算:以下一个数,即第 k = i + j + 1 个数选择的位置是左端点/右端点为依据划分。
1. 第 k 个数选择左端点。则现在左边分配的是 i + 1 个数,右边配的是 j 个数,即 dp[i+1,j];相比于dp[i,j],第 k 个数的位置由 b[k].y 到 i + 1,幸福值增加了 a[k] * | b[k].y - (i+1) |;

2. 第 k 个数选择右端点。则现在左边分配的是 i 个数,右边配的是 j + 1 个数,即 dp[i,j+1];相比于dp[i,j],第 k 个数的位置由 b[k].y 到 n-(r+1)+1 = n - r,幸福值增加了 a[k] * | b[k].y - (n - j) |;

所以,最终答案为 dp[i,n-i] 的最大值

实现:
1. 因为先放大数是最优的,所以我们将每个孩子的活跃度以及编号另外存在二维数组b中,并按照活跃度从大到小的顺序排列.(b[i].first 表示第 i 个数的值,b[i].second表示第 i 个数排序前的位置下标)

2. 枚举 i, j 。i 表示左边分配的数的个数[0,n];j 表示右边分配的数的个数[0,n],dp即可。

3. dp结束后,枚举 i 得出 dp[i][n-i] 的最大值

Code:

#include
#include
#include
#include
#include
#include
#include
using namespace std;

#define x first
#define y second
#define int long long
const int dx[] = { 1,-1,0,0 }, dy[] = { 0,0,1,-1 };

const int N = 2010, INF = 0x3f3f3f3f, mod = 1e9 + 7;

typedef pairPII;

int n;
int a[N];
PII b[N];
int dp[N][N];

bool cmp(PII& c, PII& d)                 //按first从大到小排序
{
	return c.x > d.x;
}

void solve()
{
	cin >> n;
	for (int i = 1; i <= n; i++)
	{
		cin >> a[i];
		b[i] = make_pair(a[i], i);        //将活跃度及其编号存入b二维数组中
	}
	
	sort(b + 1, b + 1 + n, cmp);          //按照活跃度从大到小排序

	for (int i = 0; i <= n ; i++)         //枚举左边分配的数量
		for (int j = 0; j + i <= n; j++)  //枚举右边分配的数量
		{
			int k = i + j + 1;
			dp[i + 1][j] = max(dp[i + 1][j], dp[i][j] + b[k].x * abs(b[k].y - (i + 1)));
			dp[i][j + 1] = max(dp[i][j + 1], dp[i][j] + b[k].x * abs(b[k].y - (n - j)));
		}

	int ans = 0;
	for (int i = 0; i <= n; i++)
		ans = max(ans, dp[i][n - i]);      //最大幸福值即最终答案
 
	cout << ans << endl;
}

signed main()
{
	//int t;
	int t = 1;
	//cin >> t;
	while (t--)
	{
		solve();
	}

	return 0;
}

------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
吐槽:这题好好好难想啊qwq,看题解都有些懵,动态规划真难呀,菜啊www。

你可能感兴趣的:(AtCoder,算法)