1. 程式人生 > 其它 >[HEOI2016/TJOI2016]序列(CDQ分治優化DP)

[HEOI2016/TJOI2016]序列(CDQ分治優化DP)

洛谷題目傳送門

解題思路

題目和最長上升子序列比較像,我們考慮用\(dp\)來解決

\(f_i\)表示以\(i\)這個位置結束的最長序列的長度,\(Max_i\)表示\(i\)這個位置最大的數,\(Min_i\)表示\(i\)這個位置最小的數,轉移時列舉上一個選的元素\(j\)是哪個位置

因為最多隻會變一個數,所以對於一個轉移點\(j\)

·如果是\(j\)的位置變化,那麼必須滿足它變的最大的數也是小於\(a_i\)的,也就是\(Max_j\leq a_i\)

·如果是\(i\)的位置變化,那麼必須滿足它變的最小的數也比\(a_j\)大,也就是\(a_j\leq Min_i\)
也就是我們的轉移式是這樣的

\[f_i=max(f_j|j<i,Max_j \leq a_i,a_j\leq Min_i)+1 \]

這個形式很像三維偏序的形式,可以使用樹套樹解決,不過我們知道CDQ可以解決一些樹套樹能夠解決的問題,考慮用CDQ分治優化轉移
我們回顧\(CDQ\)分治的過程,左半區間的\(dp\)值可以遞迴解決,然後我們就知道了左半部分每個位置的\(dp\)值,並且利用歸併可以將左半區間的元素按照\(Max\)值升序排序,然後考慮處理左區間對右區間的轉移

我們也可以提前對這些元素排序,滿足右半部分的元素按照\(a_i\)升序排序,然後我們按照\(CDQ\)分治的套路,維護雙指標\(i,j\)表示對於右半區間的元素\(j\)

,左半區間\(Max\)小於等於\(a_j\)的最遠的一個元素的位置是\(i\),這樣我們就解決掉了前兩維的限制,之後再用樹狀陣列維護第三維,就能完成轉移了

然後再遞迴計算右半區間內部的轉移

void CDQ(int l,int r)
{
	if(l==r) //邊界條件
	{
		f[q[l]]=max(f[q[l]],1);
		return;
	}
	int mid=(l+r)>>1,top=l;
	for(int i=l;i<=r;i++) if(q[i]<=mid) tmp[top++]=q[i]; //先按照第一維分成左右兩部分
	for(int i=l;i<=r;i++) if(q[i]>mid) tmp[top++]=q[i];
	for(int i=l;i<=r;i++) q[i]=tmp[i];
	CDQ(l,mid); //遞迴計算左邊
	top=l;
	int i=l,j=mid+1;
	while(i<=mid&&j<=r)
	{
		if(Max[q[i]]<=a[q[j]])	add(a[q[i]],f[q[i]]),i++; //在保證第二維的情況下用樹狀陣列維護第三維
		else f[q[j]]=max(f[q[j]],ask(Min[q[j]])+1),j++;			
	}
	while(j<=r) f[q[j]]=max(f[q[j]],ask(Min[q[j]])+1),j++;
	for(int k=l;k<=mid;k++) cover(a[q[k]],0);//清空樹狀陣列
	CDQ(mid+1,r); //遞迴計算右半部分
	i=l;j=mid+1;top=l;
	while(i<=mid&&j<=r) //按照第二維歸併,方便之後的計算
	{
		if(Max[q[i]]<=Max[q[j]]) tmp[top++]=q[i++];
		else tmp[top++]=q[j++];
	}
	while(i<=mid) tmp[top++]=q[i++];
	while(j<=r) tmp[top++]=q[j++];
	for(int k=l;k<=r;k++) q[k]=tmp[k];
}

關於這個轉移順序,不能寫成下面這種

void CDQ(int l,int r)
{
	CDQ(l,mid);
	CDQ(mid+1,r);
	calc(l,r)
}

原因是我們進行\(dp\)的時候需要保證轉移點的值是已經被完全計算好的,不會變的
但是如果按照上面的形式,我們左半區間的\(dp\)值,可能在更高一層才會被轉移,所以當前這一層時\(dp\)值並沒有被完全計算好,因此轉移時錯誤的

#include<bits/stdc++.h>
using namespace std;
const int N = 1e5+7;
typedef long long LL;
int tree[N];
int f[N];
int a[N];
int n;
int lowbit(int x)
{
	return x&(-x);
}
void add(int x,int v)
{
	for(int i=x;i<=1e5;i+=lowbit(i))
	tree[i]=max(tree[i],v);
}
int ask(int x)
{
	int res=0;
	for(int i=x;i;i-=lowbit(i))
	res=max(res,tree[i]);
	return res;
}
void cover(int x,int v)
{
	for(int i=x;i<=1e5;i+=lowbit(i))
	tree[i]=v;
}
int Min[N],Max[N];
int q[N],tmp[N];
void CDQ(int l,int r)
{

	if(l==r)
	{
		f[q[l]]=max(f[q[l]],1);
		return;
	}
	int mid=(l+r)>>1,top=l;
	for(int i=l;i<=r;i++) if(q[i]<=mid) tmp[top++]=q[i];
	for(int i=l;i<=r;i++) if(q[i]>mid) tmp[top++]=q[i];
	for(int i=l;i<=r;i++) q[i]=tmp[i];
	CDQ(l,mid);
	top=l;
	int i=l,j=mid+1;
	while(i<=mid&&j<=r)
	{
		if(Max[q[i]]<=a[q[j]])	add(a[q[i]],f[q[i]]),i++;
		else f[q[j]]=max(f[q[j]],ask(Min[q[j]])+1),j++;			
	}
	while(j<=r) f[q[j]]=max(f[q[j]],ask(Min[q[j]])+1),j++;
	for(int k=l;k<=mid;k++) cover(a[q[k]],0);
	CDQ(mid+1,r);
	i=l;j=mid+1;top=l;
	while(i<=mid&&j<=r)
	{
		if(Max[q[i]]<=Max[q[j]]) tmp[top++]=q[i++];
		else tmp[top++]=q[j++];
	}
	while(i<=mid) tmp[top++]=q[i++];
	while(j<=r) tmp[top++]=q[j++];
	for(int k=l;k<=r;k++) q[k]=tmp[k];
}
bool cmp(int x,int y)
{
	return a[x]<a[y];
}
int m;
int main()
{
	cin>>n>>m;
	for(int i=1;i<=n;i++)
	{
		int x;
		scanf("%d",&x);
		Min[i]=Max[i]=x;
		a[i]=x;
		q[i]=i;
	}
	for(int i=1;i<=m;i++)
	{
		int x,y;
		scanf("%d %d",&x,&y);
		Min[x]=min(Min[x],y);
		Max[x]=max(Max[x],y);
	}
	sort(q+1,q+n+1,cmp);
	CDQ(1,n);
	int ans=1;
	for(int i=1;i<=n;i++)
	ans=max(ans,f[i]);	
	cout<<ans;
	return 0;
}