kuangbin带你飞:点击进入新世界
总结:
简单dp,最近在做,持续更新。
文章目录
1.Max Sum Plus Plus
原题链接:传送门
思路:
- 如果不看时间复杂度和空间复杂度的话,假设用2维dp来解决。
- dp[i][j]就是前j个数分成i组所取得的最大值,那么有:
- dp[i][j]=max(dp[i][j], dp[i][j]+a[j],dp[i-1][k]+a[j] ) k<i
- 前两个分别对应不取这个数和取这个数的情况(连续),第三个对应的是在前面取i-1组,在k处断开,然后a[j]为第i组。
- 但是1e6的数据范围导致时间和空间都不够,所以需要压缩,可以发现跟背包问题其实是相似的,如果用另外一个数组f[j]来表示dp[i-1][k]的情况,一维就放得下了。
- 代码可看注释。
代码如下:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define ll unsigned long long
#define inf 1<<30
using namespace std;
const int manx=1e6+5;
int a[manx],dp[manx],f[manx];
int main()
{
int n,m;
while(scanf("%d%d",&m,&n)!=EOF) //数据较大,用scanf,不然会超时
{
for(int i=1;i<=n;i++)
scanf("%d",&a[i]);
int maxx;
memset(dp,0,sizeof(dp));
memset(f,0,sizeof(f));
for(int i=1;i<=m;i++)
{
maxx=-inf;
for(int j=i;j<=n;j++) //分成i组的话那么必须从i开始
{
dp[j]=max(dp[j-1]+a[j],f[j-1]+a[j]);
//f[j-1]是前面j-1个数取i-1组的最大值
//dp[j-1]是前面j-1个数取i组的最大值
//那么dp[j]就是j个数分i组的最大值
f[j-1]=maxx;
//这里j-1是因为maxx是上个循环得到的,就是前面dp[j-1]和maxx的较大值
maxx=max(dp[j],maxx);
//假设当前是循环到i组的时候,这个f数组是会在i+1组的时候用到
}
}
printf("%d\n",maxx);
}
return 0;
}
2.Ignatius and the Princess IV
原题链接:传送门
思路:
- 跟dp好像没什么关系,用一个map计数即可。
代码如下:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<map>
#include<algorithm>
#define ll unsigned long long
#define inf 1<<30
using namespace std;
const int manx=1e6+5;
int a[manx];
map<int,int>mp;
int main()
{
int n;
while(scanf("%d",&n)!=EOF)
{
mp.clear();
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
mp[a[i]]++;
}
for(int i=1;i<=n;i++)
if(mp[a[i]]>=(n+1)/2)
{
printf("%d\n",a[i]);
break;
}
}
return 0;
}
3.Monkey and Banana
原题链接:传送门
思路:
- 求最大子序列和,不过附加了一些其他条件:
- 后续的长和宽必须小于前面的
- 这题主要是输入的时候把六种情况都存进去就可以了。
代码如下:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<map>
#include<algorithm>
#define ll unsigned long long
#define inf 1<<30
using namespace std;
const int manx=1000;
int dp[manx];
struct node{
int l,r,w;
}a[manx];
bool cmp(node a,node b){
if(a.l==b.l) return a.r<b.r;
return a.l<b.l;
}
int main()
{
int n,t=1;
while(scanf("%d",&n)!=EOF)
{
if(!n) break;
int index=1;
for(int i=1;i<=n;i++)
{
int l,r,w;
scanf("%d%d%d",&l,&r,&w);
a[index].l=l,a[index].r=r,a[index++].w=w;
a[index].l=l,a[index].r=w,a[index++].w=r;
a[index].l=r,a[index].r=l,a[index++].w=w;
a[index].l=r,a[index].r=w,a[index++].w=l;
a[index].l=w,a[index].r=l,a[index++].w=r;
a[index].l=w,a[index].r=r,a[index++].w=l;
}
sort(a+1,a+1+index,cmp);
memset(dp,0,sizeof(dp));
int ans=0;
for(int i=1;i<index-1;i++){
for(int j=i;j<index;j++)
if(a[i].l<a[j].l&&a[i].r<a[j].r)
dp[j]=max(dp[j],dp[i]+a[j].w),ans=max(ans,dp[j]);
}
cout<<"Case "<<t++<<": maximum height = "<<ans<<endl;
}
return 0;
}
4.最少拦截系统
原题链接:传送门
思路:
- LIS模板题,很经典的DP。
- dp[i]表示前i个至少需要的导弹数。
代码如下:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<map>
#include<algorithm>
#define ll unsigned long long
#define inf 1<<30
using namespace std;
const int manx=1e6+5;
int a[manx],dp[manx];
int main()
{
int n,t=1;
while(scanf("%d",&n)!=EOF)
{
for(int i=1;i<=n;i++)
scanf("%d",&a[i]),dp[i]=1;
int ans=0;
for(int i=1;i<=n;i++)
{
for(int j=i+1;j<=n;j++)
if(a[j]>a[i])
dp[j]=max(dp[i]+1,dp[j]);
ans=max(ans,dp[i]);
}
cout<<ans<<endl;
}
return 0;
}
5.Longest Ordered Subsequence
原题链接:传送门
思路:
- 最长上升子序列,跟上面那一题相同。
代码如下:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<map>
#include<algorithm>
#define ll unsigned long long
#define inf 1<<30
using namespace std;
const int manx=1e6+5;
int a[manx],dp[manx];
int main()
{
int n,t=1;
while(scanf("%d",&n)!=EOF)
{
for(int i=1;i<=n;i++)
scanf("%d",&a[i]),dp[i]=1;
int ans=0;
for(int i=1;i<=n;i++)
{
for(int j=i+1;j<=n;j++)
if(a[j]>a[i])
dp[j]=max(dp[i]+1,dp[j]);
ans=max(ans,dp[i]);
}
cout<<ans<<endl;
}
return 0;
}
6.Common Subsequence
原题链接:传送门
思路:
- 最长相同子序列,dp[i][j]是第一个串的前i个和第二个串的前j个最大相同序列长度数。
- 由第一个开始递推,dp[n][m]就是答案。
代码如下:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<map>
#include<algorithm>
#define ll unsigned long long
#define inf 1<<30
using namespace std;
const int manx=1e3+5;
string s1,s2;
int dp[manx][manx];
int main()
{
int n,t=1;
while(scanf("%s%s",&s1,&s2)!=EOF)
{
memset(dp,0,sizeof(dp));
for(int i=1;i<=s1.size();i++)
for(int j=1;j<=s2.size();j++)
if(s1[i-1]==s2[j-1])
dp[i][j]=dp[i-1][j-1]+1;
else
dp[i][j]=max(dp[i-1][j],dp[i][j-1]);
cout<<dp[s1.size()][s2.size()]<<endl;
}
return 0;
}
7.Super Jumping! Jumping! Jumping!
原题链接:传送门
思路:
- 最大递增子序列的序列和。
- dp[i]为前i个数中的最大值。
- 把dp[i]初始化为a[i]
- 那么dp[i]=max(dp[i],dp[k]+a[k]) when k<i&&a[k]<a[i]
代码如下:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<map>
#include<algorithm>
#define ll unsigned long long
#define inf 1<<30
using namespace std;
const int manx=1e3+5;
int dp[manx];
int a[manx];
int main()
{
int n,t=1;
while(scanf("%d",&n)!=EOF)
{
if(!n) break;
memset(dp,0,sizeof(dp));
for(int i=1;i<=n;i++)
scanf("%d",&a[i]),dp[i]=a[i];
int ans=0;
for(int i=1;i<=n;i++){
for(int j=i+1;j<=n;j++)
if(a[j]>a[i])
dp[j]=max(dp[j],dp[i]+a[j]);
ans=max(ans,dp[i]);
}
cout<<ans<<endl;
}
return 0;
}
8.Milking Time
原题链接:传送门
思路:
- dp[i]日常表示前i个数的最大价值。
- 这题主要是把数据处理一下,把结束时间加上r,然后记得把结构体排序。
- 那么就转化成一维的最大子序列和了。
- dp[i]=max(dp[i],dp[k]+a[j].w) when a[k].r<=a[i].l&&l>k
代码如下:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<map>
#include<algorithm>
#define ll unsigned long long
#define inf 1<<30
using namespace std;
const int manx=1e3+5;
int dp[manx];
struct node{
int l,r,w;
}a[manx];
bool cmp(node a,node b){
return a.l<b.l;
}
int main()
{
int n,m,r,t=1;
while(scanf("%d%d%d",&n,&m,&r)!=EOF)
{
for(int i=1;i<=m;i++){
cin>>a[i].l>>a[i].r>>a[i].w;
a[i].r+=r;
}
sort(a+1,a+1+m,cmp);
int ans=0;
for(int i=1;i<=m;i++)
dp[i]=a[i].w;
for(int i=1;i<=m;i++){
for(int j=i+1;j<=m;j++)
if(a[j].l>=a[i].r)
dp[j]=max(dp[j],dp[i]+a[j].w);
ans=max(ans,dp[i]);
}
cout<<ans<<endl;
}
return 0;
}
9.FatMouse and Cheese
原题链接:传送门
思路:
- 这道题的话,跟地图相关,搜索是必须的。
- 放在dp专题里面,无非就是记忆化搜索,注意最后return 。
- dp[x][y]=a[x][y]+mx
- mx为这个点开始往后搜索得到的最大值。
代码如下:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<map>
#include<algorithm>
#define ll unsigned long long
#define inf 1<<30
using namespace std;
const int manx=2e3+5;
int dp[manx][manx],a[manx][manx];
int dx[4]={0,0,-1,1},dy[4]={1,-1,0,0};
int n,m,r,t=1;
int dfs(int x,int y){
if(dp[x][y]) return dp[x][y];
int mx=0;
for(int i=1;i<=m;i++){
for(int j=0;j<4;j++){
int xx=x+dx[j]*i,yy=y+dy[j]*i;
if(a[x][y]<a[xx][yy]&&xx>0&&yy>0&&xx<=n&&yy<=n)
mx=max(dfs(xx,yy),mx);
}
}
return dp[x][y]=a[x][y]+mx;
}
int main()
{
while(scanf("%d%d",&n,&m)!=EOF)
{
if(n==-1&&m==-1) break;
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
cin>>a[i][j];
memset(dp,0,sizeof(dp));
cout<< dfs(1,1)<<endl;
}
return 0;
}
10.Phalanx
原题链接:传送门
思路:
- 最大对称子矩阵,左下角到右上角为对称轴。
- 枚举每一个点,如果相同,把行数x向上扩展,列数y相右扩展。
- dp[i][j]=min(dp[i-1][j+1],i-x-1)+1 when c[i][j+1]==c[i-1][j]
代码如下:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<map>
#include<algorithm>
#define ll unsigned long long
#define inf 1<<30
using namespace std;
const int manx=2e3+5;
int dp[manx][manx];
char c[manx][manx];
int n,m,r,t=1;
int main()
{
while(scanf("%d",&n)!=EOF)
{
if(!n) break;
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
cin>>c[i][j];
memset(dp,0,sizeof(dp));
int ans=1;
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++){
dp[i][j]=1;
int x=i,y=j;
while(c[i][y]==c[x][j]) x--,y++;
if(c[i][j+1]==c[i-1][j])
dp[i][j]=min(dp[i-1][j+1],i-x-1)+1;
ans=max(ans,dp[i][j]);
}
cout<<ans<<endl;
}
return 0;
}
11.FatMouse’s Speed
原题链接:传送门
思路:
- 输入保存一下数据,然后排个序,就是简单的最长xx子序列。
- 因为后面需要输出路径,所以在枚举的时候可以逆向枚举i,再从后面往前面更新,这样输出的时候就可以根据ans的值来进行输出。
代码如下:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<map>
#include<algorithm>
#include<vector>
#define ll unsigned long long
#define inf 1<<30
using namespace std;
const int manx=2e3+5;
int dp[manx],q[manx];
struct node {
int w,v,x;
}a[manx];
bool cmp(node a,node b){
if(a.w==b.w) return a.v>b.v;
return a.w<b.w;
}
int n,m,r,t=0;
int main()
{
while(scanf("%d%d",&n,&m)!=EOF)
{
a[++t].w=n,a[t].v=m,a[t].x=t;
}
sort(a+1,a+t+1,cmp);
int ans=0;
for(int i=t;i>=1;i--){
dp[i]=1;
for(int j=i+1;j<=t;j++)
if(a[j].w>a[i].w&&a[j].v<a[i].v)
dp[i]=max(dp[j]+1,dp[i]);
ans=max(dp[i],ans);
}
cout<<ans<<endl;
for(int i=1;i<=t;i++){
if(dp[i]==ans)
cout<<a[i].x<<endl,ans--;
}
return 0;
}
12.免费馅饼
原题链接:传送门
思路:
- 如果把时间轴看成行数,每个点在这个时间上获得的饼数看成列数,那么a[i][j]表示在时间为i时j点获得的饼,如果把图画出来就可以发现这其实是一道数塔题。
- 因为要处理相邻两边,下标为0的时候不好处理,所以把位置+1;
- 既然是数塔题,那么显而易见:
- dp[i][j]=max( dp[i+1][j-1] , dp[i+1][j] , dp[i+1][j+1] )
代码如下:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<map>
#include<algorithm>
#include<vector>
#define ll unsigned long long
#define inf 1<<30
using namespace std;
const int manx=1e5+5;
int dp[manx][15];
int a[manx][15];
int n,m,r,t=0;
int main()
{
while(scanf("%d",&n)!=EOF)
{
if(!n) break;
memset(dp,0,sizeof(dp));
memset(a,0,sizeof(a));
int e=0;
for(int i=1;i<=n;i++){
int x,t;
scanf("%d%d",&x,&t);
a[t][++x]++;
e=max(e,t);
}
for(int i=e;i>=0;i--)
for(int j=1;j<=11;j++)
dp[i][j]=max(dp[i+1][j-1],
max(dp[i+1][j],dp[i+1][j+1]))+
a[i][j];
printf("%d\n",dp[0][6]);
}
return 0;
}
13.Tickets
原题链接:传送门
思路:
- 只能两个人一起买票,那么递推式很容易推出来:
- dp[i]=min(dp[i-1]+a[i] , dp[j-2]+b[i])
- 此处为单独买,和前一个人的买的较大值
- dp[n]为答案 。
代码如下:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<map>
#include<algorithm>
#include<vector>
#define ll unsigned long long
#define inf 1<<30
using namespace std;
const int manx=2e3+5;
int a[manx],b[manx],dp[manx];
int n,m,r,t=0;
int main()
{
while(scanf("%d",&t)!=EOF)
{
while(t--){
cin>>n;
for(int i=1;i<=n;i++) cin>>a[i];
for(int i=2;i<=n;i++) cin>>b[i];
dp[1]=a[1];
for(int i=2;i<=n;i++)
dp[i]=min(dp[i-1]+a[i],dp[i-2]+b[i]);
int h=8,mi=0,sec=0;
sec+=dp[n];
mi+=sec/60%60;
h+=sec/3600;
sec%=60;
printf("%02d:%02d:%02d ",h,mi,sec);
if(h>=12) printf("pm\n");
else printf("am\n");
}
}
return 0;
}