P3035 [USACO11DEC] Umbrellas for Cows S 题解

P3035 [USACO11DEC] Umbrellas for Cows S

简化题意

数轴上有 \(N\) 个点,第 \(i\) 个点的下标为 \(X_i\)。数轴长为 \(M\)
我们可以放置若干条长度为 \(W\) 的线段(\(1 \leq W \leq M\)),覆盖一部分数轴区间。放置长度为 \(W\) 的线段需要代价为 \(C_W\)。允许有线段重合。
求将所有点都覆盖所需的最小代价。

解题思路

我的思路可能在一些细节上比较特殊,大家看看能不能理解吧。

这题是一道动态规划题。

这里,我发现是动规题是因为:如果数轴上目前位置没有点,那么肯定是继承上一个位置的价值,否则就在前面找从那个位置开始放线段比较合算。

首先,我们开一个数组 \(x\),把有点的位置打上标记。

\(dp_i\) 为在数轴位置 \(i\)\(1 \sim i\) 上的所有点都覆盖所需的最小价值。

首先,当前位置 \(i\) 没有点,那么就继承上一个点的价值。
即:

\[x_i = 0 : dp_i = dp_{i - 1} \]

如果当前位置 \(i\) 有点,我们就可以考虑:从点 \(j \sim i\) 放一个长度为 \(i - j\),价值为 \(C_{i - j}\) 的数轴。

但是这里发现,因为不考虑数轴相交的情况,所以如果对于 \(i > j\)\(C_i < C_j\) 的情况,我们放长度为 \(j\) 的线段还不如直接放长度为 \(j\) 的线段。(可以看做把长度为 \(i\) 的线段长度砍到了 \(j\))所以,我对价值做了一个后缀最小值的操作,保证每个线段可以用到最低价值(具体的实现方法见代码)。

然后,我们就可以开始操作了。设长度为 \(C_w\) 的线段的最小价值为 \(minn_w\)
对于有点的位置 \(i\),我们可以尝试放一个长度为 \(i - j\) 的线段(\(j \leq i\)),两端分别在数轴上的 \((i, j)\)。那么,就有状态转移方程:

\[x_i = 1 : dp_i = \min \{dp_{i - j} + minn_{j}\} \]

综上所述,此题的状态转移方程即为:

\[\begin{cases} x_i=0:dp_i = dp_{i - 1} \\ x_i=1:dp_i = \min \{dp_{i - j} + minn_{j}\} \end{cases}\]

最后的答案是在 \(dp_{\max\{X_i\}} \sim dp_{m}\) 中取最小值,因为这些都是符合条件的答案。

代码部分

#include <bits/stdc++.h>
using namespace std;
int n, m, maxx = 0, ans = INT_MAX;
int a[5010], x[100010], c[100010], dp[100010], minn[100010];
int main()
{
	cin >> n >> m;
	for(int i = 1;i <= n;i ++)
	{
		scanf("%d", &a[i]);
		maxx = max(maxx, a[i]);
		x[a[i]] = 1;//打标记		
	}
	for(int i = 1;i <= m;i ++)
	{
		scanf("%d", &c[i]);
		dp[i] = INT_MAX;//初始化为极大值 
	}
	minn[m + 1] = INT_MAX;
	for(int i = m;i >= 1;i --)//这里就是求后缀最小值 
		minn[i] = min(minn[i + 1], c[i]);
	dp[0] = 0;//初始化:在第 0 为所需价值为 0 
	for(int i = 1;i <= m;i ++)
	{
		if(x[i])//如果有点 
			for(int j = 1;j <= i;j ++)
				dp[i] = min(dp[i], dp[i - j] + minn[j]);		
		if(!x[i])//没有点 
			dp[i] = min(dp[i - 1], dp[i]);					
	}
	for(int i = maxx;i <= m;i ++)//找最终的答案 
		ans = min(ans, dp[i]);
	printf("%d\n", ans);
	return 0;
}

posted on 2026-02-26 22:19  Leo_29  阅读(5)  评论(0)    收藏  举报

导航