原题链接
很容易看出来是区间(DP)(当然爆搜(+)玄学剪枝也是可以的)。
设(f[i][j][k])表示已经关闭了([i,j])间的路灯,老张的状态为(k)时所消耗的最小功耗。
- (k = 0)时,老张最后关闭了(i)灯,即在区间([i,j])的左端。
- (k = 1)时,老张最后关闭了(j)灯,即在区间([i,j])的右端。
设(dis[i])表示(i)点的位置,(W[i])表示表示(1 o i)所有灯的功率和,即前缀和。
则有状态转移方程:
(qquadqquad f[i][j][0] = min{ f[i + 1][j][0] + (dis[i + 1] - dis[i]) imes k, f[i + 1][j][1] + (dis[j] - dis[i]) imes k }, k = W[n] - (W[j] - W[i]))
(qquadqquad f[i][j][1] = min{ f[i][j - 1][0] + (dis[j] - dis[i]) imes k, f[i][j - 1][1] + (dis[j] - dis[j - 1]) imes k }, k = W[n] - (W[j - 1] - W[i - 1]))
最后答案就是(min{ f[1][n][0], f[1][n][1] })。
另外,因为这题的区间只能从起点开始才有效,所以(DP)的右端点循环可以从起点开始,即:
(qquadqquad for j = start o n)
(qquadqquadquad for i = j - 1 o 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;
}