2019-2020 ACM ICPC Brazil Subregional Programming Contest E.Exhibition of Clownfish

我搬运我自己应该算原创吧

题目

题意

某水族馆有一种神奇的🐟, 当某一个鱼缸里没有雌鱼时, 某一条雄鱼会变成雌鱼. 水族馆里有 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 2n3000,0a,b105,a=0 or b>0

题解

关键点都想出来了, 但是不太会实现, 搞了好久好久好久😭

先进行一下特判, 如果没有雄鱼, 直接输出 0 0 0即可.

先考虑没有空缸的情况.

对一个非空鱼缸, 我们只会进行以下操作之一:

  1. 把所有雄鱼拿出来, 移动到其他缸里(可能是多个鱼缸);
  2. 把所有雌鱼拿出来, 让雄鱼依次变成雌鱼, 再一个一个拿雌鱼, 最后留一只雌鱼. 拿出来的鱼移动到其他缸里(可能是多个鱼缸).

证明简单, 略.

我们把进行第一种操作的鱼缸称为 M M M, 进行第二种操作的鱼缸称为 F F F.

手玩可以发现有以下性质:

  1. 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.

  1. 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+bj1)放在某些其他 F F F中, 则这些鱼还可能需要再拿出来(目标 F F F有雄鱼的话), 贡献会增加, 不优.

所以, F F F的总贡献是 a j + b j − 1 a_j + b_j - 1 aj+bj1.

由上述性质, 可以推知:

  1. 至少存在一个 M M M和一个 F F F才是可行的.

那么, 我们的任务就是给 n n n个非空的鱼缸标号.

事实上如果题目到这里就结束了, 我们可以直接贪心解决.

但是! 这道题有空鱼缸! 加入空鱼缸之后就变得复杂多了.

空鱼缸有这些作用:

  1. 作为一个 雄鱼消除器

由前两条性质我们可以得到, 在没有空鱼缸的情况下, M M M中的一只雄鱼的贡献为 2 2 2. 如果我们把这一条鱼移动到空鱼缸里, 贡献就是 1 1 1了, 更优.

  1. 作为一个 M M M

如果所有的非空鱼缸都是 F F F的话, 可以把他们移动到空鱼缸内.

  1. 作为一个 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+bi1的大小关系了, 空鱼缸能对我们的选择进行影响. 所以, 贪心的方法不可行. 尝试一个能够对比两种决策(标记成 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(i1,j,0,1)+2ai之前有M, 当前为M, 不移动到空dp(i1,j,0,0)+2ai之前无M, 当前为M, 不移动到空dp(i1,max(0,jai),0,1)+2aimin(j,ai)之前有M, 当前为M, 全移动到空dp(i1,max(0,jai),0,0)+2aimin(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(i1,j,1,0)+ai+bi1dp(i1,j,0,0)+ai+bi1

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(i1,j,1,1)+2aidp(i1,max(0,jai),1,1)+2aimin(j,ai)dp(i1,j,1,1)+ai+bi1dp(i1,j,1,0)+2aidp(i1,max(0,jai),1,0)+2aimin(j,ai)dp(i1,j,0,1)+ai+bi1

写不动了方程不一一解释了, 代码注释里有写.

最后, 取答案需要分情况讨论:

没有空鱼缸, 那么必须满足"至少一个 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 %}}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值