Description
被保护的李明明:如果两只不持盾李明明中间的某个地方有至少一只迟钝的李明明
那么这两只李明明就是一对被保护的李明明
现在,旅鼠们会被命令交出自己手里的盾盾给它左边或者右边的旅鼠
无情的命令机器 Petr 一秒可以发出一道命令
对于所有可能的 (k) ,
求 (k) 秒内((0le kle frac{n(n-1)}{2}))最多能够让旅鼠大军里有多少对被保护的李明明?
李明明们可以脚踏很多条船。
旅鼠大队 (1le nle80)
Solution
首先我读错题目辣
一开始以为最后要变成 010010010
这样的
实际上貌似是要最大化序列中,连续的 0
段落 的 两两长度乘积之和
转化 #1
那干脆直接用这些长度来表示序列。
比如 010010010
就是表示成 1,2,2,1
。
如果让第5只旅鼠把盾给左边就变成 1,1,3,1
,再往左传就是 1,0,4,1
(也即 1,4,1
)
记 (N) 表示这种 0
段落的个数。
则操作过程中 0
段落两侧的 1
始终不变
就算段落被压扁了又出现了,两侧的 1
也是原来的那两个
转化 #2
现在要求最大化 $$Ans=sumlimits_{i=1}{N}sumlimits_{j=1}{i-1} a_i a_j$$
考虑一下怎么化简。求和表是一个三角形。
复制一下填上是缺了主对角线的正方形。所以
(egin{array}{rcl}
Ans&=&displaystylefrac{1}{2}left[left(sumlimits_{i=1}^Nsumlimits_{j=1}^N a_i a_j
ight)-sumlimits_{i=1}^N a_i^2
ight]\
&=&displaystylefrac{1}{2}left[left(sumlimits_{i=1}^N a_i
ight)^2-sumlimits_{i=1}^N a_i^2
ight]end{array})
思考意义。 (sumlimits_{i=1}^N a_i) 就是原序列中 0
的个数,为常数。
故问题到这里转化为:
最小化 (sumlimits_{i=1}^N a_i^2)
DP #1
最终的最优解就是让所有 (a_i) 的值尽可能相等。
然而对于 (k) 时的最优解就有点难做了。
(n) 很小。但是就算很小,假如一个一个枚举举盾不举盾 最坏情况最少这一步就要 (2^{40}) 也会炸的。
DP #2 设计状态
最优化,不妨考虑暴力 DP 。(n) 很小。所以 (k) 直接可以作为 DP 状态的一维。
记录一下现在到第 (j) 个 0
段落,命令了 (k) 次之后的最小的 (sumlimits_{i=1}^j a_i^2)
考虑转移。因为每次命令都是让 1
左右两边的 0
个数一边 ++
一边 --
故:只需要记录 (ell=sumlimits_{i=1}^j a_i) 就可以了。
这样左边的 0
和 1
个数都可以被确定 也就相当于确定了当前段落 1
转移后的位置。
这就足够了,是一个正确的转移。
状态: (f(k,j,ell)) 表示此时的,当前最小的 (sumlimits_{i=1}^j a_i^2)
DP #3 设计转移
无非就是枚举一下当前 0
段落右侧的 1
要走到哪里然后转移
这个不难。
我就直接写代码了,敲博客好累的(
简明扼要的正解
其实就是暴力……只要想到 DP 就可以做了
从左到右依次确定每段连续的 0
的右端 1
的最后位置。同时转移时间和答案。
剪一下枝过。
挪 1
的时候可以用前面 0
的个数代替距离,正确性显然。(独木桥一下)
注意边界。
要在 DP 的时候算出结果的话,最后应该推上最后那一段 0
并且保证枚举在末端结束
保证枚举在末端结束:首先要让枚举都能够跑到末端。
然后只需要最后输出答案的时候只考虑 (ell) 等于原序列 0
的总数的 DP 状态即可。
Code
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstdlib>
#include<cstring>
#include<cmath>
using namespace std;
const int MAXN = 100;
const int MAN = 4000;
int n, m = 0, N, Ze = 0, cnt = 0;
bool a;
int z[MAXN] = {};
int PreZe[MAXN] = {};
int DP[MAN][MAXN][MAXN] = {};
void Update(int& a, int b)
{
if (a==-1)
{
a = b; return;
}
if (b<a) a=b;
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
cin >> n;
N = n * (n - 1) >> 1;
for (int i = 1; i <= n; ++i)
{
cin >> a;
if(a) {
z[++m] = cnt;
Ze += cnt;
PreZe[m] = Ze;
cnt = 0;
}
else ++cnt;
}
// if (cnt)
// {
z[++m] = cnt;
Ze += cnt;
PreZe[m] = Ze;
// }
memset(DP, -1, sizeof(DP));
DP[0][0][0] = 0;
int ThisPara;
for (int k = 0; k <= N; ++k) // time
{
for (int j = 0; j < m; ++j) // Para M
{
for (int l = 0; l <= Ze; ++l) // Zeros
{
if (DP[k][j][l]==-1) continue;
for (int SZ = l, Walk; SZ <= Ze; ++SZ) // Zeroes To
{
Walk = k + abs(SZ - PreZe[j+1]); // Time To
if (Walk > N) continue; // Cut
ThisPara = SZ - l;
Update(DP[Walk][j+1][SZ], DP[k][j][l] + ThisPara * ThisPara);
}
}
}
}
int Ans = 0x3f3f3f3f;
int ZeZe = Ze * Ze;
for (int i = 0; i <= N; ++i)
{
if (DP[i][m][Ze] != -1 && DP[i][m][Ze] < Ans) Ans = DP[i][m][Ze];
cout << ((ZeZe - Ans) >> 1) << ' ';
}
return 0;
}