关于基本的动态规划和经典的动态规划,在之前已经总结过了,可以温习一下:
传送门

这次是延续上次的《挑战程序设计竞赛》初级篇,总结部分poj上的练习题,主要是DP方面的练习题:

一、基础的动态规划算法

1.Cow Bowing

题目大意:当牛去打保龄球的时候,它们不用真正的保龄球。他们每人取一个数字(范围为0…99),然后排成一个标准的像保龄球针一样的三角形,就像这样:

        7*3   8*8   1   0*2   7   4   4*
4   5   2   6   5

然后其他的奶牛从三角形的顶端开始穿过三角形,
“向下”移动到两头相邻的奶牛中的一头,直到到达“底部”那一排。
奶牛的得分是沿途被访问的奶牛数量的总和。得分最高的那头牛就赢了。
给定一个有N (1 <= N <= 350)行的三角形,确定可实现的最大可能和。

思路:入门级的DP问题,就是很简单的DP+穷竭搜索
令dp [i] [N] : 从底下往上加,第i行第N个值 的 最大值
有 dp [i][N] = max(dp [i+1][N] + num[i][N] , dp [i+1][N+1] + num[i][N])
最后加到第一行的值,即为可能实现的最大可能和,
因为dp每次只跟一个状态有关,可以用一维数组。

实现代码

#include<cstdio>
#define MAX_N 355
int cows[MAX_N][MAX_N]; //记录保龄球三角形
int N;
int dp[MAX_N]; //一维
int max(int x,int y){return x > y ? x : y;
}void solve(){for(int i = 0; i < N - 1;i++){ //计算N-1次,第一次已经计算过了for(int j = 0; j < N - 1 - i;++j){ dp[j] = max(dp[j] + cows[N-2-i][j],dp[j+1] + cows[N-2-i][j]);//从倒二行开始递推} }
}int main(){scanf("%d",&N);for(int i = 0,j = 1;i < N;i++,j++){for(int k = 0; k < j;++k){scanf("%d",&cows[i][k]);if(i == N - 1)  //初始化开始状态的dpdp[k] = cows[i][k];}}solve();printf("%d\n",dp[0]);
}

2.Sumset

题目大意:农夫约翰命令他的牛去寻找不同的数字集合,这些集合的总和是一个给定的数字。奶牛只能使用2的整数次方的数字组合。
下面是一组可能的和为7的数字:
1)1 + 1 + 1 + 1 + 1 + 1 + 1
2)1 + 1 + 1 + 1 + 1 + 2
3)1 + 1 + 1 + 2 + 2
4)1 + 1 + 1 + 4
5)1 + 2 + 2 + 2
6)1 + 2 + 4

帮助FJ计算给定整数N (1 <= N <= 1,000,000)的所有可能表示。
由于这个数字可能非常大,所以只打印后9位数字(以10为基数表示)…

思路:这题需要找到一些规律···
解法:
dp[i]: 数字 i 的所有可能表示的组合数
若i是奇数:
i 的任意一个组合都包含1,我们把所有组合的一个1去掉,所以dp[i] = dp[i-1]
若i是偶数:
i -1 是奇数,其任意一个组合加1构成i的组合,除了这些组合之外,i 的其他组合都不包含1(因为包含1就意味着剩余的加起来为i-1,重复了);
意味着剩余组合里的数全部是偶数,则 i/2 的每个组合乘以2可以构成 i 的一个组合(其每个数*2 都是偶数)所以dp[i] = dp[i-1] + dp[i>>1]

也可以用完全背包解: 等价于背包重量为N,有1,2,4,…, 2 ^21 > 1,000,000 件商品…
求达到 M 重量有几种情况······
dp[0]=dp[1]=1
dp[j] = dp[j-v]+ dp[j] (j>=v,v为2的n次方)
最终结果为dp[M]

实现代码

#include<cstdio>
#include<algorithm>
#define MAX_N 1000005
using namespace std;
int dp[MAX_N];
int N;void solve(){for(int i = 1;i <= N;++i){ //一个个求,从1开始 if(i & 1 == 1) { //奇数 dp[i] = dp[i-1] % 1000000000;}else {dp[i] = (dp[i-1] % 1000000000 + dp[i >> 1] % 1000000000 )% 1000000000;}}
}int main(){scanf("%d",&N);fill(dp,dp+N+1,0);dp[0] = 1;solve(); printf("%d\n",dp[N] ); //由于这个数字可能非常大,所以只打印后9位数字(以10为基数表示)。一开始没看到WA ,//改成long long 对最后结果取模WA,看来long long不溢出取模 != int溢出取模 是自己理所当然了/* int x = 20000020001;long long y =  20000020001;printf("%d\n",x % 1000000000); printf("%lld\n",y % 1000000000); */ //-474816479  and 20001 确实不同
}

3.Apple Catching

题目大意
约翰的田里有两棵苹果树(分别编号为1和2),每棵树上都结满了苹果。
贝茜摘不到树上的苹果,所以她必须等着苹果掉下来。然而,她必须在空中接住它们,
因为苹果碰到地面时会被碰伤(没有人愿意吃碰伤的苹果)。贝茜吃东西很快,所以她抓到的一个苹果几秒钟就吃完了。

每分钟,两棵苹果树中就会有一棵掉下一个苹果。如果她站在苹果掉下来的树下,她就能抓住苹果。
她任何时候都只能站在一棵树下,而她不愿意无休止地在树之间来回走动(因此会错过了一些苹果)。

苹果每分钟掉一个,持续时间为T(1 <= T <= 1000)分钟。贝西最多愿意来回走W (1 <= W <= 30)次。
给定每分钟哪棵树会掉下一个苹果,确定贝西能抓住的最大苹果数。贝西从1号树开始。

思路:这道题看起来还挺有趣的,但是实际上还是记忆化搜索的DP~
DP思路:
dp[ i ][ j ] 代表 第i秒 移动了 j 次 的获得苹果量
dp[1][0] = (T[i] == 1 ? 1 : 0); //贝西从1号树开始
dp[1][1] = (T[i] == 2 ? 1 : 0);
有 j == 0,一直在树1不移动: dp [ i ][ j ] = dp[ i-1 ][ j ] + (T[i] == 1 ? 1 : 0)
当 1=< j <= W,很明显有 j & 1 == 0 在树1,== 1 在树二下
则有 dp[ i ] [ j ]= max{ dp[ i-1 ][ j-1 ], dp[ i-1 ][ j ] } + (T[i] == ( (j & 1) + 1) ? 1 : 0);
这里每次移动肯定是在苹果掉落的时间内考虑的。

实现代码

#include<cstdio>
#include<algorithm>
#define MAX_T 1005
#define MAX_W 35
using namespace std;
int T[MAX_T];
int dp[MAX_T][MAX_W];
int Ti,W;int max(int x,int y){return x > y ? x : y;
}void solve(){  //计算出dp[T][W] fill(dp[0],dp[0] + MAX_T * MAX_W,0);dp[1][0]=(T[1] == 1 ? 1 : 0);dp[1][1]=(T[1] == 2 ? 1 : 0); //默认已知 for(int i = 2;i <= Ti;++i){dp[i][0] = dp[i-1][0] + (T[i] == 1 ? 1 : 0); // w == 0for(int w = 1;w <= W && w <= i;++w){  //w <= i 这个条件,即跳转数不大于秒数,避免无意义的左右横跳,减少计算量 去掉其实也无妨 dp[i][w] = max(dp[i-1][w-1],dp[i-1][w]) + (T[i] == ((w & 1) + 1) ? 1 : 0); //注意优先级···· + & 先算 + 再算 & ``}}
}int main(){scanf("%d%d",&Ti,&W);for(int i = 1;i <= Ti;++i){ scanf("%d",&T[i]);}solve();printf("%d\n",dp[Ti][W]);
}

4.Miking Time

题目大意:贝西真是一头勤劳的母牛。事实上,她如此专注于最大化她的生产力,
她决定安排她的下一个N(1≤N≤1,000,000)小时(方便地标记为0…N-1),以便她生产尽可能多的牛奶。
农夫约翰的挤奶时间列表有M(1≤M≤1000),可能有重叠的间隔。
每个间隔i有一个起始小时(0≤starting_houri≤N),一个结束小时(starting_houri < ending_houri≤N),
和一个对应的效率(1≤efficiency encyi≤1,000,000),它表示在该间隔内他能从Bessie中得到多少加仑的牛奶。
农夫约翰分别在开始时和结束时开始和停止挤奶。要给贝茜挤奶,必须在整个挤奶期间给她挤奶。
每隔一段时间挤奶后,奶牛必须休息R(1≤R≤N)小时,才能开始再次挤奶。
根据农夫约翰斯的时间间隔列表,确定贝西在N个小时内的最大产奶量。

思路
我们把奶牛的休息时间R加在每个挤奶时间列表右边,本质上这就是一个区间DP。
初看和用贪心方法求解的不带权重的最多区间问题有点类似,然后仔细一看又发现不一样。
这题是带权重最大区间问题。 其常见做法是DP, 不妨设:

这里先对所有区间按结束时间从小到大排序!
dp[i] ==> 使用前i+1个时间列表的最大加仑数
dp[i] = max(dp[i - 1], dp[tmp] + fj[i].e);

tmp是 在第i 个区间 前最接近的区间de序号,选择最接近的区间序号是因为:
即使在tmp之前存在符合的区间k ,dp[tmp] (tmp > k) 已经包含了dp[k], dp[tmp] >= dp[k]
不选区间i 为dp[i-1] ; 选择区间i 为dp[tmp] + fj[i].e(选择该区间的开销)

实现代码

#include<cstdio>
#include<algorithm>
using namespace std;
typedef long long ll; //int可能溢出
#define MAX_M 1005
#define MAX_N 1000005
int N,M,R;
ll dp[MAX_M];
struct FJ{int s,f,e;//start finsh efficiency
};bool cmp(const FJ& x,const FJ& y){return x.f < y.f;  //按结束时间从小到大
}FJ fj[MAX_M];ll max(ll x,ll y){return x > y ? x : y;
}void solve(){dp[0] = 0;  for (int i = 1; i <= M; i++) {//tmp是与区间i前最接近的区间序号 int tmp; for(tmp = i - 1;tmp > 0;) {if(fj[tmp].f + R > fj[i].s)tmp--;else break; }dp[i] = max(dp[i - 1], dp[tmp] + fj[i].e);//printf("%d,%d,%d\n",i,dp[i],tmp) ;}printf("%d\n",dp[M]);
}int main(){scanf("%d%d%d",&N,&M,&R);for(int i = 1;i <= M;++i){scanf("%d%d%d",&fj[i].s,&fj[i].f,&fj[i].e);}sort(fj+1,fj+M+1,cmp); //先排序,结束时间从小到大solve(); return 0;
}

5.Cheapest Palindrome

题目大意
给定一个长度为M(1≤M≤2000)的单字串,每个字符是 从N(1≤N≤26)(小写罗马字母)中抽取的字符。
我们希望通过对原字符串进行修改,获得一个“回文字符串”
例如,可以通过在末尾添加“a”来更改“abcb”,从而形成“abcba”,这样ID就是回文的(向前和向后读取相同的内容)。其他一些将ID更改为回文的方法包括在开头添加三个字母“bcb”以生成ID“bcbabcb”,
或者删除字母“a”以生成ID“bcb”。
可以在字符串中的任何位置添加或删除字符,从而产生比原始字符串长或短的字符串。
而每个字符的插入或删除都有一个代价(0≤cost≤10,000),这个代价取决于要添加或删除的字符值。
已知一个字符串和插入或删除每个字母表字符的成本,将其修改为回文的最小成本。
空字符串被认为是回文字符串。

思路 : 有点像LCS问题
定义dp [i][j] 为区间i到j变成回文的最小代价
那么对于dp [i] [j]有三种情况
首先:对于一个串如果 s[i] == s[j],那么dp [i][j] = dp[i+1][j-1]
其次:如果 dp[i+1][j] 是回文串,那么dp[i][j] = dp [i+1][j] + min(add[i],del[i]);
最后,如果 dp[i][j-1] 是回文串,那么dp[i][j] = dp [i][j-1] + min(add[j],del[j]);
注意:为确保 dp [i][j]正确计算,应保证 区间i到j内的dp 已经计算完毕
即对i ,其要先知道i+1,逆序求,对j,其要先知道j-1,正序求~
删除跟添加本质是一样的 ,例如 abcb 你要删a 跟在后面加a是一样的

实现代码

#include<cstdio>
#include<algorithm>
#include<String>
using namespace std;
#define MAX_M 2050
#define INF 10000000
int N,M;
char s[MAX_M];
int letter[26];
int dp[MAX_M][MAX_M];int min(int x,int y){return x < y ? x : y;
}void solve(){//dp [i][j] 为区间i到j变成回文的最小代价if(M == 1 || M == 0){printf("0\n");return;} for(int i = M-2;i >= 0;--i){ //倒二位置开始看 逆序for(int j = i + 1;j < M;++j) // 从左逐渐往右,从内往外 正序{if(s[i] == s[j])dp[i][j] = dp[i+1][j-1];else dp[i][j] = min(  dp[i+1][j] + letter[s[i]-'a'], dp[i][j-1] + letter[s[j]-'a']  );}}printf("%d\n",dp[0][M-1]);
}int main(){scanf("%d%d",&N,&M);scanf("%s",s);getchar(); //吃掉回车 for(int i = 0;i < N;++i){char c;int a,b;scanf("%c %d %d",&c,&a,&b);//printf("%c %d %d\n",c,a,b);//删除跟添加一样 。例如 abcb 你要删a 跟在后面加a是一样的 letter[ c-'a'] = min(a,b);getchar(); //吃掉回车 }solve();return 0;
}

二、优化递推关系式

1.Coins

题目大意
给定N个硬币,包括数量、价值,(1<=N<=100) , (M<=100000)
求使用这些硬币能够支付不超过M元的支付方式有多少种

思路
多重背包的可行性问题
对多重背包我们有两种解法:
一种是转化为2进制的0 / 1背包,
另一种是 新增一个num记录每种背包被使用的个数
num [ i ] [ j ] 表示在往背包里试着装第i件物品时,背包容量使用了j时,装了多少件i物品,有:
num [ i ] [ j ] = num [ i ] [ j - w [ i ] ] +1;
这里用第二种方法~

实现代码

#include <cstdio>
#include <cstring>
using namespace std;int n,m;
int w[105],c[105],sum[100005],dp[100005];
//必须用一维数组,不然空间爆炸int main()
{while(~scanf("%d%d",&n,&m)){if(n==0 && m==0)break;for(int i=0;i<n;i++){scanf("%d",&w[i]);}for(int i=0;i<n;i++){scanf("%d",&c[i]);}memset(dp,0,sizeof(dp));dp[0]=1;int ans=0;for(int i=0;i<n;i++)  //n种物品 {// sum[j]表示填充j大小的包需要至少使用多少个当前物品memset(sum,0,sizeof(sum)); for(int j=w[i];j<=m;j++)  // 完全背包的一维数组,正向状态转化{if(!dp[j] && dp[j-w[i]] && sum[j-w[i]] < c[i]) //dp[j]表示填充为j是否可行,如果dp[j] = 0表示不可行,那么看 dp[j-w[i]](之前是不是有组合可行) 可行的话同时看当前物品是否够放 {dp[j]=1;sum[j] = sum[j-w[i]] + 1;//更新 ans++; //组合数+1}}}printf("%d\n",ans);}return 0;
}

2.Ant Counting

题目大意:有T种蚂蚁,每种蚂蚁数量为Ni,所有蚂蚁共A个,求 取出 S 个 到 B个,总共取法有多少种?
T <= 1000 每种蚂蚁最多100个 1 <= S <= B <= A
取法数··只打印此数字的最后6位数字

思路
典型的多重组合数DP问题
即 n种物品,各有一定数量,求取m个的取法问题
DP: dp [ i ] [ j ] 从前 i 种物品中取出 j个的组合数,其有化简的递推式如下:
dp[i + 1][ j ] = dp[i + 1][ j - 1] + dp [ i ] [ j ] - dp[ i ][ j - 1 - a[ i ] ]

实现代码

#include<cstdio>
#include<algorithm>
#define MAX_T 1005
#define MAX_A 100005
#define MOD 1000000
using namespace std;
int T,A,S,B;
int ans[MAX_T];
//int dp[MAX_T][MAX_A] ; //这样写铁定超内存,可以用滚动数组,因为 dp[i+1][j] 只跟自己和上一行有关
int dp[2][MAX_A];  //滚动数组 void solve(){//一个都不取只有一种取法//for(int i = 0;i <= T; ++i){//  dp[i][0] = 1;//}dp[0][0] = 1;dp[1][0] = 1;int res = 0;for(int i = 0;i < T; ++i){for(int j = 1;j <= B;++j){if(j - 1 - ans[i] >= 0){//dp[i+1][j] = dp[i+1][j-1] + dp[i][j] - dp[i][j - 1 - ans[i]];dp[(i+1) & 1][j] = (dp[(i+1) & 1][j-1] + dp[i & 1][j] - dp[i & 1][j - 1 - ans[i]] + MOD) % MOD; //取模有减法 +MOD在取MOD 避免减法出现负数,取模也为负数的情况 }else {//dp[i+1][j] = dp[i+1][j-1] + dp[i][j];dp[(i+1) & 1][j] = (dp[(i+1) & 1][j-1] + dp[i & 1][j]) % MOD;}               }}for (int i = S; i <= B; i++)  //把 S~B 的所有取法都加上res = (res + dp[T & 1][i]) % MOD;  //这里用 res += 的话,后头还需要补一句 res %= mod一下防溢出 ,因为这个贡献了几次WA//printf("%d %d\n",dp[T][B],dp[T][S]);printf("%d\n",res);
}int main(){scanf("%d%d%d%d",&T,&A,&S,&B);for(int i = 0;i < A;++i){int num;scanf("%d",&num);ans[num-1]++;}solve();
}

3.Dollar Dayz

题目大意
给定N元,K件商品,商品价值从1 ~ K(1,2,3,…K) ;
n <= 1000 , k <= 100
请问 给定 N,K能够得到多少种购买方式 ?

思路
这题就没什么意思了,递推公式比较简单:
dp [ i ] [ j ] :用前 i 种的钱得到 j 元的方法数
dp [ i ][ j ] = dp[ i-1 ][ j ] + dp [ i ][ j - i ]

不过 当N和K特别大的时候。超了long long 需要大数相加 ,感觉考察重点从DP变成了大数相加····
而且C++的大数相加各种实现的复杂度还不一样,不像java那么友好····

实现代码

#include <cstdio>
#include <algorithm>
#include <string>
#define MAX_W 100
#define MAX_N 1005
using namespace std;
struct BigInteger {char digit[MAX_W];int length;BigInteger();BigInteger operator=(int x);BigInteger operator=(string s);BigInteger operator=(const BigInteger& b);BigInteger operator+(const BigInteger& b);
}; BigInteger::BigInteger(){fill(digit,digit+MAX_W,'0');length = 0;
}BigInteger BigInteger::operator=(int x){fill(digit,digit+MAX_W,'0');length = 0;if(x == 0){digit[length++] = '0';}while(x != 0){digit[length++] = (char)('0' + x % 10);x /= 10;}return *this;
}BigInteger BigInteger::operator=(string s){fill(digit,digit+MAX_W,'0');length = s.size();for(int i = 0;i < length; ++i){digit[i] = s[length - i - 1];}return *this;
}BigInteger BigInteger::operator=(const BigInteger& b){fill(digit,digit+MAX_W,'0');length = b.length;for(int i = 0;i < length;++i){digit[i] = b.digit[i];}return *this;
}BigInteger BigInteger::operator+(const BigInteger& b){BigInteger ans;int carry = 0;for(int i = 0;i < length || i < b.length;++i){//当前位计算,长的位由于短的没有,短的默认没有的都是0 int current = carry + (digit[i] - '0') + (b.digit[i] - '0'); carry = current / 10;ans.digit[ans.length++] = (char)('0' + current % 10);}if(carry != 0){ //最后一位有进位 ans.digit[ans.length++] = (char)('0' + carry); }return ans;
}void printB(const BigInteger& b){if(b.length == 0){printf("0\n");return;}for(int i = b.length - 1;i >=0 ;--i)printf("%c",b.digit[i]);printf("\n");
}//BigInteger dp[]
BigInteger dp[MAX_N];
int N,K;void solve(){dp[0] = 1;for(int i = 1;i <= K;++i){for(int j = i; j <= N;++j){ //从i开始因为j < i的没有改变
//          printB(dp[j] + dp[j - i]);dp[j] = dp[j] + dp[j - i]; //dp[i][j] = dp[i-1][j] + dp[i][j - i]}}printB(dp[N]);
}int main(){scanf("%d%d",&N,&K);solve();
//  BigInteger a;
//  a = "11111111111111111111";
    printB(a);
//  BigInteger b;
//  b = "11111111111111111111";
//  a = a + b;
//  printB(a);return 0;
}

大数相加采用的 用char数组存大数的每一位···写的比较通用,但是就这道题来说复杂了
因为大佬发现把数据取满了 N = 1000,K = 100的购买方式也就这么大:

15658181104580771094597751280645 (两个long long就可以存下)

所以这里拿了一份大佬写的简单版大数相加(相当简便··):

/*
用两个数组,第一个数组保存的是方法数的前1e18个数,
第二个数组保存的是方法数的后1e18个数字,那么就可以保存1e36位的方法数。
为啥取1e18呢?因为long long大概就是在1e19这个数量级,所以取到1e18可以防止溢出
*/#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long LL;
const int MAXN = 1010;
const LL INF = 1e18;
LL dp1[MAXN], dp2[MAXN];
int main()
{int n, k;scanf("%d %d", &n, &k);dp2[0] = 1;for(int i = 1;i <= k;++i){for(int j = i;j <= n;++j){dp1[j] = dp1[j] + dp1[j - i] + (dp2[j] + dp2[j - i]) / INF; //高18位 dp2[j] = (dp2[j] + dp2[j - i]) % INF;  //高17位 }}if(dp1[n])printf("%I64d", dp1[n]);printf("%I64d\n",dp2[n]);return 0;
}

三、需稍加思考的题目

1.Wooden Sticks

题目大意
有一堆n根木棍,每根棍子的长度和重量都是事先知道的。
这些棒子将被一台木工机器一个接一个地加工。它需要一些时间,称为设置时间。
安装时间与机器的清洗操作和改变工具和形状有关。木工机床的安装次数如下:
(a) 第一根木棍的设置时间为1分钟。
(b) 加工长度为l、重量为w的棒材后,如果l <= l’且w <= w’,则无需设置时间来处理长度为l’、重量为w’的棒材。否则,设置需要1分钟。
你要找到处理给定的n根木棒的最小设置时间。

举个例子,如果你有五个棒的成对的长度和重量 ( 9 , 4 ) , ( 2 , 5 ) , ( 1 , 2 ) , ( 5 , 3 ) , and ( 4 , 1 ) ,
那么最低设置时间应该是2分钟因为 ( 4 , 1 ) , ( 5 , 3 ) , ( 9 , 4 ) , ( 1 , 2 ) , ( 2 , 5 )。

思路
这道题本质上是找出这堆木头所有摆放顺序里,最少的非递减子序列个数
这道题用贪心写个人感觉比DP简单,先简单说说贪心的思路吧:
什么样的顺序非递减子序列个数最多呢?
不难想到:先按l递增排序再按w递增排序后stick应该是非递减子序列最少的
然后贪心的思路是:

  1. 每次从一个尚未被使用的木头出发,依次找到没有被使用且w 非递减(因为l肯定满足非递减了)的子序列 直到遇到 减小的w,划分加一;
  2. 因为每次贪心划分都能得到一个极大的 非下降子序列,虽然极大不一定是最大,但是每次都能划分出不重复的极大,每次划分之间没有影响,最终划分肯定是最少的。

DP的思路一开始我怎么也想不出来,后面看了大牛的提示才知道在离散数学中有这么一条定律:
Diworth定理: 对于任意有限偏序集,其最大反链中元素的数目必等于最小链划分中链的数目。
对于这道题,所有木头就是一个偏序集,最小链划分中链的数目(即非递减子序列的最少划分数个数) = 最大反链中元素的数目(最长下降子序列的长度)
具体定理的证明采用了反证法+数据归纳法,这里我其实弄得也不太懂,就不证明了····
然后问题就转化为求:
木头按什么顺序堆放找到的 最长的下降序列时是所有顺序里最长的呢 ?有多长?
不难想出,排好序的的木头,其最长的下降序列时是所有顺序里最长····
最长的下降序列长度:
等于按l递增排序后 stick按w比较得到的最长下降子序列的长度L (贪心思想,让逆序的l尽量小,则链会更长)
然后问题就变成了LIS问题,只不过递增变成了递减:
而关于LIS问题,有两种常用解法,一种为o(n^2),另一种为o(nlogn),这里给出o(nlogn)的二分搜查法

实现代码

#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
#include<functional>
using namespace std;
#define MAX_N 5000
int T,N;
struct  Wooden{int l,w;
};
Wooden wooden[MAX_N];
int dp[MAX_N] ;
// 两种理解: 以i为尾部的最大的下降子序列solve or 长度为i的下降子序列末尾元素的最小值solve2bool cmp(const Wooden& x,const Wooden& y){if(x.l == y.l)return x.w < y.w;else return x.l < y.l;
}
void solve(){  //dp o(N^2) 可优化为  o(N*lgn) memset(dp,0,sizeof(dp) );int res = 0;for(int i = 0;i < N;++i){dp[i] = 1;//初始化为只包含aifor(int j = 0;j < i;++j){if(!(wooden[i].l >= wooden[j].l && wooden[i].w >= wooden[j].w) ){ //a[i] < a[j]dp[i] = max(dp[i],dp[j] + 1);}}res = max(res,dp[i]);}printf("%d\n",res);
}void solve2(){ // o(N*lgn) memset(dp, -1, N * sizeof(int));for (int i = 0; i < N; ++i){*lower_bound(dp, dp + N, wooden[i].w, greater<int>()) = wooden[i].w;//常用的逆序可以加载头文件#include<functional>,里边有一个greater<int>()函数即可对逆序求最近位置。}printf("%d\n",lower_bound(dp, dp + N, -1, greater<int>()) - dp); // 第一个 -1前都是更新了有值的~
} int main(){scanf("%d",&T);for(int i = 0;i < T;++i){scanf("%d",&N);      for(int j = 0;j < N;++j){scanf("%d%d",&wooden[j].l,&wooden[j].w);}sort(wooden,wooden+N,cmp);//solve();solve2();}return 0;
}

2.Bridging signals

题目大意
新来的实习生把路由线路搞得一团糟!原本左右端口应当按顺序连接吗,但现在出现了交叉;现在只有切除部分线路,使得任何线路都不相交。希望你写一个程序计算最后最多剩下多少线路?

思路
比较典型的换汤不换药的题
题目看起来比较新奇,但是我们稍加转换:
要求不相交线路最多,左右端不相交,意味着右边的端口从上至下,应该是严格从上往下(非递减),而端口只准连接一个,
求 不相交线路最多 等价于求右端对应字符串的最长递增子序列长度LIS问题,经典的DP问题,可以优化到O(n*logn)
总结: 二分图不交叉最大匹配问题其实就是最长递增序列问题

实现代码

#include<cstdio>
#include<algorithm>
#define MAX_P 40005
#define INF 100000
using namespace std;
int dp[MAX_P];  //长度为i+1的递增子序列末尾元素的最小值
int T,P;
int port[MAX_P];// 可以自己实现二分查找
int binary_search(int left,int right,int path)
{   int mid;while(left<=right){mid=(left+right)/2;if(list[mid] < path) left = mid+1;else  right = mid-1;}return left;//输出大于等于num的最小的数的位置
} void solve(){fill(dp,dp+P,INF);//INF表示没有for(int i = 0;i < P;++i){ //从长度1开始 *lower_bound(dp,dp+P,port[i]) = port[i]; //STL支持的二分查找}printf("%d\n",lower_bound(dp,dp+P,INF) - dp);
}int main(){scanf("%d",&T);for(int i = 0;i < T;++i){scanf("%d",&P);for(int j = 0;j < P;++j)scanf("%d",&port[j]);solve(); }
}

3.Making the Grade

题目大意
已知N个整数A1… AN(1≤N≤2000),给出一组农场高度····(0≤Ai≤1000,000,000)
FJ想把这些海拔调整成一个新的序列B1,…BN,其不是递增就是递减。
由于在农场的任何位置添加或清除污垢的费用是相同的,所以农场改造的总费用是
|A1 - B1| + |A2 - B2| +…+ |A N - BN |
请计算一下把他的农场修整成连续的斜坡的最低成本, 有符号的32位整数int肯定可以用来计算答案。

思路
一开始想用贪心,后面发现贪心确实没想出来···
还是说说DP吧:
dp [ i ] [ j ] :修前 i 个农场,最后一个长度为 j 的cost最小值
则dp[ i+1 ][ j ] = abs(j - a[ i ]) + min(dp[ i ][ j-k ]) k <= j //非递减
则dp[ i+1 ][ j ] = abs(j - a[ i ]) + min(dp[ i ][ MAX_A - k ]) MAX_A - k >= j //非递增
可是 j ≤ 1000,000,000 直接代表高度,情况过多,铁定超时抄内存;

最低成本,要写达到最低成本,不管是递增还是递减,其最终结果的最后一个肯定是原来序列中有的,那么定义:
这里离散化处理,我们认为 j 可能值 只有在 原来序列中 有的值
于是我们 j 可以存一个索引···存 原来序列中 升序排列 的索引值;
则dp[ i+1 ][ j ] = abs(b[ j ] - a[ i ]) + min(dp[ i ] [ j ])
看了大佬的反应,poj数据太浅,这个更深··
更深的测试数据提交

实现代码:(以下代码在 更深的测试上提交了,那个必须用long long)

#include<cstdio>
#include<algorithm>
#define MAX_N 5005
using namespace std;
//int dp[MAX_N][MAX_N];
typedef long long LL;
LL dp[6005];//一维数组
int N;
LL A[6005],B[6005];
LL min_cost = INF;
LL min(LL x,LL y){return x < y ? x : y;
}
LL Abs(LL a)
{if(a >= 0)return a;else return -a;
}
bool cmp(LL  x,LL y)
{return x > y ;  // x > y ? x : y 开始写出这样导致 疯狂 RE····
}void solve2(){  //利用状态转移 写出一维数组  ,或者 2个数组的滚动数组 空间
//  //这里是非递减的最小代价 POJ数据较弱只写非递减的代码就能过了```sort(B,B+N); //递增,小的先选for(int i = 0; i < N; i++) //初始化,当  dp[0][j] 只使用第一块土地 dp[i]= Abs(A[0] - B[i]);for(int i = 1;i < N;++i){LL min_dp = dp[0];for(int j = 0;j < N;++j){min_dp = min(min_dp,dp[j]);  // 记录j以前的最小值 dp[j] = Abs(B[j] - A[i]) + min_dp;}}LL min_cost = dp[0];for(int i = 0;i < N;++i){min_cost = min(min_cost,dp[i]);}//____________________________这里是非递增 ··· 区别就在于逆序排序·sort(B,B+N,cmp); //递减,大的先选for(int i= 0; i < N; i++) //初始化dp[i]= Abs(A[0] - B[i]);for(int i = 1;i < N;++i){LL min_dp = dp[0];for(int j = 0;j < N;++j){min_dp = min(min_dp,dp[j]);  // 记录j以前的最小值 dp[j] = Abs(B[j] - A[i]) + min_dp;}}LL ANS = dp[0];for(int i=0; i<N; i++)ANS=min (dp[i],ANS);min_cost = min(min_cost,ANS);printf("%lld\n",min_cost);
} int main()
{scanf("%d",&N);for(int i = 0;i < N;++i){scanf("%lld",&A[i]);B[i] = A[i];}solve2();
}

4.Space Elevator

题目大意
有K (1 <= K <= 400) 种不同类型的积木来建造塔,类型i的每个块高度为h_i (1 <= h_i <= 100),数量为c_i (1 <= c_i <= 10), 以及类型i任何部分不能超过最大高度a_i (1 <= a_i <= 40000)。

根据规则,将积木一层一层堆叠起来建造最高的太空电梯,

思路

只不过重量变成了高度限制 ~
dp[ i ][ j ] :前i种木头能否能到 j 高度
dp[ i ][ j ] = dp[ i-1 ][ j ] || dp[ i-1 ][ j - h[ i ] ]

实现代码

#include<cstdio>
#include<algorithm>
#define MAX_K 405
#define MAX_A 40005
struct Wooden{int h,a,c;
};
Wooden wooden[MAX_K];
Wooden wooden_01[MAX_K * 4];
using namespace std;
int K;
bool cmp(const Wooden& x,const Wooden& y){return x.a < y.a;
}
bool dp[MAX_A];//表示这种高度可以达到 int main(){scanf("%d",&K);int number01 = 0;for(int i = 0;i < K;++i){scanf("%d%d%d",&wooden[i].h,&wooden[i].a,&wooden[i].c);  int num = wooden[i].c;for(int j = 1;j <= num;j *= 2){  // 拆分为 0 / 1wooden_01[number01].a = wooden[i].a;wooden_01[number01++].h = j * wooden[i].h; //长度组合     num -= j;      }if(num > 0){wooden_01[number01].a = wooden[i].a;wooden_01[number01++].h = num * wooden[i].h; //长度组合}}sort(wooden_01,wooden_01+number01,cmp); fill(dp,dp+MAX_A,false); dp[0] = true;//初始化 到达0高度肯定可以int max_highest = 0; for(int i = 0;i < number01;++i){  //对0/1的每个背包 for(int j = wooden_01[i].a;j >= wooden_01[i].h;j--){  if(dp[j - wooden_01[i].h]) { //如果不拿这个背包可以达到 dp[j] = true;max_highest = (max_highest > j ? max_highest : j);}}}printf("%d\n",max_highest);
}

5.Cow Exhibition

题目大意
有一群牛 N (1 <= N <= 100) ,其有两个值 smartness Si (-1000 <= Si <= 1000) 和 funness Fi (-1000 <= Fi <= 1000)。请选出一些牛,他们的Si和 、Ti和 都不为 负数,然后Si + Ti最大

思路
0 / 1 背包的变种,可以考虑把 Si当作重量,Fi当作价值 ,需要对负值进行处理
这道题比较难的点是如何解决负数下标····
由于Si的总体范围我们并不清楚,可以动态求取或者设置一个较大值,扩大数组··
我们扩大数组大小,右移原来的原点至,至少比最大负值Si的绝对值要大的位置(确保所有索引的大于等于0)···
然后采用 0 / 1 背包的方法求解
dp[i][j] = max(dp[i-1][j],dp[i-1][j - Si] + Fi)
如果使用一维数组的话,需要针对 Si的正负值,改变状态转移的方向

实现代码

#include<cstdio>
#include<algorithm>
#define MAX_N 105
#define MAX_S 1005
#define INF 10000000
using namespace std;
struct Cow{int Si,Fi;
};
Cow cows[MAX_N];
int N;
int dp[MAX_N * MAX_S * 2]; //为了避免出现负数下标,扩大数组,以中心为原来的dp[0] void solve(){fill(dp,dp+MAX_N * MAX_S * 2,-INF);//初始化,因为价值有负的,所以初始化为所有负值加起来都不能达到的一个数 dp[MAX_N * MAX_S] = 0;//右移后的原点,相当于没有重量时的价值 for(int i = 0; i < N;++i){if(cows[i].Si > 0) // dp[i][j] = max(dp[i-1][j],dp[i-1][j - Si] + Fi) j - Si小于j 为确保关系转移逆向走 {for(int j = MAX_N * MAX_S * 2 - 1; j >= cows[i].Si;--j){dp[j] = max(dp[j],dp[j - cows[i].Si] + cows[i].Fi); } }else {  //j - Si > j 为确保关系转移正向走 for(int j = 0; j < MAX_N * MAX_S * 2 + cows[i].Si; ++j){dp[j] = max(dp[j],dp[j - cows[i].Si] + cows[i].Fi); } }} int res = -INF;//由于我们这题的价值是fi + Si,需要遍历一下 (由于原点我们更换了,所以这里我们也得更换一,这样确保Si都是不小于0的 ) for (int i = MAX_N * MAX_S;i < MAX_N * MAX_S * 2;i++){if(dp[i] >= 0)  //Fi大于0 res = max(res,dp[i] + i - MAX_N * MAX_S); //fi + Si } if(res <= 0)printf("%d\n",0);else printf("%d\n",res);}
int main(){scanf("%d",&N);for(int i = 0;i < N;++i){scanf("%d%d",&cows[i].Si,&cows[i].Fi);}solve();return 0;
}

以上就是《挑战程序设计竞赛》–初级篇习题POJ部分【动态规划】习题,题目还是挺不错的,很适合新手入门级的DP练习…就是 有些题坑还是比较多的
(能力有限,如有错误 ,还望指出)

《挑战程序设计竞赛》--初级篇习题POJ部分【动态规划】相关推荐

  1. 《挑战程序设计竞赛》--初级篇习题POJ部分【2.4 - 2.6】

    这次是延续上次的<挑战程序设计竞赛>初级篇,总结部分poj上的练习题,主要是2.4 ~ 2.6部分: 导航 2.4 加工并存储的数据结构 优先队列 Sunscreen MooUnivers ...

  2. 《挑战程序设计竞赛》--初级篇习题POJ部分【穷竭搜索+贪心】

    最近看了<挑战程序设计竞赛>初级篇,这里总结一下部分poj上的练习题,主要涉及方面为: 穷竭搜索 and 贪心算法 具体题目: 简单导航 一.穷竭搜索 二.贪心算法 一.穷竭搜索 穷竭搜索 ...

  3. POJ 1150 The Last Non-zero Digit 《挑战程序设计竞赛》

    为什么80%的码农都做不了架构师?>>>    POJ 1150 The Last Non-zero Digit超大组合数:求超大组合数P(n, m)的最后一个非零位.4.1更加复杂 ...

  4. POJ 3735 Training little cats​ 题解 《挑战程序设计竞赛》

    为什么80%的码农都做不了架构师?>>>    POJ 3735 Training little cats调教猫咪:有n只饥渴的猫咪,现有一组羞耻Play,由k个操作组成,全部选自: ...

  5. POJ 3608 Bridge Across Islands 《挑战程序设计竞赛》

    为什么80%的码农都做不了架构师?>>>    POJ 3608 Bridge Across Islands跨岛大桥:在两个凸包小岛之间造桥,求最小距离?3.6与平面和空间打交道的计 ...

  6. POJ 3713 Transferring Sylla​ 题解 《挑战程序设计竞赛》

    为什么80%的码农都做不了架构师?>>>    POJ 3713 Transferring Sylla三连通图:判断一个无向图是否三连通?3.5借助水流解决问题的网络流最大流刷个题报 ...

  7. 挑战程序设计竞赛(第二章习题总结)

    文章目录 搜索 Curling 2.0(POJ 3009) Meteor Shower(POJ 3669) Smallest Difference(POJ 2718) Hopscotch(POJ 30 ...

  8. POJ 1418 Viva Confetti 题解 《挑战程序设计竞赛》

    为什么80%的码农都做不了架构师?>>>    POJ 1418 Viva Confetti礼花:Confetti 是一些大小不一的彩色圆形纸片,人们在派对上.过节时便抛洒它们以示庆 ...

  9. 挑战程序设计竞赛(第2版)》

    <挑战程序设计竞赛(第2版)> 基本信息 作者: (日)秋叶拓哉 岩田阳一 北川宜稔 译者: 巫泽俊 庄俊元 李津羽 丛书名: 图灵程序设计丛书 出版社:人民邮电出版社 ISBN:9787 ...

最新文章

  1. Spark源码阅读03-Spark存储原理之序列化和压缩
  2. dataframe常用操作_【Data Mining】机器学习三剑客之Pandas常用算法总结上
  3. ios rsa java_一篇搞定RSA加密与SHA签名|与Java完全同步
  4. 二、PHP框架Laravel学习笔记——路由的定义和控制器
  5. 华为P40 Pro将搭载索尼IMX 700传感器:支持十六像素合一
  6. java arraylist 构造_深入理解java集合框架之---------Arraylist集合 -----构造函数
  7. WinForm框架开发教程 - 如何实现简单化开发?
  8. [渝粤教育] 南京审计大学 审计学基础 参考 资料
  9. pcsx2运行ps1_PS2模拟器PSX2设置及使用教程.doc
  10. 我用Vue3+TS实现了一个新年倒计时组件,适用于各种场景
  11. 史上MySQL安装配置教程最细,一步一图解
  12. Android人脸支付功能,终于来了,华为Mate20 Pro微信人脸支付功能已上线
  13. 此生未完成 --- 于娟
  14. ubuntu18.04 使用scp命令
  15. 【聚水潭SDK使用说明】
  16. Eureka 健康检查
  17. 细数那些最令人难忘的电视剧
  18. 今年十月最新语言排行榜
  19. C++中的reverse()函数
  20. python图像处理(图像缩放)

热门文章

  1. 用计算机在作文格中打单字字,描写一个字的作文
  2. redshift 踩坑
  3. Elasticsearch:Rank feature query - 排名功能查询
  4. 时间序列回归模型(Forecasting: Principles and practice第六章)
  5. TCP协议详解(一) TCP服务的特点和TCP头部结构
  6. 卡牌系统psv游戏推荐_PSV精品游戏推荐之一,让你的小V再次发挥余热吧!
  7. 刚出道的黑客搞瘫美国输油管道!
  8. Sequelize 大于_巴菲特买股三原则:毛利率大于30%+ROE大于15%+现金流大于100%
  9. 实现字典树(前缀树、Trie树)并详解其应用
  10. cmake 安装下载