一名苦逼的OIer,想成为ACMer

Iowa_Battleship

洛谷1220 关路灯

原题链接

很容易看出来是区间\(DP\)(当然爆搜\(+\)玄学剪枝也是可以的)。
\(f[i][j][k]\)表示已经关闭了\([i,j]\)间的路灯,老张的状态为\(k\)时所消耗的最小功耗。

  1. \(k = 0\)时,老张最后关闭了\(i\)灯,即在区间\([i,j]\)的左端。
  2. \(k = 1\)时,老张最后关闭了\(j\)灯,即在区间\([i,j]\)的右端。

\(dis[i]\)表示\(i\)点的位置,\(W[i]\)表示表示\(1\to i\)所有灯的功率和,即前缀和。
则有状态转移方程:

\(\qquad\qquad f[i][j][0] = \min\{ f[i + 1][j][0] + (dis[i + 1] - dis[i]) \times k, f[i + 1][j][1] + (dis[j] - dis[i]) \times k \}, k = W[n] - (W[j] - W[i])\)

\(\qquad\qquad f[i][j][1] = \min\{ f[i][j - 1][0] + (dis[j] - dis[i]) \times k, f[i][j - 1][1] + (dis[j] - dis[j - 1]) \times k \}, k = W[n] - (W[j - 1] - W[i - 1])\)

最后答案就是\(\min\{ f[1][n][0], f[1][n][1] \}\)
另外,因为这题的区间只能从起点开始才有效,所以\(DP\)的右端点循环可以从起点开始,即:
\(\qquad\qquad for\ j = start \to n\)
\(\qquad\qquad\quad for\ i = j - 1 \to 1\)
可以减少点冗余的\(DP\)转移。

#include<cstdio>
#include<cstring>
using namespace std;
const int N = 55;
int f[N][N][2], dis[N], W[N];
inline int re()
{
	int x = 0;
	char c = getchar();
	bool p = 0;
	for (; c < '0' || c > '9'; c = getchar())
		p |= c == '-';
	for (; c >= '0' && c <= '9'; c = getchar())
		x = x * 10 + c - '0';
	return p ? -x : x;
}
inline int minn(int x, int y)
{
	return x < y ? x : y;
}
int main()
{
	int i, j, k, n, m;
	n = re();
	m = re();
	for (i = 1; i <= n; i++)
	{
		dis[i] = re();
		W[i] = re() + W[i - 1];
	}
	memset(f, 60, sizeof(f));
	f[m][m][0] = f[m][m][1] = 0;
	for (j = m; j <= n; j++)
		for (i = j - 1; i; i--)
		{
			k = W[n] - W[j] + W[i];
			f[i][j][0] = minn(f[i + 1][j][0] + (dis[i + 1] - dis[i]) * k, f[i + 1][j][1] + (dis[j] - dis[i]) * k);
			k = W[n] - W[j - 1] + W[i - 1];
			f[i][j][1] = minn(f[i][j - 1][0] + (dis[j] - dis[i]) * k, f[i][j - 1][1] + (dis[j] - dis[j - 1]) * k);
		}
	printf("%d", minn(f[1][n][0], f[1][n][1]));
	return 0;
}

posted on 2018-10-23 10:45  Iowa_Battleship  阅读(95)  评论(0编辑  收藏  举报

导航