前言
有意思的 dp。
题目
讲解
令 (f(i,j)) 表示两个序列末尾是 (i,j) 的最短距离。
如果 (j<i-1,f(i,j)=f(i-1,j)+dis(i,i-1)),很显然嘛,不然它就转移到 (f(i-1,i)) 去了。
所以我们只需要单独考虑 (f(i,i-1)) 的转移!
令 (g(i)=f(i,i-1)),(g(i)=min{f(i-1,j)+dis(i,j)}(jin[0,i-2]))
(ans=min{min{f(n,i)},g(n)}(iin[1,n-2]))
我们令 (s(n)=underset{i=2}{overset{n}{sum}} dis(i,i-1)),则有:
(ans=min{g(i)+s(n)-s(i)}(iin[1,n]))
真不戳,我们把 (f) 给搞掉了,当然我们也可以把 (g(i)) 的转移换一下:
(g(i)=min{g(j)+s(i-1)-s(j)+dis(j-1,i)}=min{g(j)-s(j)+dis(j-1,i)}+s(i-1)(jin[0,i-1]))
我们发现除了 (dis(j-1,i)) ,其它的都很好算出来,所以我们考虑把 (dis(j-1,i)) 拆出来,分四类讨论。
显然这个 dp 可以用 (operatorname{cdq})分治 优化,其中用树状数组求前缀最值,注意不要忘了后面有一项 (s(i-1))。
时间复杂度为 (O(nlog^2n))。
代码
//12252024832524
#include <cstdio>
#include <cstring>
#include <algorithm>
#define TT template<typename T>
using namespace std;
typedef long long LL;
const int MAXN = 100005;
const LL INF = (1ll << 60);//抱歉0x(3f)^8
int n;
LL g[MAXN],h[MAXN],s[MAXN];
LL Read()
{
LL x = 0,f = 1;char c = getchar();
while(c > '9' || c < '0'){if(c == '-')f = -1;c = getchar();}
while(c >= '0' && c <= '9'){x = (x*10) + (c^48);c = getchar();}
return x * f;
}
TT void Put1(T x)
{
if(x > 9) Put1(x/10);
putchar(x%10^48);
}
TT void Put(T x,char c = -1)
{
if(x < 0) putchar('-'),x = -x;
Put1(x);
if(c >= 0) putchar(c);
}
TT T Max(T x,T y){return x > y ? x : y;}
TT T Min(T x,T y){return x < y ? x : y;}
TT T Abs(T x){return x < 0 ? -x : x;}
struct BIT
{
LL b[MAXN];
int lowbit(int x){return (x & -x);}
void Add(int x,int len,LL val){for(int i = x;i <= len;i += lowbit(i)) b[i] = Min(b[i],val);}
LL Query(int x){LL ret = INF;for(int i = x;i >= 1;i -= lowbit(i)) ret = Min(ret,b[i]);return ret;}
void init(int x){for(int i = 1;i <= x;++ i) b[i] = INF;}
}pre,suf;
struct node
{
int x,y,px,py,ID,lsh;
}p[MAXN],q[MAXN];
bool cmpx1(node a,node b){if(a.px != b.px) return a.px < b.px; return a.ID < b.ID;}
bool cmpx2(node a,node b){if(a.px != b.px) return a.px > b.px; return a.ID < b.ID;}
bool cmpy(node a,node b){if(a.py != b.py) return a.py < b.py; return a.ID < b.ID;}
bool cmpID(node a,node b){return a.ID < b.ID;}
int dis(int x,int y){return Abs(p[x].x-p[y].x) + Abs(p[x].y-p[y].y);}
void solve(int l,int r)
{
if(l == r) return;
int mid = (l+r) >> 1,len = 1;
solve(l,mid);
for(int i = l;i <= r;++ i)
if(p[i].ID <= mid) p[i].px = q[p[i].ID-1].x,p[i].py = q[p[i].ID-1].y;
else p[i].px = p[i].x,p[i].py = p[i].y;
//situation 1,2 lx<rx ly<ry;lx<rx ly>ry
sort(p+l,p+r+1,cmpy);
p[l].lsh = 1;
for(int i = l+1;i <= r;++ i)
if(p[i].py == p[i-1].py) p[i].lsh = p[i-1].lsh;
else p[i].lsh = ++len;
sort(p+l,p+r+1,cmpx1);
pre.init(len); suf.init(len);
for(int i = l;i <= r;++ i)
{
int ID = p[i].ID;
if(ID <= mid)//update
{
pre.Add(p[i].lsh,len,g[ID]+h[ID]-s[ID]-q[ID-1].x-q[ID-1].y);
suf.Add(len-p[i].lsh+1,len,g[ID]+h[ID]-s[ID]-q[ID-1].x+q[ID-1].y);
}
else//query
{
g[ID] = Min(g[ID],p[i].x+p[i].y+pre.Query(p[i].lsh));
g[ID] = Min(g[ID],p[i].x-p[i].y+suf.Query(len-p[i].lsh+1));
}
}
//situation 3,4 lx>rx ly<ry;lx>rx ly>ry
pre.init(len); suf.init(len);
sort(p+l,p+r+1,cmpx2);
for(int i = l;i <= r;++ i)
{
int ID = p[i].ID;
if(ID <= mid)//update
{
pre.Add(p[i].lsh,len,g[ID]+h[ID]-s[ID]+q[ID-1].x-q[ID-1].y);
suf.Add(len-p[i].lsh+1,len,g[ID]+h[ID]-s[ID]+q[ID-1].x+q[ID-1].y);
}
else//query
{
g[ID] = Min(g[ID],-p[i].x+p[i].y+pre.Query(p[i].lsh));
g[ID] = Min(g[ID],-p[i].x-p[i].y+suf.Query(len-p[i].lsh+1));
}
}
sort(p+l,p+r+1,cmpID);//You must sort it again,beceuse the following "solve" will use it.
solve(mid+1,r);
}
signed main()
{
// freopen(".in","r",stdin);
// freopen(".out","w",stdout);
for(int T = Read(); T ;-- T)
{
n = Read();
for(int i = 1;i <= n;++ i) p[i].x = Read(),p[i].y = Read(),p[i].ID = i,q[i] = p[i];
if(n <= 2) {Put(0,'
');continue;}
h[1] = g[1] = 0;
for(int i = 2;i <= n;++ i) s[i] = s[i-1] + dis(i,i-1),h[i] = s[i-1],g[i] = 0;
solve(2,n);//pay attention to the boundary
LL ans = INF;
for(int i = 1;i <= n;++ i) ans = Min(ans,g[i]+h[i]+s[n]-s[i]);
Put(ans,'
');
}
return 0;
}
//sorry for my poor English
后记
现在的 dp 好像都套路化了(虽然还是不会),先写出一个暴力转移方程,然后优化就过了。
dp 难在怎么优化而不是写出方程!
当然如果连暴力转移都写不出来,那没救了。