题目大意在一个平面上有n个垃圾,坐标为(Xi,Yi),重量为Wi.有一个机器人,要求按照编号从小至大的顺序捡起垃圾放入垃圾桶(位于原点(0,0)),机器人可以捡起几个垃圾一起扔掉,但是任何时候的垃圾重量不能超过C,机器人的行走轨迹是哈密顿距离即横坐标之差的绝对值加上纵坐标之差的绝对值。
分析
设d[i]为清理完前i个垃圾后回到原点所走的距离;
d[i]=min{d[j]+dist(j+1,i),dist2origin(j+1)+dist2origin(i)|j<i&&w(j+1,i)<=c}
dist(j+1,i)表示走完j+1,j+2,j+3一直走到i的距离;
dist2origin(j)表示j至源点距离
不妨设total[i]表示从第一个垃圾至第i个垃圾的距离总和;
则 dist(j+1,i)=total[i]-total[j+1];
那么上面的公式就变成
d[i]=min{d[j]-total[j+1]+distorigin(j+1)|w(j+1,i)<=C}+distorigin(i)+total[i];
这样的话状态d[i]的递推就完全变成了只包含j的的状态递推。
我们有令func[j]=d[j]-total[j+1]+distorigin(j+1);
则d[i]=min{func[j]|w(j+1,i)<=C}+distorigin(j+1)+total[i];
对于任意状态d[i],满足条件的j就好像形成了一个闭区间,当i增大时,这个区间一定会向右滑动
对于d(i)来说,当我们计算至d(i)这一状态时,之前所有的fun(j) (j<i)的值一定是固定的,我们现在要找的是在(j+1,i-1)这个区间内上找最小的fun值,假如现在有k<j且fun(j)<fun(k),那么不论在何种时候,计算之后的状态时,一定不会用fun(k),始终有一个比它更小更优的值存在,所以这个值就相当于废值,我们把它抛掉就可以了. 我们可以把这些元素看作一个队列,每次有新元素进来的时候,就删除比他大的元素。我们维护的始终是一个非递减序列。
代码如下
#include<iostream>
#include<algorithm>
#include<string.h>
#include<cstdio>
#define maxn 100000+10
using namespace std;
int d[maxn],totald[maxn],x[maxn],y[maxn];
int q[maxn],dis2[maxn],w[maxn],fun[maxn];
int n,c,m;
void inti()
{
memset(totald, 0, sizeof(totald));
memset(w, 0, sizeof(w));
memset(x, 0, sizeof(x));
memset(y, 0, sizeof(y));
memset(dis2, 0, sizeof(dis2));
}
int dis(int i, int j) {
return abs(x[i] - x[j]) + abs(y[i] - y[j]);
}
int func(int j)
{
return d[j] - totald[j+1] + dis2[j+1];
}
int main()
{
int t,weig;
cin >> t;
while (t--) {
inti();
cin >> c >> m;
for (int i = 1; i <=m; i++) {
cin >> x[i] >> y[i]>>weig;
totald[i] = dis(i, i - 1) + totald[i - 1];
dis2[i] = dis(0, i);
w[i] += w[i - 1] + weig;
}
int front = 1, rear = 1;
for (int i = 1; i <= m; i++) {
while (front <= rear && w[i] - w[q[front]]> c) front++;
fun[i] = func(q[front]);
d[i] = fun[i] + totald[i] + dis2[i];
while (front <= rear && func(i) <=func(q[rear])) rear--;
q[++rear] = i;
}
printf("%d\n", d[m]);
if (t) cout << endl;
}
return 0;
}
在这里其实我有一个问题,就是维护的队列初始化问题,刘大神的书中没有对其初始化,但却能AC,不解;