一道绿题让我磨洋工磨了两个星期还行orz
感觉做这种题还是做不到一步直达dp方程的水平,需要经历一个从普通爆搜,到记忆爆搜,最后再到dp转移的过程
普通爆搜,漫无目的地模拟瞎找。眼瞧着这个鬼玩意儿没什么思路,别无他法。先选学校,每选完一次学校O(n*n)得结果,这样肯定是不行的。
void dfs(int last,int left)
{
if(left==0){
int aans = 0;
for(int i=1;i<=n;++i){
if(sch[i]) continue;
int l=0,r=0,k=i;
while(k>=1&&!sch[k])
--k;
l=k;k=i;
while(k<=n&&!sch[k])
++k;
r=k;
if(l==0)
aans+=std::abs(vge[r]-vge[i]);
else if(r==0)
aans+=std::abs(vge[i]-vge[l]);
else
aans+=std::min(std::abs(vge[r]-vge[i]),std::abs(vge[i]-vge[l]));
}
if(aans<ans){
ans=aans;
}
return;
}
for(int i=last+1;i<=n-left+1;++i){
sch[i] = true;
dfs(i,left-1);
sch[i] = false;
}
}
这时我们把挂在脑袋旁边的那个亮灯泡取下来。对于每一个村落而言,都可以有一个左边最近的学校和一个右边最近的学校,这个村落到学校的最短距离等于分别到这两个学校的距离的最小值(左边没有学校就直接加右边的,右边没有学校就直接加左边的)
然后这样就可以愉快地写记忆化爆搜了,每回以剩余未选学校作为阶段就行
然而三维记忆化爆搜是不行的,因为500^3*4 byte=476.84MB
快乐MLE
想用滚动数组减少一维,就不得不把记忆化爆搜改成正儿八经的dp了。联系记忆化爆搜的递归回溯过程,我们先把剩余学校数为0时的值求出来。仍旧以 剩余未选学校数 作为转移阶段,以上一个选择的学校对应村落编号和这一次选择的学校对应村落编号作为状态,定义sum[last][next]为从last到next内部所有村落到学校最小距离的和,则有
//循环:left(阶段)->last->next->go
//f[last][next] = sum[last][next] + min{f[next][go]}(last<=next<=n-left+1,last=0 when left==m-1)
//f[last][next] = sum[last][next] (last<=next,next==n)
有些问题需要注意一下。比如说在left==m-1时last只能为0,因为left==m-1时在逻辑上只选择了一所学校,左边是没有选的;在初始化成left==0的状态时先memset0x3f,再把边界f[i:0->n][n+1]设为0;在dp递推时需要正推,因为前
面的结果需要由上一层后面的结果推出,所以如果用滚动数组应保证后面的后于前面的被修改,所以需要正推
最后
然而这玩意儿常数还是很大,加上前缀和、预先处理sum[last][next]、left==0f[i][j]O(nn)处理等优化后还是只能过一半的点,时间复杂度大概是O(n^3(A LARGE NUMBER))
然而吸氧(开O2)就可以过了
吸氧万岁
可能还是选择 go 那里可以优化成O(1)