1. 程式人生 > >【樹形DP】【斜率優化】Tommy的結合

【樹形DP】【斜率優化】Tommy的結合

在這裡插入圖片描述

分析:

技巧比較多的一道題。
題目本身思維難度不算太大,主要是套路很多。

首先,有一個很顯然的DP
定義 D P [ i ] [ j ]

DP[i][j] 表示i和j匹配的情況下的最大貢獻。轉移時需要列舉下一個匹配點分別在哪,所以總的複雜度為 O ( n 4 )
O(n^4)
。然後就是非常套路地DP優化。

對於這種匹配類問題,可以換一種DP定義方式:
F ( i , j )

F(i,j) 表示i與j匹配時的最大貢獻(即上面的DP)
G ( i , j ) G(i',j) 表示,A選擇的 i i' 與下一個B選擇的數相匹配的最大貢獻(即此時A多選一個數 i i'

這樣定義DP後,可以發現,每次只需要列舉下一個點即可:
F ( i , j ) = m a x { G ( i , j ) ( t j + t j + 1 + + t j ) 2 } + v a l ( i , j ) F(i',j')=max\{G(i',j)-(t_j+t_{j+1}+……+t_{j'})^2\}+val(i',j')
G ( i , j ) = m a x { F ( i , j ) ( t i + t i + 1 + + t i ) 2 } G(i',j)=max\{F(i,j)-(t_i+t_{i+1}+……+t_{i'})^2\}
所以現在整個複雜度壓到了 O ( n 3 ) O(n^3)

然後,觀察這個式子,發現可以斜率優化。

然後就是套路地樹上斜率優化即可。

簡述一下樹上斜率優化的過程:
與普通的序列斜率優化不同,不能依次彈出隊尾/隊首元素,需要二分到合適的斜率位置彈出。同時,由於是樹上斜率優化,所以要考慮訪問某個子樹後,再回來的情況,此時必須排除所有之前子樹的節點的影響。這時如果依次彈回來顯然是比較劣的。正確的姿勢是:
由於每次插入一個元素,有兩種情況:
1、刪除隊尾的一段,再把刪完後的隊尾+1位置修改成當前點。
2、刪除隊首的一段。
所以,可以記錄加入當前這個點的影響前,其左端點/右端點原始的位置。以及當前點原來的數值。就可以做到O(1)消去一個點在我們維護的凸殼中的影響。

#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<vector>
#define SF scanf
#define PF printf
#define x first
#define y second
#define MAXN 2700
#define INF (1ll<<60)
using namespace std;
typedef long long ll;
void Read(int &x){
	x=0;
	char c;
	bool flag=0;
	while(c=getchar(),c!=EOF&&(c<'0'||c>'9')&&c!='-');
	if(c=='-') flag=1;
	else x=c-'0';
	while(c=getchar(),c!=EOF&&c>='0'&&c<='9') x=x*10+c-'0';	
	if(flag) x=-x;
}
int pa[MAXN],pb[MAXN],ta[MAXN],tb[MAXN];
int val[MAXN][MAXN];
int old1[MAXN][MAXN],now;
ll ans=-INF;
double get_k(pair<int,ll> a,pair<int,ll> b){
	return double(b.y-a.y)/(b.x-a.x);
}
vector<int> a[MAXN],b[MAXN];
struct Bac{
	bool flag;
	int pos;
	double k;
	pair<int,ll> p;
};
struct node{
	pair<int,ll> q[MAXN];
	double k[MAXN];
	Bac bac[MAXN*2];
	int top,l,r;
	void clear(){
		top=r=0;
		l=1;
		k[1]=1.0/0.0;	
	}
	void add(pair<int,ll> x){	
		bac[++top].flag=1;
		bac[top].pos=r;
		if(l>r||k[r]>get_k(q[r],x))
			r++;
		else{
			int l1=l,r1=r,res=l;
			while(l1<=r1){
				int mid=(l1+r1)>>1;
				if(k[mid]>get_k(q[mid],x))
					l1=mid+1;
				else{
					res=mid;
					r1=mid-1;
				}
			}
			r=res;
		}
		bac[top].p=q[r];
		bac[top].k=k[r];
		q[r]=x;
		if(l<r)
			k[r]=get_k(q[r-1],x);	
		else
			k[r]=1.0/0.0;
	}
	ll query(ll k1){
		bac[++top].flag=0;
		bac[top].pos=l;
		int l1=l,r1=r,res=l;
		while(l1<=r1){
			int mid=(l1+r1)>>1;
			if(k[mid]>=k1){
				res=mid;
				l1=mid+1;
			}
			else
				r1=mid-1;	
		}
		l=res;
		return q[l].y-k1*q[l].x;
	}
	void dework(int old){
		while(top>old){
			if(bac[top].flag==1){
				q[r]=bac[top].p;
				k[r]=bac[top].k;
				r=bac[top].pos;
			}
			else
				l=bac[top].pos;
			top--;
		}
	}
}Qf[MAXN],Qg;
ll f[MAXN][MAXN],g[MAXN][MAXN];
ll sqr(ll x){
	return x*x;	
}
void dfsB(int x,int fa=1){
	int old=Qg.top;
	int tax=ta[now],tax1=ta[pa[now]];
	int tbx=tb[x],tbx1=tb[pb[x]];
	f[now][x]=Qf[x].query(-2*tax1)-sqr(tax1)+val[now][x];
	ans=max(ans,f[now][x]);
	if(pb[x]!=1)
		g[now][x]=Qg.query(-2*tbx1)-sqr(tbx1);
	Qg.add(make_pair(tbx,f[now][x]-sqr(tbx)));
	if(pb[x]!=1)
		Qf[x].add(make_pair(tax,g[now][x]-sqr(tax)));
	for(int i=0;i<int(b[x].size());i++)
		dfsB(b[x][i],x);
	Qg.dework(old);
}
int n,m;
void dfsA(int x,int fa=1){
	int *old=old1[x];
	Qg.clear();
	now=x;
	for(int i=2;i<=m;i++)	
		old[i]=Qf[i].top;
	for(int i=0;i<int(b[1].size());i++)
		dfsB(b[1][i]);
	for(int i=0;i<int(a[x].size());i++)
		dfsA(a[x][i],x);
	for(int i=2;i<=m;i++)
		Qf[i].dework(old[i]);
}
int main(){
	Read(n),Read(m);
	for(int i=2;i<=n;i++) Read(ta[i]);
	for(int i=2;i<=m;i++) Read(tb[i]);
	for(int i=2;i<=n;i++){
		Read(pa[i]);
		a[pa[i]].push_back(i);
	}
	for(int i=2;i<=m;i++){
		Read(pb[i]);
		b[pb[i]].push_back(i);
	}
	for(int i=2;i&l