1. 程式人生 > >決策單調性優化

決策單調性優化

決策單調性是對於一些dp式子,比如說ans[i] = max(a[j] + sqrt(i-j)) (j < i)
如果在一個i滿足a[j] + sqrt(i-j) < a[k] + sqrt(i - k)且j<k,那麼可以發現在i變大的時候j也一定會比k劣,沒有優於k的可能。
但是對於這個決策單調性的利用方式卻很多。
1.四邊形不等式優化二維dp,利用dp[i][j-1]的決策點<=dp[i][j]的<=dp[i+1][j]的。
在求解dp[i][j]的時候就可以縮小決策點範圍,做到O(n^2 * d),其中轉移一次的複雜度為O(d)
2.分治法優化形如dp[i][0n]轉移至dp[i+1][0

n]
這個就是用分治,solve(l,r,ql,qr)表示對於dp[i+1][ql到qr]的決策點已鎖定在dp[i][l,r]間,繼續往下分治,每次(掃l到r一遍)找到mid = (ql + qr) / 2的決策點k,然後執行solve(l,k,ql,mid-1),solve(k,r,mid+1,qr).
和後面那個比起來程式設計複雜度基本為0.。。。
3.二分+單調佇列維護
既然點的決策點滿足單調性,那麼我們可以反過來,決策點能夠作為最優決策點的點一定是連續的一段區間。
比如說我們用s[i]表示i的最優決策點。
可能它是這樣的:
s : 1 2 3 4 5 6 7 8 9 10…
= 1 1 2 2 2 3 3 5 6 7 8 …
所以用單調佇列維護這些決策點和他們的影響區間。。。。。。
加入一個新點的時候先看能不能將隊尾直接擠出,不能再用二分求出各自的影響區間。
細節超多,常數較上一個小了五分之一,也比上一個實用。

例題:[POI2011]Lightning Conductor

分治法

#include<bits/stdc++.h>
#define maxn 500005
using namespace std;

int n,a[maxn],ans[2][maxn];
inline double calc(int u,int v){ return sqrt(abs(v-u))+a[u]; }
void solve(int l,int r,int ql,int qr,int ans[maxn])
{
	if(l>r || ql>qr) return;
	int Minloc = l,mid=(ql+qr)>>1;double tmp = calc(l,mid) ,res = 0;
	for(int i=l+1;i<=r && i<=mid;i++) if(tmp<=(res=calc(i,mid))) tmp = res , Minloc = i;
	ans[mid] = max(ans[mid] , int(ceil(tmp)) - a[mid]);
	solve(l,Minloc,ql,mid-1,ans),solve(Minloc,r,mid+1,qr,ans);
}

int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++) scanf("%d",&a[i]);
	solve(1,n,1,n,ans[0]);
	for(int i=1;i<n-i+1;i++) swap(a[i],a[n-i+1]);
	solve(1,n,1,n,ans[1]);
	for(int i=1;i<=n;i++) printf("%d\n",max(ans[0][i],ans[1][n-i+1]));
}

單調佇列法:

#include<bits/stdc++.h>
#define maxn 500005
using namespace std;

char cb[1<<15],*cs=cb,*ct=cb;
#define getc() (cs==ct&&(ct=(cs=cb)+fread(cb,1,1<<15,stdin),cs==ct)?0:*cs++)
inline void read(int &res){ char ch;for(;!isdigit(ch=getc()););for(res=ch-'0';isdigit(ch=getc());res=res*10+ch-'0'); }
int n,a[maxn],ans[2][maxn];
inline double calc(int u,int v){ return a[u]+sqrt(abs(u-v)); }

void solve(int ans[maxn])
{
	deque<int>q;
	static int l[maxn],r[maxn];
	for(int i=1;i<=n;i++)
	{
		for(;q.size()>1 && calc(q.back(),l[q.back()]) <= calc(i,l[q.back()]);q.pop_back());
		if(!q.empty())
		{
			int L = max(l[q.back()],i) , R = r[q.back()]+1;
			for(int mid;L<R;)
			{
				mid = (L+R)>>1;
				if(calc(i,mid) >= calc(q.back(),mid)) R = mid;
				else L = mid+1;
			}
			l[i] = L , r[i] = n;
			r[q.back()] = L-1;
		}
		else l[i] = i , r[i] = n;
		q.push_back(i);
		for(;!q.empty() && r[q.front()] < i;q.pop_front());
		ans[i] = ceil(calc(q.front(),i)) - a[i];
	}
}

int main()
{
	read(n);
	for(int i=1;i<=n;i++) read(a[i]);
	solve(ans[0]);
	for(int i=1;i<n-i+1;i++) swap(a[i],a[n-i+1]);
	solve(ans[1]);
	for(int i=1;i<=n;i++) printf("%d\n",max(ans[0][i],ans[1][n-i+1]));
}