hdu 2084 数塔
由上往下推,由于结果状态多,不好处理
由底往上推,都归于一个起点,只需要算出由底往上得到的最大价值即可
方程:dp[i][j] = max(dp[i+1][j],dp[i+1][j+1])+a[i][j];
hdu 2018 母牛的故事
以n=6为例,fn=9头牛可以分解为6+3,其中6是上一年(第5年)的牛,3是新生的牛(因为第3年有3头牛,这3头在第6年各生一头牛)。
我们可以得出这样一个公式:fn=fn-1+fn-3
再理解一下,fn-1是前一年的牛,第n年仍然在,fn-3是前三年那一年的牛,但换句话说也就是第n年具有生育能力的牛,也就是第n年能生下的小牛数。
编程序,求解这个公式就行了。
当然,第1-3年的数目,需要直接给出。
很像斐波那契数列,有不一样之处,道理、方法一样。其实,在编程之前,讲究先用这样的方式建模。
下面给出参考程序:
hdu 2041 超级楼梯 Fibonacci
hdu 2050 折线分割平面 找递推公式
1递推递推,先分析下直线分割平面的情况,增加第n条直线的时候,跟之前的直线最多有n-1个交点,此时分出的部分多出了(n-1)+1;
2折线也是同理,f(1)=2,f(2)=7,先画好前面n-1条折线,当增加第n条拆线时,此时与图形新的交点最多有2*2(n-1)个,所以分出的部分多出了2*2(n-1)+1 所以推出f(n)=f(n-1)+4*(n-1)+1,n>=3
Codeforces 429B B. Working out
给n*m的矩阵,每个格子有个数,A从(1,1)出发只能向下或右走,终点为(n,m),B从(n,1)出发只能向上或右走,终点为(1,m)。两个人的速度不一样,走到的格子可以获的该格子的数,两人相遇的格子上的数两个人都不能拿。求A和B能拿到的数的总和的最大值。
n,m<=1000
解题思路:
dp.
先预处理出每个格子到四个角落格子的路径最大数值,然后枚举两个人相遇的交点格子,枚举A、B的进来和出去方式,求最大值即可。
注意边界情况。
代码:
背包问题//背包九讲http://love-oriented.com/pack/Index.html
对比一下,看到的区别是,完全背包问题中,物品有无限多件。往背包里面添加物品时,只要当前背包没装满,可以一直添加。那么状态转移方程为:
f[i+1][j]=max(f[i][j-k*weight[i+1]+k*value[i+1]),其中0<=k<=V/weight[i+1]
使用内存为一维数组,伪代码
for i=1……N
for j=1……M
f[j]=max(f[j],f[j-weight[i]+value[i])
和01背包问题唯一不同的是j是从1到M。01背包问题是在前一个子问题(i-1 种 物品)的基础上来解决当前问题(i 种 物品),向i-1种物品时的背包添加第i种物品;而完全背包问题是在解决当前问题(i种物品),向i种物品时的背包添加第i种物品。代码如下:
最小纸币数
给定一定钱,币面有1,21,25,2,5几种,求组成该钱数的最少张数并输出方案。
void solve(vector&money,int sum){
vector dp(sum+1,INT_MAX);//i元需要最少纸币张数。
vector res(sum+1,0);//存方案
dp[0]=0;
for(int i=1;i<=sum;i++){
for(int k=0;kdp[i-money[k]]+1){
dp[i]=dp[i-money[k]]+1;
res[i]=money[k];
}
}
}
}
int s=sum;
while(res[s]){
cout< money={1,21,25,2,5};
int sum;
cin>>sum;
solve(money,sum);
}
多重背包问题
题目:有N种物品和一个容量为V的背包。第i种物品最多有num[i]件可用,每件费用是c[i],价值是w[i]。求解将哪些物品装
入背包可使这些物品的费用总和不超过背包容量,且价值总和最大。
分析:状态转移为:
题目:http://acm.hdu.edu.cn/showproblem.php?pid=2191
一个强盗要去抢劫银行,对于每个银行来说,都有一个不被抓的概率p,和能抢劫到的钱数money,每个银行最多只可以被抢劫一次。问在不被抓的总概率P下,怎样得到最大价值的钱数。
分析:我第一眼看的时候觉得见到01,然后弄一个dp[100],把概率*100。。。。。。如果你和我一样这么想。。那么恭喜你上当了!需要把money当做下标。。。保存的是概率。。。状态转移方程:dp[j] = max(dp[j], (1.0-gai[i]) * dp[j-money[i]]);
最大连续子数组和
int solve(vector& v){
int n=v.size();
vector dp(n,0);//dp[i]:以i为结尾的子数组元素和
dp[0]=v[0];
int res=dp[0];
for(int i=1;i& v){
int n=v.size();
int thissum=v[0];
int res=thissum;
for(int i=1;i
最大递增子序列和
#include
#include
using namespace std;
#define max(a,b) ((a)>(b)?(a):(b))
int solve(vector&v){
int n=v.size();
vector dp(n,0);
dp[0]=v[0];
int res=-1000000;
for(int i=1;iv[j]){
dp[i]=max(dp[i],dp[j]+v[i]);
}
}
res=max(res,dp[i]);
}
return res;
}
int main(){
int n;
while(cin>>n){
if(n==0) continue;
vector v(n);
for(int i=0;i>v[i];
cout<
最长递增子序列
class Solution {
public:
int lengthOfLIS(vector& nums) {
int n=nums.size();
if(n==0) return 0;
if(n==1) return 1;
int res=0;
vectordp(n,1);
for(int i=1;i=0;j--){
if(nums[i]>nums[j]){
dp[i]=max(dp[i],dp[j]+1);
}
}
}
return *max_element(dp.begin(),dp.end());
}
};
最长公共子序列
用二维数组c[i][j]记录串 x1x2⋯xi 与 y1y2⋯yj 的LCS长度,则可得到状态转移方程
代码实现
public static int lcs(String str1, String str2) {
int len1 = str1.length();
int len2 = str2.length();
int c[][] = new int[len1+1][len2+1];
for (int i = 0; i <= len1; i++) {
for( int j = 0; j <= len2; j++) {
if(i == 0 || j == 0) {
c[i][j] = 0;
} else if (str1.charAt(i-1) == str2.charAt(j-1)) {
c[i][j] = c[i-1][j-1] + 1;
} else {
c[i][j] = max(c[i - 1][j], c[i][j - 1]);
}
}
}
return c[len1][len2];
}
前面提到了子串是一种特殊的子序列,因此同样可以用DP来解决。定义数组的存储含义对于后面推导转移方程显得尤为重要,糟糕的数组定义会导致异常繁杂的转移方程。考虑到子串的连续性,将二维数组 c[i,j] 用来记录具有这样特点的子串——结尾为母串 x1x2⋯xi 与 y1y2⋯yj 的结尾——的长度。
得到转移方程:
最长公共子串的长度为 max(c[i,j]), i∈{1,⋯,m},j∈{1,⋯,n} 。
代码实现
public static int lcs(String str1, String str2) {
int len1 = str1.length();
int len2 = str2.length();
int result = 0; //记录最长公共子串长度
int c[][] = new int[len1+1][len2+1];
for (int i = 0; i <= len1; i++) {
for( int j = 0; j <= len2; j++) {
if(i == 0 || j == 0) {
c[i][j] = 0;
} else if (str1.charAt(i-1) == str2.charAt(j-1)) {
c[i][j] = c[i-1][j-1] + 1;
result = max(c[i][j], result);
} else {
c[i][j] = 0;
}
}
}
return result;
}
给定整数m,n和数组x[n],找出某个i,使得x[i]+x[i+1]+x[i+2]+x[i+3]+x[i+4]…x[i+m]最接近于零。
(0<=i
一.暴力解法
遍历各个i值,计算子序列的和,然后求出最接近0的
int find(int a[],int n,int m) //寻找m+1个数字,使得他们的和最小
{
int i=0;
int thissum=0;
int j=0;
int ans=INT_MAX;
for(i=0;i
二.动态规划:
建立数组dp[],dp[i]存放的是x[i]+x[i+1]+…x[i+m]的值,则递推关系式为:
dp[i+1]=dp[i]-a[i]+a[i+1+m],然后遍历dp[]数组,求出最近于零的。
int find2(int a[],int n,int m)
{
int *dp=(int *)malloc(n*sizeof(int));
int i=0;
int tmp=0;
for(i=0;i<=m;i++)
{
tmp+=a[i];
}
dp[0]=tmp;
for(i=1;i
时间复杂度为O(n),空间复杂度为O(n)
还可以对上面的代码稍作改进,上面的dp[]数组,只要用一个变量即可。
int find3(int a[],int n,int m)
{
int tmp=0;
int ans=INT_MAX;
int i=0;
for(i=0;i<=m;i++)
tmp+=a[i];
if(abs((int)tmp)
时间复杂度为O(n),空间复杂度为O(1)
完整代码为:
# include
# include
# include
# include
using namespace std;
int find(int a[],int n,int m) //寻找m+1个数字,使得他们的和最小
{
int i=0;
int thissum=0;
int j=0;
int ans=INT_MAX;
for(i=0;i
这题有空间和时间的解法
首先把数组中的 全部改成
此时题目等价于询问一段最长的区间,使得区间内的数的和是
这个时候可以对新的 数组求前缀和数组
题目又转化为求两个下标 使得 ,且 最大
这个可以用一个长度为 的数组来记录前缀和的下标来进行快速查询
细节见代码#include
#include
using namespace std;
int main() {
vector arr = { 1, 1, 1, 0, 1,0,1,1 };
int counts = arr.size();
vector sum;
sum.reserve(counts + 1);
sum.push_back(0);
for (const auto& x : arr) {
sum.push_back(sum.back() + x * 2 - 1);
}
vector temp(counts * 2 + 1, -1);//哈希表
int maxlen = -1;
int startindex = -1, endindex = -1;
for (int i = 0; i < sum.size(); i++) {
int index = sum[i] + counts;//加上偏置,因为sum可能小于零
if (temp[index] == -1) {
temp[index] = i;
}
else {
int curlen = i - temp[index];
if (curlen > maxlen) {
startindex = temp[index];
endindex = i - 1;
maxlen = curlen;
}
}
}
if (maxlen != -1) {
cout << "MAXLEN: " << maxlen << " START: " << startindex << " END: " << endindex << endl;
}
else {
cout << "NOT EXIST" << endl;
}
}
输入:int 型数组由正数、负数、0组成
输出:最长和为0的子序列
例:
输入:[3,0,-1,-2,-3,1,1,1,2,3,1,-2,-1]
输出:9
思路:原数组为A,长度为N
新建一个数组B[1...N+1],B[i]=A[i-1]+A[i-2]+A[1],B[1]=0 时间复杂度为O(n)
将问题转化为求B数组两个相同数字最远距离 时间复杂度可能为O(n^2)
private static int find(int[] arr) {
int[] arr1 = new int[arr.length + 1];
//创建求和串,为了考虑整个串和为0,将首项设置为0;
arr1[0] = 0;
for (int x = 0; x < arr.length; x++) {
arr1[x+1] = arr[x]+arr1[x];
}
int arr2[] = new int [arr.length];
//寻找两个相同数字最大距离,从后向前,找到就ok
for(int i = 0;iarr.length-i){
break;
}
for(int j =arr.length;j>=0;j--){
if(arr1[i]==arr1[j]){
arr2[i]=j-i;
break;
}
}
}
//找最大值
int max = 0;
for(int x =0;xmax){
max=arr2[x];
}
}
return max;
}
POJ - 3061 Subsequence(连续子序列和>=s的最短子序列长度)
题意:T组实例,每组实例给出n和s,接着n个数。求连续子序列和大于等于s的最短子序列长度。
分析:有点点模拟的意思,形象点说,就像你堆积木,超过一定值S后我们就把最下面的几块积木去掉。具体方法是从前面开始不断累加,当和值超过S时减少前面的数值,然后记录下刚好>=S的ans值,如此重复直到序列尾,输出最小的ans。
AC代码1:
1 #include2 #include<string.h> 3 #include 4 #include 5 #include 6 #include 7 #include 8 using namespace std; 9 #define N 1200000 10 #define INF 0x3f3f3f3f 11 12 int dp[N]; 13 int a[N]; 14 15 int main() 16 { 17 int n,m,T,i,j; 18 19 scanf("%d", &T); 20 21 while(T--) 22 { 23 scanf("%d %d", &n, &m); 24 memset(dp,0,sizeof(dp)); 25 26 for(i=1;i<=n;i++) 27 { 28 scanf("%d", &a[i]); 29 dp[i]=dp[i-1]+a[i]; 30 } 31 32 int minn=INF,i=1;; 33 for(j=1;j<=n;j++) 34 { 35 if(dp[j]-dp[i-1]<m) 36 continue ; 37 while(dp[j]-dp[i]>=m) 38 i++; 39 minn=min(minn,j-i+1); 40 } 41 42 if(minn==INF) 43 printf("0\n"); 44 else 45 printf("%d\n", minn); 46 } 47 return 0; 48 }
class Solution {
public:
string longestPalindrome(string str) {
if(str.empty()) return "";
int len=str.length();
int min_start=0;
int max_len=1;
for(int i=0;i0&&right
最长无重复字串(不是子序列,而是连续的)
void LNRS_dp_hash_impro(char * arr, int size)
{
memset(visit, -1, sizeof visit);
maxlen = maxindex = 0;
visit[arr[0]] = 0;
int curlen = 1;
int last_start = 0;
for(int i = 1; i < size; ++i)
{
if(visit[arr[i]] == -1)
{
++curlen;
visit[arr[i]] = i; /* 记录字符下标 */
}else
{
if(last_start <= visit[arr[i]])
{
curlen = i - visit[arr[i]];
last_start = visit[arr[i]] + 1;
visit[arr[i]] = i; /* 更新最近重复位置 */
}else
{
++curlen;
visit[arr[i]] = i; /* 更新最近重复位置 */
}
}
if(curlen > maxlen)
{
maxlen = curlen;
maxindex = i + 1 - maxlen;
}
}
output(arr);
}
最大乘积子数组
class Solution {
public:
int maxProduct(vector& nums) {
int n=nums.size();
int posmax=nums[0];
int negmax=nums[0];
int res=nums[0];
for(int i=1;i
#include
#include
#include
using namespace std;
//最长公共子序列
int lcs1(string str1, string str2) {
int len1 = str1.length();
int len2 = str2.length();
vector>c(len1+1,vector(len2+1));
for (int i = 0; i <= len1; i++) {
for( int j = 0; j <= len2; j++) {
if(i == 0 || j == 0) {
c[i][j] = 0;
} else if (str1[i-1] == str2[j-1]) {
c[i][j] = c[i-1][j-1] + 1;
} else {
c[i][j] = max(c[i - 1][j], c[i][j - 1]);
}
}
}
return c[len1][len2];
}
//最长公共子串长度
int lcs2(string str1, string str2) {
int len1 = str1.length();
int len2 = str2.length();
int result = 0; //记录最长公共子串长度
vector>c(len1+1,vector(len2+1));
for (int i = 0; i <= len1; i++) {
for( int j = 0; j <= len2; j++) {
if(i == 0 || j == 0) {
c[i][j] = 0;
} else if (str1[i-1] == str2[j-1]) {
c[i][j] = c[i-1][j-1] + 1;
result = max(c[i][j], result);
} else {
c[i][j] = 0;
}
}
}
return result;
}
int main(){
string str1="cnblogs";
string str2="belong";
cout<
区间dp
题目链接:
http://poj.org/problem?id=1141
题目大意:
给一个由[,],{,}组成的字符串序列,求增加最少的字符,使该序列能够匹配,并输出最后的方案。
解题思路:
区间dp.dp[i][j]表示从i~j 所需的最少的字符使之能匹配,转移的话要么是头尾匹配直接加中间,要么分成两段。
不过要输出到达路径,所以在用一个path[i][j]表示到达该路径时的选择,-1表示头尾,其他表示中间分开的位置。
递归输出路径。递归是个好东西,能够很大程度的改变顺序,特别是逆着的。
PS:这题不能用贪心直接模拟过,不一定非要每步都与前面的都匹配。
测试样例:(([[)]] 长度应为10 用模拟为12
代码:
题目地址
题意:
两只兔子,在n块围成一个环形的石头上跳跃,每块石头有一个权值ai,一只从左往右跳,一只从右往左跳,每跳一次,两只兔子所在的石头的权值都要相等,在一圈内(各自不能超过各自的起点,也不能再次回到起点)它们最多能经过多少个石头(1 <= n <= 1000, 1 <= ai <= 1000)。
分析:
其实就是求一个环中,非连续最长回文子序列的长度。
dp[i][j] = max{ dp[i + 1][j], d[i][j - 1], (if a[i] == a[j]) dp[i + 1][j - 1] + 2 }
1 #include2 #include 3 #include 4 #include 5 #include 6 using namespace std; 7 int dp[1010][1010]; 8 int a[1010]; 9 int main() 10 { 11 int i,j,n; 12 while(scanf("%d",&n)!=EOF) 13 { 14 if(!n) break; 15 memset(dp,0,sizeof(dp)); 16 for(i = 1; i <= n ; i++) 17 { 18 scanf("%d",&a[i]); 19 dp[i][i] = 1; 20 } 21 for(i = n ; i >= 1 ;i--) 22 { 23 for(j = i+1; j <= n ; j++) 24 { 25 if(a[i]==a[j]) 26 dp[i][j] = dp[i+1][j-1]+2; 27 dp[i][j] = max(dp[i][j],max(dp[i+1][j],dp[i][j-1])); 28 } 29 } 30 int ans=1; 31 for(i = 1; i < n ; i++) 32 ans = max(ans,dp[1][i]+dp[i+1][n]); 33 printf("%d\n",ans); 34 } 35 return 0; 36 }