[HDU6800]Play osu! on Your Tablet

壹、题目描述

传送门 to HDU

贰、题解

定义 \(\text{dis}(x,y)\) 表示 \(x,y\) 的曼哈顿距离,定义 \(\text{dis}(i_1,i_2,i_3,...,i_t)\) 表示 \(\sum_{k=1}^{k<t}\text{dis}(i_k,i_{k+1})\).

定义 \(f_{i,j}\) 表示考虑了前 \(i\) 个数字,其中一个序列的结尾在 \(j\) 的最小曼哈顿距离,发现对于 \(j<i-1\) 的情况,肯定是我们没有维护的序列之前以 \(i-1\) 结尾,维护的序列就是 \(j\) 结尾了,同时我们最后选择的数字归于没有维护的这一个数列(即以 \(i-1\) 结尾的这一个数列),那么我们可以直接从 \(i-1\) 转移

\[f_{i,j}=f_{i-1,j}+\text{dis}(i-1,i) \]

对于 \(j=i-1\) 的情形,我们无法转移,因为在 \(f_{i-1}\) 中的前驱状态不清楚,我们知道前驱状态中我们维护的序列结尾是 \(i-1\),但是没有维护的序列呢?所以这个时候我们就得枚举这个序列结束的位置,从 \(f_{i-1,k}\) 转移(注意此处的 \(f_{i-1,k}\)\(f_i\) 维护的序列是不一样的,交换过后的)

也就是

\[f_{i,i-1}=\min_{k=1}^{i-2}\{f_{i-1,k}+\text{dis}(k,i)\} \]

我们将 \(f_{i-1,k}\) 继续迭代展开:

\[\begin{aligned} f_{i,i-1}&=\min \begin{cases} f_{i-1,i-2}+\text{dis}(i-2,i) &k=i-2 \\ f_{i-2,k}+\text{dis}(i-2,i-1)+\text{dis}(k,i) &k<i-2 \end{cases} \\ \end{aligned} \]

如果我们将 \(f_{i-2,k}\) 继续展开,同样也会得到一个类似的结果,然后我们发现,最后一定是这样的一个结果:

\[f_{i,i-1}=\min_{k=1}^{i-1}\{f_{k,k-1}+\text{dis}(k,k+1,k+2,...,i-1)+\text{dis}(k-1,i)\} \]

这个转移的本质是什么?考虑下面这一张图:

实际上我们就是枚举另一个序列在结尾部分的长度,然后进行转移。

同时,发现只涉及 \(f_{i,i-1}\) 之间的转移,我们考虑单独做,记 \(g_i=f_{i,i-1}\),那么有

\[g_i=\min_{j=1}^{i-1}\{g_j+\text{dis}(j,j+1,j+2,...,i-1)+\text{dis}(j-1,i)\} \]

考虑记 \(s_i=\text{dis}(1,2,3,...,i)\),那么

\[g_i=\min_{j=1}^{i-1}\{g_j-s_{j}+\text{dis}(j-1,i)\}+s_{i-1} \]

我们可以考虑使用 \(\tt CDQ\) 分治,考虑左区间对于右区间的贡献,但是由于 \(\text{dis}(j-1,i)\) 带绝对值,需要考虑将点 \(j-1\) 与点 \(i\) 的相对位置分四种情况讨论,每种情况将和 \(j\) 有关的放在一起,将和 \(i\) 有关的放在一起,最后就变成了一个二维数点的类似的问题,总复杂度 \(\mathcal O(n\log^2 n)\).

但是还有一部分 \(f_{i,j}(j<i-1)\) 的部分怎么办?我们同样将转移式子展开,最后可以得到

\[\begin{aligned} f_{n,j}&=f_{j+1,j}+\text{dis}(j+1,j+2,...,i) \\ &=g_{j+1}+s_i-s_{j+1} \end{aligned} \]

所以最后加一个循环即可。

叁、参考代码

\(\color{red}{\text{Talk is s**t, show you the code.}}\)

#include<cstdio>
#include<algorithm>
using namespace std;

typedef long long ll;
template<class T>inline T fab(const T x){return x<0? -x: x;}
template<class T>inline T readin(T x){
    x=0; int f=0; char c;
    while((c=getchar())<'0' || '9'<c) if(c=='-') f=1;
    for(x=(c^48); '0'<=(c=getchar()) && c<='9'; x=(x<<1)+(x<<3)+(c^48));
    return f? -x: x;
}

const int maxn=1e5;
const ll inf=1ll<<60;

int n, T;
ll s[maxn+5];

struct point{
    int x, y, id;
    point(){}
    point(const int X, const int Y): x(X), y(Y), id(0){}
    inline int operator <(const point rhs) const{
		return x<rhs.x;
	}
}p[maxn+5];

inline ll dis(const int i, const int j){
	return fab(p[i].x-p[j].x)+fab(p[i].y-p[j].y);
}

inline void input(){
    n=readin(1);
    for(int i=1; i<=n; ++i){
        p[i].x=readin(1), p[i].y=readin(1);
        if(i>1) s[i]=s[i-1]+dis(i-1, i);
    }
}

inline int lowbit(const int i){return i&(-i);}
struct BIT1{
    ll c[maxn+5]; int siz;
    inline void clear(const int up){
        siz=up;
        for(int i=0; i<=siz; ++i) c[i]=inf;
    }
    inline void add(int i, const ll val){
        for(; i<=siz; i+=lowbit(i))
			c[i]=min(c[i], val);
    }
    inline ll query(int i){
    	ll ret=inf;
    	for(; i; i-=lowbit(i))
    		ret=min(ret, c[i]);
    	return ret;
	}
}upp;
struct BIT2{
    ll c[maxn+5]; int siz;
    inline void clear(const int up){
    	siz=up;
        for(int i=0; i<=siz; ++i) c[i]=inf;
    }
    inline void add(int i, const ll val){
        for(; i; i-=lowbit(i))
			c[i]=min(c[i], val);
    }
    inline ll query(int i){
        ll ret=inf;
        for(; i<=siz; i+=lowbit(i))
			ret=min(c[i], ret);
        return ret;
    }
}downp;

// assisted array
point t[maxn+5];
ll g[maxn+5];
int hashy[maxn+5];
struct node{
	int y, id;
	node(){}
	node(const int Y, const int I): y(Y), id(I){}
	inline int operator <(const node rhs) const{
		return y<rhs.y;
	}
}ty[maxn+5];
inline int cmp1(const point a, const point b){
	if(a.x==b.x) return a.id<b.id;
	return a.x<b.x;
}
inline int cmp2(const point a, const point b){
	if(a.x==b.x) return a.id<b.id;
	return a.x>b.x;
}
void solve(const int l, const int r){
    if(l==r) return;
    int mid=(l+r)>>1;
    solve(l, mid);
    for(int i=l-1; i<=r; ++i)
    	ty[i]=node(p[i].y, i);
   	sort(ty+l-1, ty+r+1);
    int has_num=0, pre=-1;
    for(int i=l-1; i<=r; ++i){
    	if(ty[i].y!=pre) ++has_num, pre=ty[i].y;
    	hashy[ty[i].id]=has_num;
	}
    // copy the array
    for(int i=l; i<=mid; ++i)
        t[i]=p[i-1], t[i].id=i;
    for(int i=mid+1; i<=r; ++i)
    	t[i]=p[i], t[i].id=i;
    	
    sort(t+l, t+r+1, cmp1);
    upp.clear(has_num), downp.clear(has_num);
    for(int i=l; i<=r; ++i){
    	if(t[i].id<=mid){
    		// if this point is under
    		upp.add(hashy[t[i].id-1], g[t[i].id]-s[t[i].id]-t[i].x-t[i].y);
    		// if this point is over
    		downp.add(hashy[t[i].id-1], g[t[i].id]-s[t[i].id]-t[i].x+t[i].y);
		}
		else{
			g[t[i].id]=
				min(g[t[i].id],
				min(upp.query(hashy[t[i].id])+s[t[i].id-1]+t[i].x+t[i].y,
					downp.query(hashy[t[i].id])+s[t[i].id-1]+t[i].x-t[i].y
				));
		}
	}
	
	upp.clear(has_num), downp.clear(has_num);
	sort(t+l, t+r+1, cmp2);
	for(int i=l; i<=r; ++i){
		if(t[i].id<=mid){
			upp.add(hashy[t[i].id-1], g[t[i].id]-s[t[i].id]+t[i].x-t[i].y);
			downp.add(hashy[t[i].id-1], g[t[i].id]-s[t[i].id]+t[i].x+t[i].y);
		}
		else{
			g[t[i].id]=
				min(g[t[i].id],
				min(upp.query(hashy[t[i].id])+s[t[i].id-1]-t[i].x+t[i].y,
					downp.query(hashy[t[i].id])+s[t[i].id-1]-t[i].x-t[i].y
				));
		}
	}
    solve(mid+1, r);
}

signed main(){
    T=readin(1);
    while(T--){
        input();
        if(n<=2){
        	printf("0\n");
        	continue;
		}
        g[1]=0;
        for(int i=2; i<=n; ++i) g[i]=s[i-1];
        solve(2, n);
        ll ans=inf;
        for(int j=1; j<=n; ++j)
        	ans=min(ans, g[j]+s[n]-s[j]);
        printf("%lld\n", ans);
    }
    return 0;
}

卡住原因:

  1. 某些地方将 \(\min\) 打成 \(\max\)
  2. 对于 \([l,r]\) 的数组的复制,对于 \([l,mid]\)\([mid+1,r]\) 的部分,拷贝需要使用不同的点坐标;
  3. 需要将 \(y\) 哈希;
  4. 树状数组的 \(\pm\) 要判断清楚;

肆、用到の小 \(\tt trick\)

\(DP\) 式子的时候要深入地考虑,分析其本质,最后可能会发现一些意想不到的惊喜。

对于带绝对值的东西,由于非线性不好分析,可以考虑将情况分开,将绝对值去掉之后用更简单的方案,但是这样做一般会将问题转化为一个类似二维数点的问题,这个时候就需要各种数据结构进行维护了。

posted @ 2021-02-22 20:39  Arextre  阅读(130)  评论(2编辑  收藏  举报