文章目录
- P1020 [NOIP1999 普及组] 导弹拦截
- P1439 【模板】最长公共子序列
- P1637 三元上升子序列
- 272. 最长公共上升子序列
- LCIS

P1020 [NOIP1999 普及组] 导弹拦截
P1020 [NOIP1999 普及组] 导弹拦截
导弹拦截应该是接触DP的第一题(只不过洛谷上的数据加强了,按照上图的 O ( N 2 ) O(N^2) O(N2)做法过不去QAQ可以改用二分。
Dilworth定理:偏序集能划分成的最少的全序集个数等于最大反链的元素个数
把一个数列划分成最少的最长不升子序列的数目就等于这个数列的最长上升子序列的长度(LIS)
#include<bits/stdc++.h>
using namespace std;
int a[100010],LDS[100010],LIS[100010];
int main()
{
int x,i=0,len=1;
while(~scanf("%d",&x))
a[i++]=x;
LDS[0]=a[0];
for(int j=1;j<i;j++)
{
if(a[j]<=LDS[len-1])
LDS[len++]=a[j];
else
{
int l=0,r=len;
while(l<r)
{
int mid=(l+r)>>1;
if(a[j]>LDS[mid]) r=mid;
else l=mid+1;
}
LDS[l]=a[j];
}
}
cout<<len<<endl;
LIS[0]=a[0];
int len2=1;
for(int j=1;j<i;j++)
{
if(a[j]>LIS[len2-1])
LIS[len2++]=a[j];
else
{
int l=0,r=len2;
while(l<r)
{
int mid=(l+r)>>1;
if(a[j]<=LIS[mid]) r=mid;
else l=mid+1;
}
LIS[l]=a[j];
}
}
cout<<len2<<endl;
return 0;
}
P1439 【模板】最长公共子序列
P1439 【模板】最长公共子序列
也不给你用上图的朴素DP过去( O ( N 2 ) O(N^2) O(N2))www
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(a1[i]==a2[j])
dp[i][j]=max(dp[i][j],dp[i-1][j-1]+1);
}
cout<<dp[n][m];
但本题数据有个明显的特征即两个序列都是全排列,也就是说我们可以重新构造一种映射关系。
#include<bits/stdc++.h>
using namespace std;
int a[100010],b[100010],LIS[100010];
int main()
{
int n,i;
cin>>n;
//由于a b 中元素完全相同,只是顺序不同,可以利用映射关系,将a映射成一个单调递增序列
for(int i=1;i<=n;i++)
{
int x;
scanf("%d",&x);
a[x]=i;
}
//相应地,由a给出的映射关系,可将b构造成一个新的序列
for(int i=1;i<=n;i++)
{
int y;
scanf("%d",&y);
b[i]=a[y];
}
// 两个序列的子序列,一定是a的子序列。而a本身就是单调递增的。因此这个子序列是单调递增的。
//换句话说,只要这个子序列在b中单调递增,它就是a的子序列
//哪个最长呢?当然是b的LIS最长。下用二分+贪心求b的LIS即可
LIS[1]=b[1];
int len2=1;
for(int j=2;j<=n;j++)
{
if(b[j]>LIS[len2])
LIS[++len2]=b[j];//如果此时检索到的数比LIS的最后一个数(也即最大的数)大,直接插到LIS尾部
else//否则,在LIS前面的数中二分查找合适的位置进行替换
{
int l=1,r=len2;
while(l<r)
{
int mid=(l+r)>>1;
if(b[j]<=LIS[mid]) r=mid;
else l=mid+1;
}
LIS[l]=b[j];
}
}
cout<<len2<<endl;
return 0;
}
P1637 三元上升子序列
P1637 三元上升子序列
UVA12983 The Battle of Chibi
给定一个长度为n的序列,求其包含的m元单调上升子序列个数。
见求LIS思DP。导弹拦截那种题,开f[结尾编号]=序列长度,信息量不够,考虑定义f[序列长度][结尾编号] =序列个数。
状态转移: f [ i ] [ j ] = ∑ f [ i − 1 ] [ k ] , 1 < = k < j , a [ k ] < a [ i ] f[i][j]=\sum f[i-1][k],1<=k<j,a[k]<a[i] f[i][j]=∑f[i−1][k],1<=k<j,a[k]<a[i]
这样做的时间复杂度为 O ( n 2 ∗ m ) O(n^2*m) O(n2∗m)
得想办法在更短时间内统计出 ∑ f [ i − 1 ] [ k ] \sum f[i-1][k] ∑f[i−1][k],树状数组或者线段树都可以实现在 O ( l o g n ) O(log n) O(logn)内的区间统计。
先离散化,将a[]的分布集中起来,以a[k]为下标储存f[i-1][k]的值。
此时针对状态转移方程写两层循环:外层循环枚举长度,每次枚举到一个长度都重置一下树状数组c[]。{内层枚举序列结尾坐标,此前已将f[i-1][k]增加到树状数组中,此时只需用树状数求和即可。然后把f[i-1][j]加到树状数组中,这个值只会更新到比a[j]大的数(对应状态转移方程里的a[k]<a[i]),这些数后续会被调用(对应状态转移方程里的1<=k<j)}。
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int N=30010;
ll n,f[4][N],a[N],b[N],c[N],ans;
ll lowbit(ll x) { return x&(-x);}
void update(ll p,ll v)
{
for(int i=p;i<=n;i+=lowbit(i))
c[i]+=v;
}
ll ask(int p)
{
ll sum=0;
for(int i=p;i;i-=lowbit(i))
sum+=c[i];
return sum;
}
int main()
{
cin>>n;
for(int i=1;i<=n;i++)
scanf("%lld",&a[i]),b[i]=a[i];
sort(b+1,b+n+1);
unique(b+1,b+n+1)-(b+1);
for(int i=1;i<=n;i++)
{
a[i]=lower_bound(b+1,b+n+1,a[i])-b;//二分查找在数组中的位置
f[1][i]=1;//初始化:长度唯一,本身为结尾
}
//离散化结束,初始化结束,准备工作完成
for(int i=2;i<=3;i++)//枚举长度
{
memset(c,0,sizeof(c));//重置树状数组
for(int j=1;j<=n;j++)
{
f[i][j]=ask(a[j]-1);//求和
update(a[j],f[i-1][j]);//更新
}
}
for(int i=1;i<=n;i++)
ans+=f[3][i];
cout<<ans;
return 0;
}
272. 最长公共上升子序列
272. 最长公共上升子序列
定义 f [ i ] [ j ] f[i][j] f[i][j]: a [ 1 ] − a [ i ] , b [ 1 ] − b [ j ] a[1]-a[i],b[1]-b[j] a[1]−a[i],b[1]−b[j]可以构成的以 b [ j ] b[j] b[j]结尾的LCIS的长度。
#include<bits/stdc++.h>
using namespace std;
const int N=3010;
int f[N][N];
int n,a[N],b[N];
int main()
{
cin>>n;
for(int i=1;i<=n;i++)
scanf("%d",&a[i]);
for(int i=1;i<=n;i++)
scanf("%d",&b[i]);
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
{
f[i][j]=f[i-1][j];
if(a[i]==b[j])
{
int maxv=1;
for (int k=1;k<j;k++)
if (a[i]>b[k]) maxv=max(maxv, f[i - 1][k] + 1);
f[i][j]=max(f[i][j], maxv);
}
}
int ans=0;
for(int j=1;j<=n;j++)
ans=max(ans,f[n][j]);
cout<<ans<<endl;
return 0;
}
TLE了,发现可以没必要枚举k,可以用val实时更新长度。
#include<bits/stdc++.h>
using namespace std;
const int N=3010;
int f[N][N];//a1~ai,b1~bj可以构成的以bj结尾的LCIS的长度
int n,a[N],b[N],ans=0,m;
int main()
{
cin>>n;
for(int i=1;i<=n;i++)
scanf("%d",&a[i]);
for(int i=1;i<=n;i++)
scanf("%d",&b[i]);
for(int i=1;i<=n;i++)
{
int val=0;//(i,0)的LCIS长度
//i固定,b1->bn时LCIS长度一定是单调递增的
for(int j=1;j<=n;j++)
{
if(a[i]==b[j]) f[i][j]=val+1;//遍历到一个ai、bj相同的位置,长度++
else f[i][j]=f[i-1][j];//不相等,直接继承(i-1,j)的长度
if(b[j]<a[i]) val=max(val,f[i-1][j]);//更新val:(i,j)前面的LCIS长度
//另一种情况:(b[j]>a[i])一定不会影响到LCIS的长度
}
}
for(int j=1;j<=n;j++)//找出所有以bj结尾的LCIS中长度最大的
ans=max(ans,f[n][j]);
cout<<ans<<endl;
return 0;
}
进一步发现f的第一维也是可以省略的。
#include<bits/stdc++.h>
using namespace std;
const int N=3010;
int f[N];//a1~ai,b1~bj可以构成的以bj结尾的LCIS的长度
int n,a[N],b[N],ans=0,m;
int main()
{
cin>>n;
for(int i=1;i<=n;i++)
scanf("%d",&a[i]);
for(int i=1;i<=n;i++)
scanf("%d",&b[i]);
for(int i=1;i<=n;i++)
{
int val=0;//(i,0)的LCIS长度
//i固定,b1->bn时LCIS长度一定是单调递增的
for(int j=1;j<=n;j++)
{
if(a[i]==b[j]) f[j]=val+1;//遍历到一个ai、bj相同的位置,长度++
if(b[j]<a[i]) val=max(val,f[j]);//更新val:(i,j)前面的LCIS长度
//另一种情况:(b[j]>a[i])一定不会影响到LCIS的长度
}
}
for(int j=1;j<=n;j++)//找出所有以bj结尾的LCIS中长度最大的
ans=max(ans,f[j]);
cout<<ans<<endl;
return 0;
}
LCIS
LCIS
加上路径输出。
#include<bits/stdc++.h>
using namespace std;
const int N=3010;
int f[N];//a1~ai,b1~bj可以构成的以bj结尾的LCIS的长度
int n,a[N],b[N],ans[N],m,scheme[N],p;
int main()
{
cin>>n;
for(int i=1;i<=n;i++)
scanf("%d",&a[i]);
cin>>m;
for(int i=1;i<=m;i++)
scanf("%d",&b[i]);
for(int i=1;i<=n;i++)
{
int id=0;
//i固定,b1->bn时LCIS长度一定是单调递增的
for(int j=1;j<=m;j++)
{
if(a[i]==b[j]) f[j]=f[id]+1,scheme[j]=id;
//遍历到一个ai、bj相同的位置,长度++,记录下j前面一个位置是id
if(b[j]<a[i]&&f[id]<f[j]) id=j;
}
}
int p=0;
for(int i=1;i<=m;i++)
if(f[i]>f[p]) p=i;//找出所有以bj结尾的LCIS中长度最大的
printf("%d\n",f[p]);
int top=0;
while(f[p]--)
{
ans[++top]=b[p];
id=scheme[p];//从后往前倒出来
}
for(int i=top;i;i--)
printf("%d ",ans[i]);//输出方案前需要记录
return 0;
}