高速公路

LXXVI.高速公路

简直恶心到爆炸……

首先,暴力的DP是非常简单的。设\(dis_x\)表示位置\(x\)到根的距离,则有

\[f_x=\min\limits_{y\text{ is an ancestor of }x}f_y+p_x(dis_x-dis_y)+q_x \]

暴力一敲,期望得分\(40\%\)。由于数据可能水了,实际得分\(60\%\)

代码:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
int n,head[1001000],cnt,a[1001000],b[1001000],dis[1001000];
ll f[1001000];
struct node{
	int to,next,val;
}edge[1001000];
void ae(int u,int v,int w){
	edge[cnt].next=head[u],edge[cnt].to=v,edge[cnt].val=w,head[u]=cnt++;
}
vector<int>v;
void dfs(int x){
	for(auto y:v)f[x]=min(f[x],1ll*a[x]*(dis[x]-dis[y])+b[x]+f[y]);
	v.push_back(x);
	for(int i=head[x];i!=-1;i=edge[i].next)dis[edge[i].to]=dis[x]+edge[i].val,dfs(edge[i].to);
	v.pop_back();
}
int main(){
	scanf("%d",&n),memset(head,-1,sizeof(head)),memset(f,0x3f3f3f3f,sizeof(f)),f[1]=0;
	for(int i=2,x,y;i<=n;i++)scanf("%d%d%d%d",&x,&y,&a[i],&b[i]),ae(x,i,y);
	dfs(1);
	for(int i=2;i<=n;i++)printf("%lld\n",f[i]);
	return 0;
}

考虑斜率优化一下。

我们先把问题抽象到序列上。设有一个\(j<k<i\),则如果\(j\)\(k\)优,必有

\[f_j+p_i(dis_i-dis_j)+q_i\leq f_k+p_i(dis_i-dis_k)+q_i \]

\[f_j+p_idis_i-p_idis_j\leq f_k+p_idis_i-p_idis_k \]

\[f_j-f_k\leq p_idis_j-p_idis_k \]

\[f_j-f_k\leq p_i(dis_j-dis_k) \]

\[\dfrac{f_j-f_k}{dis_j-dis_k}\geq p_i \]

然后直接优化即可,因为\(p_i\)保证递增。

但是,因为是在树上,所以你从一颗子树中跑出来之后,还要复原原序列!

当然,复原是很简单的,因为斜率优化的过程中除了队首队尾的移动以外,唯一真正改动的位置就是最后的入队环节。这时候只需要记录这个位置原本放了什么即可。

但是,暴力地维护队列的话,就不能完美地应用“单调队列”的性质了,因此复杂度最劣仍然是\(O(n^2)\)。期望得分\(60\%\)(听说常卡的好也能A?)

代码:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
int n,head[1001000],cnt,a[1001000],b[1001000],q[1001000],l[1001000],r[1001000],cha[100100];
ll f[1001000],dis[1001000];
void read(int &x){
	x=0;
	char c=getchar();
	while(c>'9'||c<'0')c=getchar();
	while(c>='0'&&c<='9')x=x*10+c-48,c=getchar();
}
struct node{
	int to,next,val;
}edge[1001000];
void ae(int u,int v,int w){
	edge[cnt].next=head[u],edge[cnt].to=v,edge[cnt].val=w,head[u]=cnt++;
}
void dfs(int x){
	while(l[x]<r[x]&&(f[q[l[x]]]-f[q[l[x]+1]])>=(dis[q[l[x]]]-dis[q[l[x]+1]])*a[x])l[x]++;
	f[x]=(dis[x]-dis[q[l[x]]])*a[x]+b[x]+f[q[l[x]]];
	while(l[x]<r[x]&&(f[q[r[x]-1]]-f[q[r[x]]])*(dis[q[r[x]]]-dis[x])>=(f[q[r[x]]]-f[x])*(dis[q[r[x]-1]]-dis[q[r[x]]]))r[x]--;
	cha[x]=q[++r[x]],q[r[x]]=x;
	printf("%d:%d,%d:",x,l[x],r[x]);for(int i=l[x];i<=r[x];i++)printf("%d ",q[i]);puts("");
	for(int i=head[x];i!=-1;i=edge[i].next)dis[edge[i].to]=dis[x]+edge[i].val,l[edge[i].to]=l[x],r[edge[i].to]=r[x],dfs(edge[i].to);
	q[r[x]]=cha[x];
}
int main(){
	read(n),memset(head,-1,sizeof(head));
	for(int i=2,x,y;i<=n;i++)read(x),read(y),read(a[i]),read(b[i]),ae(x,i,y);
	l[1]=1,dfs(1);
	for(int i=2;i<=n;i++)printf("%lld\n",f[i]);
	return 0;
}

发现我们每个位置都那么费劲地移动左右端点太蠢了。不如直接套上一个二分。复杂度是妥妥的\(O(n\log n)\)

(这二分真的非常难写,各种边界太恶心了)

代码:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
int n,head[1001000],cnt,a[1001000],b[1001000],q[1001000],l[1001000],r[1001000],cha[1001000];
ll f[1001000],dis[1001000];
void read(int &x){
	x=0;
	char c=getchar();
	while(c>'9'||c<'0')c=getchar();
	while(c>='0'&&c<='9')x=x*10+c-48,c=getchar();
}
struct node{
	int to,next,val;
}edge[1001000];
void ae(int u,int v,int w){
	edge[cnt].next=head[u],edge[cnt].to=v,edge[cnt].val=w,head[u]=cnt++;
}
void dfs(int x){
	int L,R,res;
	L=l[x],R=r[x]-1,res=r[x];
	while(L<=R){
		int mid=(L+R)>>1;
		if((f[q[mid]]-f[q[mid+1]])>=(dis[q[mid]]-dis[q[mid+1]])*a[x])L=mid+1;
		else R=mid-1,res=mid;
	} 
	l[x]=res;
	f[x]=(dis[x]-dis[q[l[x]]])*a[x]+b[x]+f[q[l[x]]];
	L=l[x]+1,R=r[x],res=l[x];
	while(L<=R){
		int mid=(L+R)>>1;
		if((f[q[mid-1]]-f[q[mid]])*(dis[q[mid]]-dis[x])>=(f[q[mid]]-f[x])*(dis[q[mid-1]]-dis[q[mid]]))R=mid-1;
		else res=mid,L=mid+1;
	}	
	r[x]=res;
	cha[x]=q[++r[x]],q[r[x]]=x;
	for(int i=head[x];i!=-1;i=edge[i].next)dis[edge[i].to]=dis[x]+edge[i].val,l[edge[i].to]=l[x],r[edge[i].to]=r[x],dfs(edge[i].to);
	q[r[x]]=cha[x];
}
int main(){
	read(n),memset(head,-1,sizeof(head));
	for(int i=2,x,y;i<=n;i++)read(x),read(y),read(a[i]),read(b[i]),ae(x,i,y);
	l[1]=1,dfs(1);
	for(int i=2;i<=n;i++)printf("%lld\n",f[i]);
	return 0;
}

原文地址:https://www.cnblogs.com/Troverld/p/14598423.html