动态规划简介:动态规划是在一个困难的 嵌套决策链 中,决策出最优解。动态规划有 可推导性 ,但同时,动态规划也有 无后效性 ,即 每个当前状态会且仅会决策出下一状态,而不直接对未来的所有状态负责 。
子序列问题
首先声明2个名词:
$ operatorname{LIS} $ :Longest Increasing Subsequence 最长递增子序列
$ operatorname{LCS} $ :Longest Common Subsequence 最长公共子序列
我们知道,用朴素的 $ operatorname{DP} $ 解 $ operatorname{LIS} $ 和 $ operatorname{LCS} $ ,复杂度都是 (O(n^2)) 的,而且很容易写出来。
for(int i=1;i<=n;i++)
{
dp[i]=1;
for(int j=1;j<i;j++)
if(val[j]<val[i] && dp[i]<dp[j]+1) dp[i]=dp[j]+1,pre[i]=j;
} // LIS
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++)
{
dp[i][j]=max(dp[i-1][j],dp[i][j-1]);
if(a[i]==b[j]) dp[i][j]=max(dp[i][j],dp[i-1][j-1]+1);
}
} // LAS
↑ 这便是极为低效的 (O(n^2)) 做法
考虑用 (O(n log n)) 算法:用 (dp[i]) 表示在该序列中,上升子序列长度为 (i) 的上升子序列,的最小末尾数值。这样可以二分求出比当前数值小的最长子序列。
$ operatorname{LIS} $ 核心代码:
memset(dp,inf,sizeof(dp));
dp[0]=0;
for(int i=1;i<=n;i++)
{
if(val[i]>dp[ans]) dp[++ans]=val[i]; // 比之前最长的子序列的最大值还大,则 ans += 1
else
{
int l=0,r=ans,ret=0;
while(l<r)
{
int mid=(l+r)/2;
if(dp[mid]<=val[i]) l=mid+1; // 找到第一个比 dp[...] 小的数 (这一题中是严格递增)
else r=mid-1,ret=mid;
}
dp[l]=min(dp[l],val[i]);
}
}
在特殊条件下,即两个序列的元素种类相同,可以用类似离散化的方式将一个序列变为 (1…n) 的序列,此时的 $ operatorname{LCS} $ 就是另一个序列的 $ operatorname{LIS} $ 则在这种条件下可以 (O(nlogn)) 地处理 $ operatorname{LCS} $ 问题
$ operatorname{LCS} $ 核心代码:
memset(dp,inf,sizeof(dp));
dp[0]=0;
for(int i=1;i<=n;i++)
{
if(Turn[b[i]]>dp[ans]) dp[++ans]=Turn[b[i]]; // 比之前最长的子序列的最大值还大,则 ans += 1
else
{
int l=0,r=ans,ret=0;
while(l<r)
{
int mid=(l+r)/2;
if(dp[mid]<=Turn[b[i]]) l=mid+1; // 找到第一个比 Turn[b[i]] 小的数
else r=mid-1,ret=mid;
}
dp[l]=min(dp[l],Turn[b[i]]);
}
}
LIS变形——平面吃豆子问题
题意:给定一张平面直角坐标系,你现在处在 ((0,0)) 的位置,而有些网格点上有豆子。如果你在 ((x,y)) ,只能前往 ((x+1,y),(x,y+1),(x+1,y+1)) 。请问最多能够吃到多少颗豆子?
(nle 10^5,-10^9le x_i,y_i le 10^9) 。
首先找出 (x_ige 0,y_ige 0) 的所有点,并对他们根据 (x) 为第一关键字,(y) 为第二关键字排序。
之后就对所有 (y) 跑一边 (operatorname{LIS}) 就可以啦
$ exttt{code}$
#define inf 0x3f3f3f3f
#define Maxn 100005
int n,m,ans,dp[Maxn];
struct Data { int x,y; }a[Maxn];
bool cmp(Data x,Data y)
{
if(x.x!=y.x) return x.x<y.x;
return x.y<y.y;
}
int main()
{
n=rd();
for(int i=1,x,y;i<=n;i++)
{
x=rd(),y=rd();
if(x<0 || y<0) { i--,n--; continue; }
a[i]=(Data){x,y};
}
sort(a+1,a+n+1,cmp);
memset(dp,inf,sizeof(dp)),dp[0]=0;
for(int i=1;i<=n;i++)
{
if(a[i].y>=dp[ans]) dp[++ans]=a[i].y;
else
{
int nl=0,nr=ans,mid,ret=0;
while(nl<=nr)
{
mid=(nl+nr)>>1;
if(dp[mid]<=a[i].y) nl=mid+1;
else nr=mid-1,ret=mid;
}
dp[ret]=min(dp[ret],a[i].y);
}
}
printf("%d
",ans);
return 0;
}