P9550 「PHOI-1」晚宴筵题解

image

题解

简化一下题意,已知从 \((p,q)\) 直接到达 \((x,y)\) 的费用函数如下:

\[\text{cost}(p,q,x,y) = \begin{cases} w_p+w_q+w_x+w_y-p-q-x-y, & l1_x \le p \le r1_x,l2_y \le q \le r2_y \\ \text{inf}, & \text{otherwise} \\ \end{cases}\]

问从 \((1,1)\) 到各位置的最小费用。

如果暴力建图跑最短路,由于 \(n\) 可以达到 \(\text{1e3}\),一共有 \(n^2\) 个点,边更多,肯定爆炸。

不妨思考是否有 \(\text{DP}\) 的做法,如果设 \(dp_{i,j}\) 表示 \((1,1)\)\((i,j)\) 的最小费用,则有:

\[dp_{x,y}=\min_{l1_x \le p \le r1_x,l2_y \le q \le r2_y}{(dp_{p,q}+\text{cost}(p,q,x,y))} \]

注意到,\(r1_x<x\),\(r2_y<y\),显然满足无后效性,可以 \(\text{DP}\),但是暴力是 \(n^4\) 的,仍然超时。

将方程抽象成矩阵问题,有一个 \((n+1) \times (n+1)\) 的矩阵,因为有 0。每个位置 \((i,j)\) 有一个权值表示 \(dp_{i,j}+w_i+w_j-i-j\)。那么在求解 \((x,y)\) 时,相当于在一个子矩阵中求出最小权值 \(\text{Pre}\),然后让 \(dp_{x,y}=\text{Pre}+w_x+w_y-x-y\),接着修改 \((x,y)\) 处的数值为 \(dp_{x,y}+w_x+w_y-x-y\)

也就是要维护子矩阵极值,单点修改。我们可以用 \(1\) 个线段树维护行,线段树的每个结点上维护一个线段树来维护列。即线段树套线段树,容易证明,单次操作是 \(O(\log^2 n)\) 的。总体的时间复杂度就是 \(O(n^2\log^2 n)\) 可以接受。

代码

点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int N=1e3+10;
const int inf=1e9;
int n,l1[N],r1[N],l2[N],r2[N],w[N]; 
int dp[N][N],Pre;
struct node_y {
	int l,r,val;
};
struct tree_y {
	node_y tr[N<<2];
	void pushup(int p) {
		tr[p].val=min(tr[p<<1].val,tr[p<<1|1].val);
	}
	void build(int p,int l,int r) {
		tr[p].l=l,tr[p].r=r;
		if(l==r) {
			tr[p].val=inf;
			return;
		}
		int mid=l+r>>1;
		build(p<<1,l,mid);
		build(p<<1|1,mid+1,r);
		pushup(p);
	}
	void update(int p,int y,int k) {
		if(tr[p].l==tr[p].r) {
			tr[p].val=min(tr[p].val,k);
			return;
		}
		int mid=(tr[p].l+tr[p].r)>>1;
		if(y<=mid) update(p<<1,y,k);
		else update(p<<1|1,y,k);
		pushup(p);
	}
	int query(int p,int l,int r) {
		if(l<=tr[p].l&&tr[p].r<=r) return tr[p].val;
		int mid=(tr[p].l+tr[p].r)>>1;
		int val=inf;
		if(l<=mid) val=min(val,query(p<<1,l,r));
		if(r>mid) val=min(val,query(p<<1|1,l,r));
		return val;
	} 
};
struct node_x {
	int l,r;
	tree_y T;
};
struct tree_x {
	node_x tr[N<<2];
	void build(int p,int l,int r) {
		tr[p].l=l,tr[p].r=r;
		tr[p].T.build(1,1,n+1);
		if(tr[p].l==tr[p].r) return;
		int mid=l+r>>1;
		build(p<<1,l,mid);
		build(p<<1|1,mid+1,r);
	}
	void update(int p,int x,int y,int k) {
		tr[p].T.update(1,y,k);
		if(tr[p].l==tr[p].r) return;
		int mid=(tr[p].l+tr[p].r)>>1;
		if(x<=mid) update(p<<1,x,y,k);
		else update(p<<1|1,x,y,k);
	}
	int query(int p,int lx,int rx,int ly,int ry) {
		if(lx<=tr[p].l&&tr[p].r<=rx) return tr[p].T.query(1,ly,ry);
		int mid=(tr[p].l+tr[p].r)>>1;
		int val=inf;
		if(lx<=mid) val=min(val,query(p<<1,lx,rx,ly,ry));
		if(rx>mid) val=min(val,query(p<<1|1,lx,rx,ly,ry));
		return val;
	}
};
tree_x A;
signed main() {
	ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	cin>>n;
	for(int i=1;i<=n;i++) cin>>l1[i];
	for(int i=1;i<=n;i++) cin>>r1[i];
	for(int i=1;i<=n;i++) cin>>l2[i];
	for(int i=1;i<=n;i++) cin>>r2[i];
	for(int i=1;i<=n;i++) cin>>w[i];
	A.build(1,1,n+1);
	for(int i=1;i<=n;i++)
		for(int j=1;j<=n;j++) dp[i][j]=inf;
	dp[1][1]=0;
	A.update(1,2,2,2*w[1]-2);
	for(int i=1;i<=n;i++)
		for(int j=1;j<=n;j++) {
			if(i==1&&j==1) continue;
			Pre=inf;
			Pre=min(Pre,A.query(1,l1[i]+1,r1[i]+1,l2[j]+1,r2[j]+1));
			if(Pre!=inf) {
				dp[i][j]=Pre+w[i]+w[j]-i-j;
				A.update(1,i+1,j+1,dp[i][j]+w[i]+w[j]-i-j);
			}
		}
	for(int i=1;i<=n;i++) {
		for(int j=1;j<=n;j++) {
			if(dp[i][j]==inf) cout<<"inf ";
			else cout<<dp[i][j]<<" ";
		}
		cout<<endl;
	}
	return 0;
}

posted @ 2024-01-26 23:48  2021hych  阅读(57)  评论(0)    收藏  举报