线性dp 基础课+提高课
1
题意:给定有序对 两个数分别在两根数轴上,问如果将有序对连线,最多可以连几条(保证任意一个点至多有唯一的数与之对应)
思维 +LIS
按照任意一维数据排序之后做LIS即可保证题目的要求
代码如下:
#include<iostream>
#include<algorithm>
using namespace std;
const int N=5010;
int n;
int f[N];
struct Node{
int a,b;
bool operator<(const Node&s)const
{
return a<s.a;
}
}node[N];
int main()
{
cin>>n;
for(int i=1;i<=n;i++)scanf("%d%d",&node[i].a,&node[i].b);
sort(node+1,node+n+1);
int res=0;
for(int i=1;i<=n;i++)
{
f[i]=1;
for(int j=1;j<i;j++)
if(node[j].b<node[i].b)f[i]=max(f[i],f[j]+1);
res=max(res,f[i]);
}
cout<<res;
}
2
LIC2模板
二分优化暴力的LIC1
(这道题可以和单调队列对比一下~)
3 1 4 5 6
如上面的例子 能接在3后面的一定能接在1后面,且1的阈值更大
维护数组q,q[i]为长度为i的上升子序列下的最小的一个a(原数组的值)
可以看出这样的q是一个单调的 每次从q中找出小于a[i]的最大的数字 接在它的后面是最优的,然后更新一下q数组即可
代码如下:
#include<iostream>
using namespace std;
const int N=1e5+10;
int a[N],q[N];
int main()
{
int n;
cin>>n;
for(int i=1;i<=n;i++)scanf("%d",&a[i]);
int len=0;
int l,r;
for(int i=1;i<=n;i++)
{
l=0,r=len;
while(l<r)
{
int mid=l+r+1>>1;
if(q[mid]>=a[i])r=mid-1;
else l=mid;
}
len=max(len,l+1);
q[l+1]=a[i];
}
cout<<len;
}
3
LIS 模板
这个和一般的集合的划分后计算不太一样,是一种萌新的我不能自己推出的 是看了之后了解的这种计算的方式
代码如下:
#include<iostream>
using namespace std;
const int N=1010;
int f[N][N];
int n,m;
char a[N],b[N];
int main()
{
cin>>n>>m;
scanf("%s",a+1);
scanf("%s",b+1);
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
{
f[i][j]=max(f[i][j-1],f[i-1][j]);
if(a[i]==b[j])f[i][j]=max(f[i][j],f[i-1][j-1]+1);
}
cout<<f[n][m];
}
4
这道题的贪心的证明思路对我这种小萌新比较有启发,所以写一下
贪心的思路:
从头到尾扫,当前元素接到能接上的最小的后面 (这样对阈值的”使用“最小)
如果都不能接上的话 我们就开一个新的位置
证明如下:
即如何证明贪心解是我们的最优解
①首先贪心解一定是我们的一个合法解
②从最优解中找到我们第一个和贪心解不一样的地方,
-----a-x-----贪心解
-----b-x-----最优解
a<=b 故可以将最优解调整为贪心解 所以我们的贪心思路正确
(这里给出了证明两个集合相等的方法 也是最常用的一个方法)
另外这里有不限定元素的读取方法
还有一种是流读入如下图所示:
string line;
getline(cin,line);
stringstream ssin(line);
while(ssin>>a[n])n++;
代码如下:
#include<iostream>
using namespace std;
const int N=1e5+10;
int f[N];
int a[N];
int g[N];
int n=1;
int main()
{
while(cin>>a[n])n++;
n--;
int res=1;
for(int i=1;i<=n;i++)
{
f[i]=1;
for(int j=1;j<i;j++)
if(a[j]>=a[i])f[i]=max(f[i],f[j]+1);
res=max(res,f[i]);
}
cout<<res<<endl;
int cnt=0;
for(int i=1;i<=n;i++)
{
int k=1;
while(k<=cnt&&g[k]<a[i])k++;
g[k]=a[i];
if(k>cnt)cnt++;
}
cout<<cnt;
}
5
导弹防御系统 187. 导弹防御系统 - AcWing题库
贪心+dfs 有上一题的基础套上一个dfs即可
代码如下:
#include<iostream>
using namespace std;
const int N=55;
int a[N];
int n;
int T;
int ans;
int up[N],down[N];
void dfs(int u,int st1,int st2)
{
if(st1+st2>=ans)return;
if(u>n)
{
ans=st1+st2;
return;
}
int k=1,t;
while(k<=st1&&up[k]>=a[u])k++;
t=up[k];
up[k]=a[u];
if(k>st1)dfs(u+1,st1+1,st2);
else dfs(u+1,st1,st2);
up[k]=t;
k=1;
while(k<=st2&&down[k]<=a[u])k++;
t=down[k];
down[k]=a[u];
if(k>st2)dfs(u+1,st1,st2+1);
else dfs(u+1,st1,st2);
down[k]=t;
}
int main()
{
while(cin>>n,n)
{
for(int i=1;i<=n;i++)scanf("%d",&a[i]);
ans=n;
dfs(1,0,0);
cout<<ans<<endl;
}
}
6
最长公共上升子序列
LCS+LIS 的耦合题
首先这道题的一个关键点是我们的a与b因为是等价的(公共子序列保证了)
所以用a来解决LCS b保证LIS
这个题的集合划分和计算比较有意思,然后需要对n3优化
集合划分如下
暴力代码如下:
#include<iostream>
using namespace std;
const int N=3010;
int f[N][N];
int a[N],b[N];
int n;
int main()
{
cin>>n;
for(int i=1;i<=n;i++)scanf("%d",&a[i]);
for(int j=1;j<=n;j++)scanf("%d",&b[j]);
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])
for(int k=0;k<j;k++)
if(b[k]<b[j])f[i][j]=max(f[i-1][k]+1,f[i][j]);
}
}
int res=-1;
for(int i=1;i<=n;i++)res=max(res,f[n][i]);
cout<<res;
}
这里看最内层的for循环其实就是在求我们的满足b[k]<a[i]的情况下的f[i-1][k]的最大值 发现与kj无关 只需要用一个变量维护这个前缀的最大值即可~~优化后如下:
#include<iostream>
using namespace std;
const int N=3010;
int f[N][N];
int a[N],b[N];
int n;
int main()
{
cin>>n;
for(int i=1;i<=n;i++)scanf("%d",&a[i]);
for(int j=1;j<=n;j++)scanf("%d",&b[j]);
for(int i=1;i<=n;i++)
{
int maxv=1;
for(int j=1;j<=n;j++)
{
f[i][j]=f[i-1][j];
if(a[i]==b[j])f[i][j]=max(f[i][j],maxv);
if(b[j]<a[i])maxv=max(maxv,f[i-1][j]+1);
// if(a[i]==b[j])
// {
// f[i][j]=max(f[i][j],1);
// for(int k=1;k<j;k++)//这里其实就是在求我们的满足b[k]<a[i]的情况下的
//f[i-1][k]的最大值 发现与kj无关 只需要用一个变量维
//护这个前缀的最大值即可~~
// if(b[k]<a[i]])f[i][j]=max(f[i][j],f[i-1][k]+1);
// }
}
}
int res=-1;
for(int i=1;i<=n;i++)res=max(res,f[n][i]);
cout<<res;
}
参考:acwing 算法提高课 算法基础课