题意
某水族馆有一种神奇的🐟, 当某一个鱼缸里没有雌鱼时, 某一条雄鱼会变成雌鱼. 水族馆里有 n n n个鱼缸, 其中第 i i i个鱼缸中有 a i a_i ai条雄鱼和 b i b_i bi条雌鱼(保证初始状态合法). 把某一个鱼缸中的一条鱼移动到另一个鱼缸需要 1 1 1点花费. 求把所有雄鱼变成雌鱼的最小花费.
2 ≤ n ≤ 3000 , 0 ≤ a , b ≤ 1 0 5 , a = 0 o r b > 0 2 \le n \le 3000, 0 \le a, b \le 10^5, a = 0 \ or\ b > 0 2≤n≤3000,0≤a,b≤105,a=0 or b>0
题解
关键点都想出来了, 但是不太会实现, 搞了好久好久好久😭
先进行一下特判, 如果没有雄鱼, 直接输出 0 0 0即可.
先考虑没有空缸的情况.
对一个非空鱼缸, 我们只会进行以下操作之一:
- 把所有雄鱼拿出来, 移动到其他缸里(可能是多个鱼缸);
- 把所有雌鱼拿出来, 让雄鱼依次变成雌鱼, 再一个一个拿雌鱼, 最后留一只雌鱼. 拿出来的鱼移动到其他缸里(可能是多个鱼缸).
证明简单, 略.
我们把进行第一种操作的鱼缸称为 M M M, 进行第二种操作的鱼缸称为 F F F.
手玩可以发现有以下性质:
- M M M拿出来的鱼一定会移动到某些 F F F中
证明:
M M M( a i , b i a_i, b_i ai,bi)拿出来的雄鱼 a i a_i ai会放到 F F F的雄鱼里, 让拿过来的雄鱼 a i a_i ai变成雌鱼的最优方式就是这个 F F F进行他的操作, 这样这 a i a_i ai条雄鱼会被移动两次, 贡献为 2 a i 2a_i 2ai. 如果不是这样的操作, 那么只可能是把这些鱼再放到其他缸内, 这样贡献一定会大于 2 a i 2a_i 2ai, 不优. 如果是把 M M M放到其他 M M M里, 也会造成贡献大于 2 a i 2a_i 2ai, 不优.
所以, M M M的贡献是 2 a i 2a_i 2ai.
- F F F拿出来的鱼一定会移动到某些 M M M中
证明:
放到 M M M中, 由于 M M M中的雄鱼不需要通过移动雌鱼让雄鱼变性, 所以放好以后就不用动了. 假如 F F F( a j , b j a_j, b_j aj,bj)拿出来的鱼 ( a i + b j − 1 ) (a_i + b_j - 1) (ai+bj−1)放在某些其他 F F F中, 则这些鱼还可能需要再拿出来(目标 F F F有雄鱼的话), 贡献会增加, 不优.
所以, F F F的总贡献是 a j + b j − 1 a_j + b_j - 1 aj+bj−1.
由上述性质, 可以推知:
- 至少存在一个 M M M和一个 F F F才是可行的.
那么, 我们的任务就是给 n n n个非空的鱼缸标号.
事实上如果题目到这里就结束了, 我们可以直接贪心解决.
但是! 这道题有空鱼缸! 加入空鱼缸之后就变得复杂多了.
空鱼缸有这些作用:
- 作为一个 雄鱼消除器
由前两条性质我们可以得到, 在没有空鱼缸的情况下, M M M中的一只雄鱼的贡献为 2 2 2. 如果我们把这一条鱼移动到空鱼缸里, 贡献就是 1 1 1了, 更优.
- 作为一个 M M M
如果所有的非空鱼缸都是 F F F的话, 可以把他们移动到空鱼缸内.
- 作为一个 F F F
如果所有非空鱼缸都是 M M M的话, 可以把他们都移动到空鱼缸内, 然后再对收集了雄鱼的空鱼缸进行 F F F的操作(如果有必要).
作用 2 , 3 2,3 2,3削弱了对非空鱼缸的"至少一个为 M M M, 一个为 F F F"的限制条件, 即可以全是 M M M或者 F F F.
同时, 还有这样的性质:
移动到空鱼缸里的鱼一定是某个 M M M内的雄鱼
证明:
由作用1可得, 这样做可以使答案更优. M M M的雌鱼不需要移动. F F F的雄鱼或者雌鱼移动到这里, 贡献是 1 1 1; 而原来的贡献也是 1 1 1, 移动的话不仅不会使答案更优, 还会浪费一个可能可以减小答案的空鱼缸, 不优.
贪心一下, 当然是把尽量多的 M M M中的雄鱼移动到空鱼缸中.
那么, 哪些鱼缸需要标记成 M M M, 不再仅仅取决于 2 a i 2a_i 2ai与 a i + b i − 1 a_i + b_i - 1 ai+bi−1的大小关系了, 空鱼缸能对我们的选择进行影响. 所以, 贪心的方法不可行. 尝试一个能够对比两种决策(标记成 M M M还是 F F F)的优劣性的做法 —— DP.
一步一步来.
我们不需要给空鱼缸标号, 把他和非空鱼缸分离开, d p dp dp的第一维只考虑非空鱼缸.
由于空鱼缸的数量会对结果造成影响, 所以在状态中加入空鱼缸.
d p ( i , j ) dp(i, j) dp(i,j)表示前 i i i个(非空)鱼缸, 另外有 j j j个空鱼缸被使用, 即有 j j j条 M M M中的雄鱼被拿出来且分别放在了空鱼缸中.
如果没有空鱼缸, 鱼缸的标号是有限制条件的, 所以我们需要知道鱼缸的标号情况. 但是不需要知道具体的标号情况, 而需要知道有没有标号为 F F F的和有没有标号为 M M M的. 所以, 我们附加上两个状态, 分别表示前 i i i个中有无 F F F, M M M. 最后状态长这样:
KaTeX parse error: Undefined control sequence: \/ at position 12: dp(i, j, 0 \̲/̲ 1, 0 \/ 1)
表示前 i i i个(非空)鱼缸中, 移动了 j j j条雄鱼到 j j j个空鱼缸中, 前 i i i个鱼缸有/无 F F F, 前 i i i个鱼缸有/无 M M M
d p dp dp方程老长老长了:
首先边界条件
d
p
(
0
,
0
,
0
,
0
)
=
0
dp(0, 0, 0, 0) = 0
dp(0,0,0,0)=0, 其他
d
p
(
0
,
0
)
dp(0, 0)
dp(0,0)的不存在, 可直接设为INF
. 当
i
>
1
i>1
i>1时,
d
p
(
i
,
j
,
0
,
0
)
dp(i, j, 0, 0)
dp(i,j,0,0)也不存在, 设为INF
.
d p ( i , j , 0 , 1 ) dp(i, j, 0, 1) dp(i,j,0,1) 从以下状态转移:
d p ( i − 1 , j , 0 , 1 ) + 2 a i 之前有M, 当前为M, 不移动到空 d p ( i − 1 , j , 0 , 0 ) + 2 a i 之前无M, 当前为M, 不移动到空 d p ( i − 1 , m a x ( 0 , j − a i ) , 0 , 1 ) + 2 ∗ a i − m i n ( j , a i ) 之前有M, 当前为M, 全移动到空 d p ( i − 1 , m a x ( 0 , j − a i ) , 0 , 0 ) + 2 ∗ a i − m i n ( j , a i ) 之前无M, 当前为M, 全移动到空 \begin{aligned} &dp(i-1, j, 0, 1) + 2a_i &\text{之前有M, 当前为M, 不移动到空} \newline &dp(i-1, j, 0, 0) + 2a_i &\text{之前无M, 当前为M, 不移动到空} \newline &dp(i-1, max(0, j - a_i), 0, 1) + 2 * a_i - min(j, a_i) &\text{之前有M, 当前为M, 全移动到空} \newline &dp(i-1, max(0, j - a_i), 0, 0) + 2 * a_i - min(j, a_i) &\text{之前无M, 当前为M, 全移动到空} \end{aligned} dp(i−1,j,0,1)+2ai之前有M, 当前为M, 不移动到空dp(i−1,j,0,0)+2ai之前无M, 当前为M, 不移动到空dp(i−1,max(0,j−ai),0,1)+2∗ai−min(j,ai)之前有M, 当前为M, 全移动到空dp(i−1,max(0,j−ai),0,0)+2∗ai−min(j,ai)之前无M, 当前为M, 全移动到空
d p ( i , j , 1 , 0 ) dp(i, j, 1, 0) dp(i,j,1,0) 从以下状态转移:
d p ( i − 1 , j , 1 , 0 ) + a i + b i − 1 d p ( i − 1 , j , 0 , 0 ) + a i + b i − 1 \begin{aligned} dp(i-1, j, 1, 0) + a_i + b_i - 1 \newline dp(i-1, j, 0 ,0) + a_i + b_i - 1 \end{aligned} dp(i−1,j,1,0)+ai+bi−1dp(i−1,j,0,0)+ai+bi−1
F F F中的鱼不需要移到空鱼缸, 只有这两个状态需要转移
d p ( i , j , 1 , 1 ) dp(i, j, 1, 1) dp(i,j,1,1) 从以下状态转移:
d p ( i − 1 , j , 1 , 1 ) + 2 ∗ a i d p ( i − 1 , m a x ( 0 , j − a i ) , 1 , 1 ) + 2 ∗ a i − m i n ( j , a i ) d p ( i − 1 , j , 1 , 1 ) + a i + b i − 1 d p ( i − 1 , j , 1 , 0 ) + 2 ∗ a i d p ( i − 1 , m a x ( 0 , j − a i ) , 1 , 0 ) + 2 ∗ a i − m i n ( j , a i ) d p ( i − 1 , j , 0 , 1 ) + a i + b i − 1 \begin{aligned} &dp(i-1, j, 1, 1) + 2 * a_i \newline &dp(i-1, max(0, j - a_i), 1, 1) + 2 * a_i - min(j, a_i) \newline &dp(i-1, j, 1, 1) + a_i + b_i - 1 \newline &dp(i-1, j, 1, 0) + 2 * a_i \newline &dp(i-1, max(0, j - a_i), 1, 0) + 2 * a_i - min(j, a_i) \newline &dp(i-1, j, 0, 1) + a_i + b_i - 1 \end{aligned} dp(i−1,j,1,1)+2∗aidp(i−1,max(0,j−ai),1,1)+2∗ai−min(j,ai)dp(i−1,j,1,1)+ai+bi−1dp(i−1,j,1,0)+2∗aidp(i−1,max(0,j−ai),1,0)+2∗ai−min(j,ai)dp(i−1,j,0,1)+ai+bi−1
写不动了方程不一一解释了, 代码注释里有写.
最后, 取答案需要分情况讨论:
没有空鱼缸, 那么必须满足"至少一个 M M M, 一个 F F F"的条件, 所以答案是 d p ( n o n e m p t y , 0 , 1 , 1 ) dp(non\\_empty, 0, 1, 1) dp(nonempty,0,1,1)
有空鱼缸, 没有限制条件. 枚举空鱼缸的使用数量, 对KaTeX parse error: Undefined control sequence: \/ at position 21: …n\\_empty, k, 0\̲/̲1, 0\/1), 0 \le… 取min就是答案了.
复杂度 O ( n 2 ) O(n^2) O(n2)
终于写完了…
总结一下, 为什么没有想到这样的dp
首先, 没有想到只需要保存"前 i i i个是否有标号为 M M M或 F F F"这样的状态, 傻乎乎地保存前 i i i个有 j j j个标号为 M M M.
其次, 没有想到把空鱼缸数量保存在状态里. 可能是受到保存了 M M M的个数的影响, 再加一维就炸了, 所以根本没往这方面想.
{{% code %}}
由于一开始开了LL, 然后炸空间了, 所以 d p dp dp滚了第一维. 后来发现最大值不会超过int, 于是改回了int, 但是滚动数组依然保留, 没算不滚会不会炸.
const int MAXN = 3e3+10;
int n, empty_tank, non_empty_tank, a[MAXN], b[MAXN];
int dp[2][MAXN][2][2], male = 0;
int main() {
scanf("%d", &n);
for (int i = 1; i <= n; i++) {
int x, y;
scanf("%d%d", &x, &y);
if (!x && !y)
empty_tank++;
else {
a[++non_empty_tank] = x;
male += x;
b[non_empty_tank] = y;
}
}
if (!male) {
puts("0");
return 0;
}
for (int i = 0; i <= empty_tank; i++) {
dp[0][i][0][0] = dp[0][i][0][1] = dp[0][i][1][0] = dp[0][i][1][1] = INTINF;
dp[1][i][0][0] = dp[1][i][0][1] = dp[1][i][1][0] = dp[1][i][1][1] = INTINF;
}
dp[0][0][0][0] = 0;
for (int i = 1; i <= non_empty_tank; i++) {
for (int j = 0; j <= empty_tank; j++) {
//至少有一个是F或者M, 所以00不存在.
int cur = i&1;
// 初始化
dp[cur][j][0][0] = dp[cur][j][0][1] = dp[cur][j][1][0] = dp[cur][j][1][1] = INTINF;
dp[cur][j][0][0] = INTINF;
int &dpij01 = dp[cur][j][0][1];
// 之前有M, 当前为M, 不移动到空
dpij01 = min(dpij01, dp[cur^1][j][0][1] + 2 * a[i]);
// 之前无M, 当前为M, 不移动到空
dpij01 = min(dpij01, dp[cur^1][j][0][0] + 2 * a[i]);
// 之前有M, 当前为M, 全移动到空
// 如果这个a为0, 那么下面第一个方程无法按照"把0放到空"这样的表达方式进行转移, 特判一下
dpij01 = min(dpij01, dp[cur^1][max(0, j - a[i])][0][1] + 2 * a[i] - min(j, a[i]));
// 之前无M, 当前为M, 全部移到空
dpij01 = min(dpij01, dp[cur^1][max(0, j - a[i])][0][0] + 2 * a[i] - min(j, a[i]));
int &dpij10 = dp[cur][j][1][0];
// 由于F移动到空和不移动到空的花费相同, 移动的话浪费了空, 不划算, 所以F不移动到空
// 之前有F, 当前为F
dpij10 = min(dpij10, dp[cur^1][j][1][0] + a[i] + b[i] - 1);
// 之前无F, 当前为F
dpij10 = min(dpij10, dp[cur^1][j][0][0] + a[i] + b[i] - 1);
int &dpij11 = dp[cur][j][1][1];
// 之前有F和M, 当前为M, 不移动到空
dpij11 = min(dpij11, dp[cur^1][j][1][1] + 2 * a[i]);
// 之前有F和M, 当前为M, 全移动到空
dpij11 = min(dpij11, dp[cur^1][max(0, j - a[i])][1][1] + 2 * a[i] - min(j, a[i]));
// 之前有F和M, 当前为F, 不移动到空
dpij11 = min(dpij11, dp[cur^1][j][1][1] + a[i] + b[i] - 1);
// 之前有F无M, 当前为M, 不移动到空
dpij11 = min(dpij11, dp[cur^1][j][1][0] + 2 * a[i]);
// 之前有F无M, 当前为M, 全移动到空
dpij11 = min(dpij11, dp[cur^1][max(0, j - a[i])][1][0] + 2 * a[i] - min(j, a[i]));
// 之前无F有M, 当前为F, 不移动到空
dpij11 = min(dpij11, dp[cur^1][j][0][1] + a[i] + b[i] - 1);
}
}
int ans = INTINF;
if (empty_tank)
for (int i = 0; i <= empty_tank; i++) {
ans = min(ans, dp[non_empty_tank&1][i][0][0]);
ans = min(ans, dp[non_empty_tank&1][i][1][0]);
ans = min(ans, dp[non_empty_tank&1][i][0][1]);
ans = min(ans, dp[non_empty_tank&1][i][1][1]);
}
else
ans = dp[non_empty_tank&1][0][1][1];
printf("%d\n", ans);
return 0;
}
{{% /code %}}