洛谷P1025数的划分
题目大意:
给定数n分成k份有几种分法
题目分析:
相当于把n个小球放入k个盒子中,其中每个盒子都不能为空。
这里分成两种情况
1、有盒子里有一个球
2、所有盒子中球的个数都大于等于两个
从而列出转移方程:dp[n][k]=dp[n-1][k-1]+dp[n-k][k]
AC代码
#include <iostream>
#include <algorithm>
#include <iomanip>
#include <cmath>
#include <cstdio>
#include <vector>
#include <cstring>
#include <queue>
#include <map>
#include <stack>
#include <fstream>
typedef long long ll;
#define MAXN 500010
using namespace std;
int dp[500][10];
int main()
{
int n,k;
cin >> n >> k;
memset(dp, 0, sizeof(dp));
for (int i=1;i<=400;i++)
{
dp[i][1]=1;
dp[i][0]=1; //注意这里的初始条件是把任意个球放入一个盒子中情况种数为1
}
for(int i=2;i<=n;i++)
for (int j=2;j<=k;j++)
if (i>j)
{
dp[i][j]=dp[i-1][j-1]+dp[i-j][j];
}
else dp[i][j]=dp[i-1][j-1];
cout << dp[n][k] << endl;
}
洛谷P1057传球游戏
题目分析
当在第j次传球传到第i人手上时的情况数等于第j-1次传球传到左边那个人或右边那个人到情况总数之和,以此列出转移方程
AC代码
#include <iostream>
#include <algorithm>
#include <iomanip>
#include <cmath>
#include <cstdio>
#include <vector>
#include <cstring>
#include <queue>
#include <map>
#include <stack>
#include <fstream>
typedef long long ll;
#define MAXN 500010
using namespace std;
int dp[40][40];
int main()
{
int n,m;
cin >> n >> m;
memset(dp, 0, sizeof(dp));
dp[1][n]=1;
dp[1][2]=1;
int l,r;
for (int i=2;i<=m;i++)
for (int j=1;j<=n;j++)
{
l=j-1;
r=j+1;
if (l==0) l=n;
if (r>n) r=1;
dp[i][j]=dp[i-1][l]+dp[i-1][r];
}
cout << dp[m][1] << endl;
}
洛谷P1064 金明的预算方案
题目分析:
这道题相比于01背包的不同处在于物件之间存在着优先级的关系,即拿某些物件的前提条件是拿了另外一个物件。这里根据附件个数不超过2个的特性(如果附件个数很多可以用树形dp)可以在输入时进行一定的预处理:将附件归于主件的门类下,这样在每次就可以列出4个转移方程:
1、与只取主件比较
2、同时取主件和附件1
3、同时取主件和附件2
4、都取
最终得到答案
AC代码
#include <iostream>
#include <algorithm>
#include <iomanip>
#include <cmath>
#include <cstdio>
#include <vector>
#include <cstring>
#include <queue>
#include <map>
#include <stack>
#include <fstream>
typedef long long ll;
#define MAXN 50000010
using namespace std;
struct attach
{
int v;
int p;
};
struct goods
{
int v;
int p;
attach att1;
attach att2;
};
int main()
{
int n,m;
cin >> n >> m;
goods a[40000];
int v1,p1,q1;
for (int i=0;i<=33000;i++)
{
a[i].p=0;
a[i].v=0;
a[i].att1.v=0;
a[i].att2.v=0;
a[i].att1.p=0;
a[i].att2.v=0;
}
for (int i=1;i<=m;i++)
{
cin >> v1 >> p1 >> q1;
if (q1==0)
{
a[i].v=v1;
a[i].p=p1;
}
else if (a[q1].att1.v==0)
{
a[q1].att1.v=v1;
a[q1].att1.p=p1;
}
else
{
a[q1].att2.v=v1;
a[q1].att2.p=p1;
}
}
int dp[40000];
memset(dp, 0, sizeof(dp));
for(int i = 1;i <= m;i++)
for(int j = n;j >= 0;j--){
if (j>=a[i].v)
{
dp[j]=max(dp[j],dp[j-a[i].v]+a[i].v*a[i].p);
}
if (j>=a[i].att1.v+a[i].v)
{
dp[j]=max(dp[j],dp[j-a[i].att1.v-a[i].v]+a[i].att1.v*a[i].att1.p+a[i].v*a[i].p);
}
if (j>=a[i].att2.v+a[i].v)
{
dp[j]=max(dp[j],dp[j-a[i].att2.v-a[i].v]+a[i].att2.v*a[i].att2.p+a[i].v*a[i].p);
}
if (j>=a[i].att1.v+a[i].att2.v+a[i].v)
{
dp[j]=max(dp[j],dp[j-a[i].att2.v-a[i].att1.v-a[i].v]+a[i].att2.v*a[i].att2.p+a[i].att1.v*a[i].att1.p+a[i].v*a[i].p);
}
}
cout << dp[n] << endl;
}
洛谷P1164 小A点菜
题目分析:
一个入门级别的dp题。i表示前i个菜品,j表示恰好用了j块钱,列出转移方程dp[i][j]+=dp[i-1][j-a[i]]
AC代码
#include <iostream>
#include <algorithm>
#include <iomanip>
#include <cmath>
#include <cstdio>
#include <vector>
#include <cstring>
#include <queue>
#include <map>
#include <stack>
#include <fstream>
typedef long long ll;
#define MAXN 50000010
using namespace std;
int dp[10005];
int a[1010];
int main()
{
int n,m;
cin >> n >> m;
memset(dp, 0, sizeof(dp));
for (int i=1;i<=n;i++)
scanf("%d",&a[i]);
dp[0]=1;
for(int i = 1;i <= n;i++){
for(int j = m;j >= a[i];j--){
dp[j]+=dp[j-a[i]];
}
}
cout << dp[m] << endl;
}
洛谷P1020 导弹拦截
题目分析:
本来觉得是一个简单的求最长不升子序列和最长上升子序列,后来发现时间复杂度要O(nlogn)就十分诡异。于是便采用了二分的思想。
这里机智(懒惰 )的我用了lower_bound和upper_bound
这两个函数具体用法参见我另外一个博客 博客地址在此
lower_bound求的是一个有序序列中第一个大于等于所查询数的地址,upper_bound是大于
要求一个最长不上升子序列,用一个d数组维护一段不升序列,如果后面的数小于等于末尾最小数,那么就加在末尾,否则利用lower_bound函数找出有序数列中第一个小于的数用这个数将其替换,最后得到的数列便为最长不升子序列。(道理可以自己琢磨一下),最长上升子序列反之。
AC代码:
#include <iostream>
#include <algorithm>
#include <iomanip>
#include <cmath>
#include <cstdio>
#include <vector>
#include <cstring>
#include <queue>
#include <map>
#include <stack>
#include <fstream>
typedef long long ll;
#define MAXN 50000010
using namespace std;
int a[100010];
int dp1[100010];
int dp2[100010];
int main()
{
int co=0;
while (scanf("%d",&a[++co])!=EOF);
co--;
int len1=1,len2=1;
dp1[1]=a[1]; dp2[1]=a[1];
for (int i=2;i<=co;i++)
{
if (a[i]<=dp1[len1])
dp1[++len1]=a[i];
else
*(upper_bound(dp1+1, dp1+len1+1, a[i], greater<int>()))=a[i];
if (a[i]>dp2[len2])
dp2[++len2]=a[i];
else
*(lower_bound(dp2+1, dp2+len2+1, a[i], less<int>()))=a[i];
}
cout << len1 << endl << len2 << endl;
}
洛谷P1280 尼克的任务
题目分析:
显然是个dp题,但是如果从前到后dp不行,因为我们每碰到一个任务开始点进行dp时对于任务结束处还未进行过数据处理。从而我们采用从后往前点方法并分成两种情况:
1、该时间点无开始的任务那么这个时间点往后最大休息时间便是上一项加一
2、若有开始的一个或多个任务则从中找到休息时间最大的一种取法
以此便可以写了
AC代码
#include <iostream>
#include <algorithm>
#include <iomanip>
#include <cmath>
#include <cstdio>
#include <vector>
#include <cstring>
#include <queue>
#include <map>
#include <stack>
#include <fstream>
typedef long long ll;
#define MAXN 50000010
using namespace std;
struct task
{
int beg;
int en;
};
bool cmp(task x,task y)
{
return x.beg<y.beg;
}
int dp[10010];
task a[10010];
int num[10010];
int main()
{
int n,k;
memset(dp, 0, sizeof(dp));
memset(num, 0, sizeof(num));
cin >> n >> k;
for (int i=1;i<=k;i++)
{
scanf("%d%d",&a[i].beg,&a[i].en);
num[a[i].beg]++;
}
sort(a+1,a+k+1,cmp);
int pp=k;
for (int i=n;i>=1;i--)
{
if (num[i]==0)
{
dp[i]=dp[i+1]+1;
}
else
{
while (num[i]--)
{
dp[i]=max(dp[i],dp[a[pp].en+i]);
pp--;
}
}
}
cout << dp[1] << endl;
}
洛谷P1006 传纸条
题目分析:
这是个多维的简单dp问题,思考的时候不能拘泥于格点而是要以状态为核心问题考虑状态转移方程。本题比较容易想到的思路是用四维dp数组进行记录,但是在观察到对于任意时刻两个纸条横纵坐标相同的性质可以进行降维。
AC代码
#include <iostream>
#include <algorithm>
#include <iomanip>
#include <cmath>
#include <cstdio>
#include <vector>
#include <cstring>
#include <queue>
#include <map>
#include <stack>
#include <fstream>
using namespace std;
typedef long long ll;
int dp[115][60][60];
int a[61][60];
int main()
{
int m,n;
cin >> m >> n;
for (int i=1;i<=m;i++)
for (int j=1;j<=n;j++)
scanf("%d",&a[i][j]);
memset(dp, -1, sizeof(dp));
dp[2][1][1]=0;
for (int k=3;k<m+n;k++)
for (int i=1;i<n;i++)
for (int j=i+1;j<=n;j++)
{
if (i>=k || j>=k) continue;
if(dp[k-1][i][j]==-1 && dp[k-1][i-1][j]==-1 &&dp[k-1][i][j-1]==-1 &&dp[k-1][i-1][j-1]==-1)
continue;
dp[k][i][j]=max(dp[k][i][j],dp[k-1][i][j]+a[k-i][i]+a[k-j][j]);
dp[k][i][j]=max(dp[k][i][j],dp[k-1][i-1][j]+a[k-i][i]+a[k-j][j]);
dp[k][i][j]=max(dp[k][i][j],dp[k-1][i][j-1]+a[k-i][i]+a[k-j][j]);
dp[k][i][j]=max(dp[k][i][j],dp[k-1][i-1][j-1]+a[k-i][i]+a[k-j][j]);
}
cout << dp[m+n-1][n-1][n] << endl;
}
洛谷P1387 最大正方形
题目分析:
多维dp问题。dp[i][j]表示的是以i,j点为正方形右下角的最大正方形边长,以此列出转移方程
AC代码:
#include <iostream>
#include <algorithm>
#include <iomanip>
#include <cmath>
#include <cstdio>
#include <vector>
#include <cstring>
#include <queue>
#include <map>
#include <stack>
#include <fstream>
using namespace std;
typedef long long ll;
int dp[120][120];
int a[120][120];
int main()
{
int m,n;
cin >> m >> n;
for (int i=1;i<=m;i++)
for (int j=1;j<=n;j++)
scanf("%d",&a[i][j]);
memset(dp, 0, sizeof(dp));
for (int i=1;i<=n;i++)
dp[1][i]=a[1][i];
for (int i=1;i<=m;i++)
dp[i][1]=a[i][1];
int ans=-1;
for (int i=2;i<=m;i++)
for (int j=2;j<=n;j++)
{
if (a[i][j])
dp[i][j]=min(min(dp[i-1][j],dp[i][j-1]),dp[i-1][j-1])+1;
ans=max(ans,dp[i][j]);
}
cout << ans << endl;
}
洛谷P1063 能量项链
题目分析:
一个典型的区间dp题。区间dp的思想核心在于把问题逐层分为更小的区间,第一重循环控制区间长度,从小开始直到扩展到整个区间。第二重循环控制区间到开始位置,考虑越界问题。第三重指到是区间的划分方式:例如长度为三的区间可能由长2+长1或长1+长2。此题另外一个技巧点在于对环的处理,这里直接将环重复一遍进行求解(这也是一个处理环形结构时比较常见的方法)
AC代码
#include <iostream>
#include <algorithm>
#include <iomanip>
#include <cmath>
#include <cstdio>
#include <vector>
#include <cstring>
#include <queue>
#include <map>
#include <stack>
#include <fstream>
using namespace std;
typedef long long ll;
int dp[500][500];
int a[500];
int main()
{
memset(dp, 0, sizeof(dp));
int n;
cin >> n;
for (int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
a[i+n]=a[i];
}
for (int len=2;len<=n+1;len++)
for (int i=1;i+len-1<=2*n;i++)
for (int k=i+1;k<=i+len-2;k++)
dp[i][i+len-1]=max(dp[i][i+len-1],dp[i][k]+dp[k][i+len-1]+a[i]*a[k]*a[i+len-1]);
int ans=0;
for (int i=1;i<=n;i++)
ans=max(ans,dp[i][i+n]);
cout << ans << endl;
}