P3637 最长上升子序列
该题为动规板子题,主要思路:如果一个上升子序列的最大值小于它后面的一个数,那么这个子序列的长度就可以增加1,即将这个数纳入子序列,并保留以这个数结束的最优情况,比较以各个数结束的最优情况,用max取出最大值
代码如下:
#include<bits/stdc++.h>
using namespace std;
const int N=1e4+5;
int a[N];
int main()
{
int n;
cin>>n;
int dp[N];
for(int i=1;i<=n;i++)
{
cin>>a[i];
dp[i]=1;
}
int maxx=1;
for(int i=1;i<=n;i++)
{
for(int j=1;j<=i-1;j++)
{
if(a[i]>a[j])
dp[i]=max(dp[j]+1,dp[i]);
}
maxx=max(dp[i],maxx);
}
cout<<maxx;
return 0;
}
P1115 最大字段和
比较后一个数与前一个数之前的的最优情况加上这个数后的大小,若后一个数较大,则没有必要用前面的数之和加上这个数,以这个数开始即可,相反,后一个数相对较小,则加上这个数,并依次比较下去,期间用max一直记录最大值
还是题解说得明白:
- 如果一个数加上上一个有效序列得到的结果比这个数大,那么该数也属于这个有效序列。
- 如果一个数加上上一个有效序列得到的结果比这个数小,那么这个数单独成为一个新的有效序列
代码如下:
#include<bits/stdc++.h>
using namespace std;
const int N=2e5+5;
int num[N],dp[N];
int main()
{
int n;
cin>>n;
memset(num,0,sizeof(num));
memset(dp,0,sizeof(dp));
for(int i=1;i<=n;i++)
cin>>num[i];
int maxx=-2147483647;
for(int i=1;i<=n;i++)
{
dp[1]=num[1];
dp[i]=max(dp[i-1]+num[i],num[i]);
maxx=max(dp[i],maxx);
}
cout<<maxx;
return 0;
}
P8707 [蓝桥杯 2020 省 AB1] 走方格
从题目来看,不难得出结论:第1行以及第1列的所有位置都可自成一种情况,故都赋上1
然后开始对横坐标都为偶数的点进行标注,即赋值为0
最后可以得到状态转移方程:
代码如下:
#include<bits/stdc++.h>
using namespace std;
const int N=35;
int dp[N][N];
int main()
{
int n,m;
cin>>n>>m;
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
dp[i][1]=1,dp[1][j]=1;
for(int i=2;i<=n;i++)
for(int j=2;j<=m;j++)
{
if(i%2==0&&j%2==0)
dp[i][j]=0;
else
dp[i][j]=dp[i-1][j]+dp[i][j-1];
}
cout<<dp[n][m];
return 0;
}
P1216 数字三角形 Number Triangles
本题属于数塔模型,解决思路为:先递后归
将问题先分为各个小部分,首先从上往下分析可得,数之和若要取得最优解,直接受决定于这个数下面的左右子树的最优解,以此类推,问题最终解需要从数塔的最底层开始找,然后进行每轮比较,最终得出最大值
代码如下:
#include<bits/stdc++.h>
using namespace std;
const int N=1e3+5;
int dp[N][N];
int main()
{
int r;
cin>>r;
memset(dp,0,sizeof(dp));
for(int i=1;i<=r;i++)
for(int j=1;j<=i;j++)
cin>>dp[i][j];
int maxx=0;
for(int i=r-1;i>=1;i--)
{
for(int j=1;j<=i;j++)
{
dp[i][j]+=max(dp[i+1][j],dp[i+1][j+1]);
maxx=max(maxx,dp[i][j]);
}
}
cout<<maxx;
return 0;
}
P1020 导弹拦截
这题真的ex,好好地不行,非得,
这是代码:
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+5;
int main()
{
int a[N],m=1,dp[N],ans=0;
memset(dp,0,sizeof(dp));
while(cin>>a[m])
m++;
m--;
for(int i=1;i<=m;i++)
{
dp[i]=1;
for(int j=1;j<=m;j++)
{
if(a[i]<=a[j]&&i!=j)
dp[i]=max(dp[i],dp[j]+1);
}
ans=max(dp[i],ans);
}
cout<<ans<<endl;
memset(dp,0,sizeof(dp));
ans=0;
for(int i=1;i<=m;i++)
{
dp[i]=1;
for(int j=1;j<=m;j++)
{
if(a[i]>a[j]&&i!=j)
dp[i]=max(dp[i],dp[j]+1);
}
ans=max(dp[i],ans);
}
cout<<ans;
return 0;
}
结果:
所以思前想后,如何利用二分查找解决此问题,是这样的:
第一个问题:
此问题即找到一个最长非递增序列,那么可以先得到一个初始子序列,后一个数如果比最后一个数小,那么就接在这个数的后面,形成新一个子序列,但是如果比最后一个数大,就对其所在区间位置进行二分查找,并且替换掉较小数。(为了实现最长,则最后一个数越大就越有可能实现)一直进行下去直至最后,就可得到最终的最长非递增子序列
第二个问题:
为了简化运算以及思路,引入Dilworth定理:
狄尔沃斯定理亦称偏序集分解定理,该定理断言:对于任意有限偏序集,其最大反链中元素的数目必等于最小链划分中链的数目。此定理的对偶形式亦真,它断言:对于任意有限偏序集,其最长链中元素的数目必等于其最小反链划分中反链的数目。
在本题中,此定理的运用为:第二个问题可以转化成将第一个问题中的数反序排列,然后再求一遍最长非递增子序列,证明如下:
-
当 n=1 时:命题显然成立。
-
假设对于 n=k 结论成立,考虑 n=k+1 的情况:
当 A 中最长链的长度为 k+1 时,令 M 为 A 中极大元的集合,显然 M 是一条反链。而且 A−M 中最长链的长度为 k。
由归纳假设,可以把 A−M 分成至少 k 个不相交的反链,加上反链 M,则 A 可分成至少 k+1 条反链。
因为 A 不可能分解成 n−1 条反链。假若只有 n−1 条反链,那么最长链的 n 个元素中必有 22 个元素被分到同一个反链,显然这与反链的定义矛盾。(摘自洛谷题解)
代码如下:
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+5;
int main()
{
int m=1;
int a[N];
memset(a,0,sizeof(a));
while(cin>>a[m])
m++;
m--;
int d[N];
memset(d,0,sizeof(d));
int cou=1;
d[1]=a[1];
int xx=0;
for(int i=2;i<=m;i++)
{
if(a[i]<=d[cou])
{
cou++;
d[cou]=a[i];
}
else
{
int l=1,r=cou,mid=(r+l)>>1;
while(l<r)
{
mid=(r+l)>>1;
if(d[mid]<a[i])
r=mid;
else
l=mid+1;
}
d[l]=a[i];
}
}
cout<<cou<<endl;
cou=1;
reverse(a+1,a+1+m);
memset(d,0,sizeof(d));
d[1]=a[1];
for(int i=2;i<=m;i++)
{
if(a[i]<d[cou])
{
cou++;
d[cou]=a[i];
}
else
{
int l=1,r=cou,mid=(r+l)>>1;
while(l<r)
{
mid=(r+l)>>1;
if(d[mid]<=a[i])
r=mid;
else
l=mid+1;
}
d[l]=a[i];
}
}
cout<<cou;
return 0;
}
以下是选做题:
AT_dp_f LCS
最长公共子序列,英文缩写为LCS(Longest Common Subsequence)。定义为一个序列 S ,如果分别是两个或多个已知序列的子序列,且是所有符合此条件序列中最长的,则 S 称为已知序列的最长公共子序列。
求最大长度:
对于本题,要对字符串1和字符串2依次进行遍历,判断每个字符是否一样,每当有相同字符出现的时候,进行一次dp,这时的dp只与字符串1前一位和字符串2的前一位有关,由于要找到最长子序列,故要将该字符纳入子串,即:
当遍历到不相同的字符时,则根据动规的最有子序列的特性,此次情况与字符串1上一个字符以及字符串2遍历上一个字符的情况有关,最优的话,则取二者最大值,即:
求最大子序列:
说真的这个挺难想的,想要找到具体的子序列,正向遍历字符串肯定不行,因为不能判断在哪一步dp有最大值,或者说是通往最大值的解,因此需要从最后开始,倒着向前遍历dp,由于正向遍历时,dp是一个一个遍历上去的,故倒过来也是一样,分别从字符串1和字符串2的最后一位开始,若字符相同,则存入另一数组中,若不同,则判断字符串1上一位与字符串2上一位的dp大小,最后取最大的
代码如下:
#include<bits/stdc++.h>
using namespace std;
int dp[3010][3010];
char s[3010],t[3010],ans[3010];
int main()
{
scanf("%s%s",s+1,t+1);
int n=strlen(s+1),m=strlen(t+1);
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
if(s[i]==t[j])
dp[i][j]=dp[i-1][j-1]+1;
else
dp[i][j]=max(dp[i-1][j],dp[i][j-1]);
int i=n,j=m;
while(dp[i][j]>0)
{
if(s[i]==t[j])
ans[dp[i][j]]=s[i],i--,j--;
else if(dp[i-1][j]>=dp[i][j-1])
i--;
else
j--;
}
for(int i=1;i<=dp[n][m];i++)
cout<<ans[i];
return 0;
}
P2196 [NOIP1996 提高组] 挖地雷
看到这一题,想用爆搜,但还不会,qwq。。所以,用动规想了至少两天还没想出来,看题解也看不明白,谁懂啊,www
于是那天晚上发狠,弄不出来不睡觉了,终于,最后写出来了hhh
初始化:
首先是要初始化,将每个隧道中地雷数赋给每一个dp[i],作为初始值,一开始挖地雷当然从1开始,故pos赋值1
找到地雷数最大值:
思路是这样的:大循环从1开始依次遍历到n,内循环从1开始遍历到大循环的 i ,每次以 i 结尾时找到考虑前面 i 个数的能挖到地雷数目加上第 i 个隧道中的地雷数的最大值,即:
&&
用maxx将每一次dp[i]储存,最后就可得到所挖地雷数的最大值了
挖到最大地雷数步骤:
每当判断出来可以挖到第 i 个隧道中的地雷时,将此时的隧道记入数组中,每个下标对应数组的值,每次判断出来后就用 i 指向 j (可以看做逐渐建立子树),再判断此dp[i]是否可以是最大值(可以看成一种树结点,每次更换 i 可以看做更换一个父节点,然后将其枝丫接上去),并赋给pos,这样下去,逐渐就找到了每一次 i 所对应的最优解,最后也将最优子树连接起来,最后按照数组值指向对应下标,将下表倒序输出即可
代码如下:
#include<bits/stdc++.h>
using namespace std;
int n;
const int N=35;
int mp[N][N];
int dp[N];
int di[N];
int so[N];
int pre[N];
int main()
{
cin>>n;
int pos;
int maxx=0;
memset(mp,0,sizeof(mp));
memset(di,0,sizeof(di));
memset(dp,0,sizeof(dp));
for(int i=1;i<=n;i++)
cin>>di[i];
for(int i=1;i<=n;i++)
for(int j=i+1;j<=n;j++)
cin>>mp[i][j];
for(int i=1;i<=n;i++)
dp[i]=di[i];
memset(so,0,sizeof(so));
maxx=di[1],pos=1;
for(int i=2;i<=n;i++)
for(int j=1;j<i;j++)
{
if(mp[j][i]&&dp[j]+di[i]>dp[i])
{
dp[i]=dp[j]+di[i];
so[i]=j;
}
if(maxx<dp[i])
{
pos=i;
maxx=dp[i];
}
}
int cnt=0;
while(so[pos]!=0)
{
pre[++cnt]=pos;
pos=so[pos];
}
pre[++cnt]=pos;
reverse(pre+1,pre+1+cnt);
for(int i=1;i<=cnt;i++)
{
cout<<pre[i];
if(i!=cnt)
cout<<" ";
}
cout<<endl;
cout<<maxx;
return 0;
}
P1541 [NOIP2010 提高组] 乌龟棋
额
本题觉得含金量不大,思路很好想,因有4个数,直接开四维数组即可,每一次循环找到对应的上一步棋的最优解,从而找到大问题最优解
代码:
#include<bits/stdc++.h>
using namespace std;
const int N=41,M=125;
int n,m;
int grade[355];
int numm;
int num[5];
int step[N][N][N][N];
int a,b,c,d;
int main()
{
memset(step,0,sizeof(step));
cin>>n>>m;
for(int i=1;i<=n;i++)
cin>>grade[i];
for(int i=1;i<=m;i++)
{
cin>>numm;
num[numm]++;
}
int r;
step[0][0][0][0]=grade[1];
for(int i=0;i<=num[1];i++)
for(int j=0;j<=num[2];j++)
for(int k=0;k<=num[3];k++)
for(int l=0;l<=num[4];l++)
{
r=1+i+2*j+3*k+4*l;
if(i!=0)
step[i][j][k][l]=max(step[i-1][j][k][l]+grade[r],step[i][j][k][l]);
if(j!=0)
step[i][j][k][l]=max(step[i][j-1][k][l]+grade[r],step[i][j][k][l]);
if(k!=0)
step[i][j][k][l]=max(step[i][j][k-1][l]+grade[r],step[i][j][k][l]);
if(l!=0)
step[i][j][k][l]=max(step[i][j][k][l-1]+grade[r],step[i][j][k][l]);
}
cout<<step[num[1]][num[2]][num[3]][num[4]];
return 0;
}
P2679 [NOIP2015 提高组] 子串
对于本题,四维数组挺好理解的,三维么,理解不了一点了。。。
四维数组其实很好想,分别对应四种状态
第一个状态,字符串A的每个字符
第二个状态,字符串B的每个字符
第三个状态,需要选出的子串数目
第四个状态,对此时字符串B的字符选或不选
但是这样会爆!!!
于是改用滚动数组,因为对于A的每个字符,存在选与不选两种情况(对应第四维的1和0),故只与上一个字符的最优解有关,则上面的方法需要存储的前 i-2种情况都可以删去,最终第 i 次只和第 i-1 次有关,所以
第一个状态,字符串A第 i 个字符与第 i-1个字符
可是要注意了,当A中有字符和B相等,此时对第 i 个字符有两种情况,即选与不选,选则有情况:纳入旧子串并使用前一个字符的,纳入新子串但不使用前一个字符,纳入新子串并使用前一字符,即:
dp[i%2][j][h][1]=dp[(i-1)%2][j-1][h-1][0]+dp[(i-1)%2][j-1][h][1])+dp[(i-1)%2][j-1][h-1][1];
若不相等了,则种数为前一字符所有情况之和,即:
dp[i%2][j][h][0]=dp[(i-1)%2][j][h][0]+dp[(i-1)%2][j][h][1];
P5322 [BJOI2019] 排兵布阵
这一题乍一看还是不太难的,再一看,是绿题,想着这也太不值了吧。。。
额额
可是这题让我知道,绿题是有它绿的原因的,太巧妙了hhh,这题别看它输入是一行行输士兵数,代表每个城堡对应的士兵数量,其实要把它倒过来看,把对每个城堡派去的士兵数看做一个集合,即对第 i 个城堡派去了5、3、4...进行这样一个巧妙地转换,就能使用背包算法了
最外面循环是对第 i 个城堡进行进攻,第二个循环是遍历攻打每个城堡的士兵数量,最里面一个循环,目的是遍历每个选手这样选择的情况数
当然了,作为背包问题,还是有一定模版的,只不过这题需要一个转的思想罢了,状态转移方程还是很友好的:
代码如下:
#include<bits/stdc++.h>
using namespace std;
int s,n,m;
int stra[105][105];
int dp[20005];
int maxx=0;
int main()
{
cin>>s>>n>>m;
for(int i=1;i<=s;i++)
for(int j=1;j<=n;j++)
cin>>stra[j][i];
for(int i=1;i<=n;i++)
sort(stra[i]+1,stra[i]+1+s);
for(int i=1;i<=n;i++)//第i个城堡
for(int j=m;j>=0;j--)//j个士兵
for(int k=1;k<=s;k++)//第k位选手
{
if(j>2*stra[i][k])
dp[j]=max(dp[j-2*stra[i][k]-1]+i*k,dp[j]);
}
cout<<dp[m];
return 0;
}