1. 程式人生 > 實用技巧 >P6820 [PA2012]Two Cakes DP狀態優化

P6820 [PA2012]Two Cakes DP狀態優化

題意:

戳這裡

分析:

很久以前做過的題,在巨佬 fgf 的幫助下又學了一遍

  • 暴力

直接設 DP 狀態 \(f[i][j]\) 表示第一個序列列舉到第 \(i\) 位,第二個序列列舉到第 \(j\) 位時的最短時間,按題意模擬就好了

  • 正解

我們發現暴力做法中大部分的情況下 DP 都沒有做出任何決策直接轉移了,所以有好多狀態是無用的。所以我們只考慮 \(a[x]==b[y]\) 這種特殊狀態下的 DP,對於其他的情況直接轉移向 \(a[x]==b[y]\) 的情況

  1. \(a[x]==b[y]\)
f[x]=min(dfs(x-1,y),dfs(x,y-1))+1;

因為一個 \(x\)

一定只會對應一個 \(y\) ,所以這種情況只會遇到一次,那麼直接對於 \(x\) 記憶化就可以了,之後遇到直接返回值就可以了

  1. \(a[x]!=b[y]\)

我們需要找到之前他們剛好相等時的另一個點,我們注意到當轉移是不會影響兩個點的差值,所以我們直接對於每一類差值,存一下它們下一次相同時的點的位置,然後直接二分的查詢

複雜度 \(O(n \log n)\) , 因為決策點只有 \(n\) 個,每一次不同的情況都會 \(O(\log n)\) 的跳

程式碼:

#include<bits/stdc++.h>
#define pii pair<int,int>
#define mk(x,y) make_pair(x,y)
#define lc rt<<1
#define rc rt<<1|1
#define pb push_back
#define fir first
#define sec second

using namespace std;

namespace zzc
{
	inline int read()
	{
		int x=0,f=1;char ch=getchar();
		while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
		while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
		return x*f;
	}
	
	const int maxn = 1e6+5;
	int a[maxn],b[maxn],pos[maxn],f[maxn];
	int n;
	vector<int> del[maxn<<1];
	
	int get(int id,int pos)
	{
		int l=0,r=del[id].size()-1,mid,res=0;
		while(l<=r)
		{
			mid=(l+r)>>1;
			if(del[id][mid]<=pos)
			{
				res=del[id][mid];
				l=mid+1;
			}
			else r=mid-1;
		}
		return res;
	}
	
	int dfs(int x,int y)
	{
		if(!x||!y) return x|y;
		if(a[x]==b[y])
		{
			if(!f[x]) f[x]=min(dfs(x-1,y),dfs(x,y-1))+1;
			return f[x]; 
		}
		int pre=get(x-y+n,x);
		return pre?dfs(pre,y+pre-x)+(x-pre):max(x,y);
	}
	
	void work()
	{
		n=read();
		for(int i=1;i<=n;i++) a[i]=read();
		for(int i=1;i<=n;i++)
		{
			b[i]=read();
			pos[b[i]]=i;
		}
		for(int i=1;i<=n;i++) del[i-pos[a[i]]+n].pb(i);
		printf("%d\n",dfs(n,n));
	}

}

int main()
{
	zzc::work();
	return 0;
}