HDU 2577 简单线性DP
http://acm.hdu.edu.cn/showproblem.php?pid=2577
这道题是简单的线性DP,状态很明确,就是当前的caps是on还是off的,因此我们定义dp[i][0]表示输入第i个字符的时候caps off,dp[i][1]表示输入第i个字符时caps on。定义好了状态之后,我们按照输入的习惯进行转移就行了,具体见代码。
#include <cstring> #include <cstdlib> #include <cstdio> #include <iostream> using namespace std; const int MAX = 120; char str[MAX]; int dp[MAX][2]; int main() { int T; scanf("%d",&T); while(T--) { scanf("%s",str+1); dp[0][0] = 0; dp[0][1] = 1; int n = strlen(str+1); for(int i=1;i<=n;i++) { if(str[i]>='a'&&str[i]<='z') { dp[i][0]=min(dp[i-1][0]+1,dp[i-1][1]+2); dp[i][1]=dp[i-1][1]+2; } else { dp[i][1] = min(dp[i-1][1]+1,dp[i-1][0]+2); dp[i][0] = dp[i-1][0] + 2; } } printf("%d\n",min(dp[n][0],dp[n][1]+1)); } return 0; }
HDU 1513 最长公共子串
http://acm.hdu.edu.cn/showproblem.php?pid=1513
这道题一开始我直接记忆化搜索,交上去显然会MLE,于是思考新的解法。
我们发现对于字符串添加最少个字符成为回文串,我们可以转化成为两个串的最长公共子串。
我们可以这样理解,设原始串是s1,那么s1反向之后的串是s2,如果s1是回文串,那么s1完全与s2相同。可以证明如果按照s1和s2的最长公共子串来对s1构建回文串将使添加的字符数最少。那么对于求两个串的最长公共子串直接使用DP,这里加一个滚动数组优化就行了。详细见代码:
#include <iostream> #include <cstring> #include <cstdio> #include <cstdlib> using namespace std; const int MAX = 5005; int dp[2][MAX]; char s1[MAX],s2[MAX]; int main() { int n; while(scanf("%d",&n)!=EOF) { scanf("%s",s1+1); for(int i=1;i<=n;i++) { s2[i] = s1[n+1-i]; } memset(dp,0,sizeof(dp)); for(int i=1;i<=n;i++) { for(int j=1;j<=n;j++) { dp[i%2][j]=max(dp[(i-1)%2][j],dp[i%2][j-1]); if(s1[i]==s2[j]) dp[i%2][j] = max(dp[(i-1)%2][j-1]+1,dp[i%2][j]); } } printf("%d\n",n-dp[n%2][n]); } return 0; }
HDU 1978 记忆化搜索,亦可以反向推回去,原理是一样的。
http://acm.hdu.edu.cn/showproblem.php?pid=1978
只要在一个方格的能量范围内可以走到的方格,都可以累加方案数,于是只要简单的定义状态dp[i][j]表示方格[i][j]到达终点的方案数就行了,简单记忆化搜索。
#include <iostream> #include <cstdio> #include <cstring> #include <cstdlib> #define MOD 10000 #define MAX 110 using namespace std; int dp[MAX][MAX],h[MAX][MAX]; int n,m; int solve(int row,int col) { if(dp[row][col]>-1) return dp[row][col]; if(row==n-1&&col==m-1) { dp[row][col] = 1; return 1; } int t=0; for(int i=row;i<n;i++) { for(int j=col;j<m;j++) { if(i==row&&j==col) continue; else if(abs(i-row)+abs(j-col)<=h[row][col]) t = (t+(solve(i,j)%MOD))%MOD; else break; } } dp[row][col] = t; return t; } int main() { int T; scanf("%d",&T); while(T--) { scanf("%d%d",&n,&m); for(int i=0;i<n;i++) { for(int j=0;j<m;j++) scanf("%d",&h[i][j]); } memset(dp,-1,sizeof(dp)); printf("%d\n",solve(0,0)); } return 0; }
HDU 1506
http://acm.hdu.edu.cn/showproblem.php?pid=1506
这道题感觉上不是特别像DP,更像是数据结构的题。不过我们还是可以使用递推转移的思想。
我们这样定义状态,dpl[i]表示区间[dpl[i],i]的所有柱子的高度都大于等于标号i的柱子的高度,也就是说,区间内的柱子i的高度最小。
于是想想转移,如果标号dpl[i]-1的柱子比i的高的话,那么[dpl[dpl[i]-1],i]的柱子的高度肯定是大于等于i的高度的,于是我们置dpl[i]=dpl[dpl[i]-1]。
同样处理右边的柱子的高度,最后O(n)的遍历一遍以最矮的那根柱子为中间的连续的面积就行了,具体见代码。
#include <cstring> #include <cstdlib> #include <cstdio> #include <iostream> #define MAX 100010 using namespace std; typedef long long LL; int dpl[MAX],dpr[MAX]; int h[MAX]; void initDP(int n) { for(int i=0;i<n;i++) dpl[i]=dpr[i]=i; for(int i=1;i<n;i++) { while(dpl[i]-1>=0&&h[i]<=h[dpl[i]-1]) dpl[i]=dpl[dpl[i]-1]; } for(int i=n-2;i>=0;i--) while(dpr[i]+1<n&&h[i]<=h[dpr[i]+1]) dpr[i]=dpr[dpr[i]+1]; } int main() { int n; LL ans; while(scanf("%d",&n)&&n) { for(int i=0;i<n;i++) { scanf("%d",&h[i]); } if(n==1) { printf("%d\n",h[0]); continue; } ans = 0; initDP(n); for(int i=0;i<n;i++) { LL t = LL(dpr[i]-dpl[i]+1)*(LL)h[i]; if(ans<t) ans = t; } printf("%I64d\n",ans); } return 0; }
HDU 1505 dp预处理之后就和上一题一样的处理方式
http://acm.hdu.edu.cn/showproblem.php?pid=1505
我们按照题意进行转化成01矩阵,R用0代替,F用1代替,于是我们对于每一行预处理一个dp数组出来,状态定义如下:
dp[i][j]表示第j列的前i行中以第i行为结尾的连续的1的个数,那么对于每一行的dp值预处理出来之后,然后求一个上一题的最大矩阵面积就行了,于是这一题经过dp预处理之后就和上一题一样了。
具体见代码:
#include <iostream> #include <cstring> #include <cstdio> #include <cstdlib> #define MAX 1010 using namespace std; int maze[MAX][MAX],dp[MAX][MAX]; void initDP(int n,int m) { for(int j=0;j<m;j++) dp[0][j]=maze[0][j]; for(int i=1;i<n;i++) { for(int j=0;j<m;j++) { if(maze[i][j]==0) dp[i][j]=0; else dp[i][j]=dp[i-1][j]+1; } } } int l[MAX],r[MAX]; int get(int *a,int m) { for(int i=0;i<m;i++) l[i]=r[i]=i; for(int i=1;i<m;i++) { while(l[i]-1>=0&&a[i]<=a[l[i]-1]) l[i]=l[l[i]-1]; } for(int i=m-2;i>=0;i--) { while(r[i]+1<m&&a[i]<=a[r[i]+1]) r[i]=r[r[i]+1]; } int ans = 0; for(int i=0;i<m;i++) ans = max(ans,(r[i]-l[i]+1)*a[i]); return ans; } int solve(int n,int m) { initDP(n,m); int ans = 0; for(int i=0;i<n;i++) { ans = max(ans,get(dp[i],m)); } return ans; } int main() { int T,n,m; scanf("%d",&T); while(T--) { scanf("%d%d",&n,&m); getchar(); char s[5]; for(int i=0;i<n;i++) { for(int j=0;j<m;j++) { scanf("%s",s); if(s[0]=='R') maze[i][j]=0; else maze[i][j]=1; } } printf("%d\n",solve(n,m)*3); } return 0; }
HDU 2870
http://acm.hdu.edu.cn/showproblem.php?pid=2870
这道题其实转个弯就和上一道题是一样的了。
我们可以知道,我们所选的最大的相同的子矩阵,肯定是要么全a或b或c,因此我们三种情况全部求一边就行了。
当我们是要求全a的时候,我们就把所以可以转化成a的字符用1表示,其他的用0表示,这样就变成了求最大子矩阵了,因此和上一道题是一模一样的,只不过是转了一个弯而已,比较简单。
知道这个思路自己就很容易实现了,如果不会就看看代码吧。
#include <iostream> #include <cstring> #include <cstdio> #include <cstdlib> #define MAX 1010 using namespace std; int maze[MAX][MAX],dp[MAX][MAX]; void initDP(int n,int m) { for(int i=0;i<m;i++) dp[0][i]=maze[0][i]; for(int i=1;i<n;i++) { for(int j=0;j<m;j++) { if(maze[i][j]==0) dp[i][j]=0; else dp[i][j]=dp[i-1][j]+1; } } } int l[MAX],r[MAX]; int get(int *t,int tlen) { for(int i=0;i<tlen;i++) l[i]=r[i]=i; for(int i=1;i<tlen;i++) { while(l[i]-1>=0&&t[i]<=t[l[i]-1]) l[i]=l[l[i]-1]; } for(int i=tlen-2;i>=0;i--) { while(r[i]+1<tlen&&t[i]<=t[r[i]+1]) r[i]=r[r[i]+1]; } int ans = 0; for(int i=0;i<tlen;i++) ans = max(ans,(r[i]-l[i]+1)*t[i]); return ans; } bool check(char ch1,char ch2) { switch(ch1) { case 'w': { if(ch2=='a'||ch2=='b') return true; else return false; } case 'x': { if(ch2=='b'||ch2=='c') return true; else return false; } case 'y': { if(ch2=='a'||ch2=='c') return true; else return false; } case 'z': { if(ch2=='a'||ch2=='b'||ch2=='c') return true; else return false; } default: return false; } } char s[MAX][MAX]; int solve(int n,int m,char ch) { for(int i=0;i<n;i++) { for(int j=0;j<m;j++) { if(s[i][j]==ch) maze[i][j]=1; else if(check(s[i][j],ch)) maze[i][j]=1; else maze[i][j]=0; } } initDP(n,m); int ans = 0; for(int i=0;i<n;i++) { ans = max(get(dp[i],m),ans); } return ans; } int main() { int n,m; while(scanf("%d%d",&n,&m)!=EOF) { for(int i=0;i<n;i++) scanf("%s",s[i]); int ans = solve(n,m,'a'); ans = max(solve(n,m,'b'),ans); ans = max(solve(n,m,'c'),ans); printf("%d\n",ans); } return 0; }
HDU 2686 记忆化搜索
http://acm.hdu.edu.cn/showproblem.php?pid=2686
这道题很有意思,要转好几个弯才想得到解法。
首先,题意是说回到起点但是不经过同一个格子,于是我们这样考虑,从起点开始分两路往终点方向走,这样到达重点是所得到的最大的和就是题目中要求的,这个很好理解吧。
其次,在方格上走,走相同的步数可以到达终点,因此我们在分两路走的时候只要保证不要走到同一个格点就行了,走到同一个格点必然走了相同的步数,因为我们是从同样的起点出发的。
利用上面两点进行记忆化搜索就行了。
详细见代码:
#include <iostream> #include <cstring> #include <cstdio> #include <cstdlib> #define MAX 31 #define INF 0xFFFFFFF using namespace std; int n; int dp[MAX][MAX][MAX][MAX]; int maze[MAX][MAX]; bool check(int x,int y) { if(x>=0&&x<n&&y>=0&&y<n) return true; else return false; } int dir[2][2]={{1,0},{0,1}}; bool equal(int x1,int y1,int x2,int y2) { if(x1==x2&&y1==y2) return true; else return false; } void mySwap(int &a,int &b) { int t = a; a=b,b=t; } int solve(int x1,int y1,int x2,int y2) { if(x1>x2) { mySwap(x1,x2); mySwap(y1,y2); } else if(x1==x2) { if(y1>y2) { mySwap(y1,y2); } } if(dp[x1][y1][x2][y2]>-1) return dp[x1][y1][x2][y2]; if(x1==n-1&&y1==n-1) return 2*maze[n-1][n-1]; int tx1,ty1,tx2,ty2; int ans = -INF; for(int i=0;i<2;i++) { tx1=dir[i][0]+x1; ty1=dir[i][1]+y1; if(check(tx1,ty1)) { for(int j=0;j<2;j++) { tx2=dir[j][0]+x2; ty2=dir[j][1]+y2; if(check(tx2,ty2)) { if(equal(tx1,ty1,n-1,n-1)&&equal(tx2,ty2,n-1,n-1)) { ans = max(ans,solve(tx1,ty1,tx2,ty2)); } if(equal(tx1,ty1,tx2,ty2)) continue; else { ans = max(solve(tx1,ty1,tx2,ty2),ans); } } } } } dp[x1][y1][x2][y2]=maze[x1][y1]+maze[x2][y2]+ans; return dp[x1][y1][x2][y2]; } int main() { while(scanf("%d",&n)!=EOF) { for(int i=0;i<n;i++) { for(int j=0;j<n;j++) scanf("%d",&maze[i][j]); } memset(dp,-1,sizeof(dp)); int ans = solve(0,0,0,0); printf("%d\n",ans-maze[0][0]-maze[n-1][n-1]); } return 0; }
HDU 3392
http://acm.hdu.edu.cn/showproblem.php?pid=3392
将个数较小的那个数组中的height全部匹配完,然后是的所有的差值的总和最小。
我们先将两个数组进行排序,排完序后得到一个非常优美的结论,就是匹配的时候不会进行交叉匹配,因为交叉匹配一定不是最优的。
又因为两个数组的个数差距不会超过100,因此我们这样定义状态:
dp[i][j]表示前i个数,并且第i个数对应偏移j个位置进行匹配的时候的最小结果,那么现在转移方程就非常好写了:
dp[i][j]=fabs(a[i]-b[i+j])+min(dp[i-1][k]) {j>=k>=0}
那么这个时候进行一个优化,用一个t记录dp[i-1]前j个数中的最小值,这样就能够将转移优化成O(1)的了。
如果有什么不明白请看代码:
#include <iostream> #include <cstring> #include <cstdlib> #include <cstdio> #include <algorithm> #include <cmath> #define MAX 10010 #define INF 100000000 using namespace std; double x[MAX],y[MAX]; double dp[MAX][110]; double solve(double *a,int n,double *b,int m) { int dt = abs(n-m); for(int k=0;k<=dt;k++) { dp[0][k]=fabs(a[0]-b[k]); } for(int i=1;i<n;i++) { double t=(double)INF; for(int j=0;j<=dt;j++) { t = min(dp[i-1][j],t); dp[i][j]=t+fabs(a[i]-b[i+j]); } } double ans = (double)INF; for(int i=0;i<=dt;i++) ans = min(ans,dp[n-1][i]); return ans; } int main() { int n,m; while(scanf("%d%d",&n,&m)) { if(n==0&&m==0) break; for(int i=0;i<n;i++) scanf("%lf",&x[i]); for(int i=0;i<m;i++) scanf("%lf",&y[i]); sort(x,x+n); sort(y,y+m); double ans; if(n>m) ans = solve(y,m,x,n); else ans = solve(x,n,y,m); printf("%.6f\n",ans); } return 0; }
HDU 1422 最大连续元素和
http://acm.hdu.edu.cn/showproblem.php?pid=1422
这道题,把生活费减去花费得到一个v值之后,把链拉长一倍,问题就变成求一个串的最大的连续元素和,并且其中任意一个从该串启示的连续的元素和不能出现负数。
我们这样定义状态:
dp[i]表示以元素i结尾的最大的连续的元素的和,用num[i]表示在这种情况下,连续的元素的个数。
然后按照题目中的要求进行转移就行了,这个还是比较好想到的。如果不会可以参照一下代码:
#include <iostream> #include <cstring> #include <cstdlib> #include <cstdio> #include <algorithm> #include <cmath> #define MAX 100010 using namespace std; typedef long long LL; int v[MAX*2],num[MAX*2]; LL dp[MAX*2]; int main() { int n; while(scanf("%d",&n)!=EOF) { int t1,t2; for(int i=1;i<=n;i++) { scanf("%d%d",&t1,&t2); v[i]=t1-t2; v[i+n]=v[i]; } dp[0]=-1; num[0]=0; int ans = 0; for(int i=1;i<=n*2;i++) { if(dp[i-1]<0) { if(v[i]>=0) num[i]=1; else num[i]=0; dp[i]=(LL)v[i]; } else { if(dp[i-1]+v[i]>=0) { dp[i]=dp[i-1]+(LL)v[i]; num[i]=num[i-1]+1; } else { dp[i]=(LL)v[i]; num[i]=0; } } ans = max(num[i],ans); } ans = min(ans,n); printf("%d\n",ans); } return 0; }
HDU 2830 DP预处理
http://acm.hdu.edu.cn/showproblem.php?pid=2830
这道题又是求最大的子矩阵和,但是有个性质就是列可以随意交换。
于是我们先dp进行预处理,dp[i][j]表示第j列,以i行的元素结尾的连续的1的个数。
如果列不可以随意交换的话,我们的目的总是让最小的那个元素尽可能向两边扩展。由于可以随意交换两个列,因此我们排序之后,对最小的那个元素一定是最优的,因为后面的元素都比他大,因此对每一行求一个最大值就行了。
这道题还是比较简单的,只要想到了思路就很好写了,具体见代码:
#include <iostream> #include <cstring> #include <cstdlib> #include <cstdio> #include <algorithm> #define MAX 1010 using namespace std; char s[MAX][MAX]; int dp[MAX][MAX]; int get(int *t,int m) { int a[MAX]; for(int i=0;i<m;i++) a[i]=t[i]; sort(a,a+m); int ans = 0; for(int i=0;i<m;i++) { ans = max(ans,a[i]*(m-i)); } return ans; } int main() { int n,m; while(scanf("%d%d",&n,&m)!=EOF) { for(int i=0;i<n;i++) scanf("%s",s[i]); for(int j=0;j<m;j++) { if(s[0][j]=='1') dp[0][j]=1; else dp[0][j]=0; } for(int i=1;i<n;i++) { for(int j=0;j<m;j++) { if(s[i][j]=='0') dp[i][j]=0; else dp[i][j]=dp[i-1][j]+1; } } int ans = 0; for(int i=0;i<n;i++) { ans = max(ans,get(dp[i],m)); } printf("%d\n",ans); } return 0; }
HDU 1950 最长上升子序列(线段树进行优化)
http://acm.hdu.edu.cn/showproblem.php?pid=1950
这道题,理解题意之后就知道是要求最长上升子序列,这种题定义状态还是比较好想的。
定义dp[i]表示以a[i]为结尾的最长的上升序列的元素个数,那么转移方程当然也很好写:dp[i]=max(dp[k])+1,{k<a[i]}
这里需要用到一个区间的最值,我们使用线段树进行优化,这个线段树很好写,就是更新点查找区间的最大值。
具体见代码:
#include <iostream> #include <cstring> #include <cstdlib> #include <cstdio> #define MAX 40010 using namespace std; typedef struct NODE { int l,r; int v; }Node; Node seg[MAX*4]; void init(int l,int r,int k) { seg[k].l=l; seg[k].r=r; seg[k].v=0; if(l==r) return ; init(l,(l+r)/2,2*k); init((l+r)/2+1,r,2*k+1); } void update(int id,int v,int k) { if(seg[k].l==seg[k].r&&seg[k].l==id) { seg[k].v+=v; return; } int mid=(seg[k].l+seg[k].r)/2; if(id<=mid) update(id,v,2*k); else update(id,v,2*k+1); seg[k].v=max(seg[2*k].v,seg[2*k+1].v); } int read(int l,int r,int k) { if(l>r) return 0; if(seg[k].l==l&&seg[k].r==r) return seg[k].v; int mid = (seg[k].l+seg[k].r)/2; if(r<=mid) return read(l,r,2*k); else if(l>mid) return read(l,r,2*k+1); else return max(read(l,mid,2*k),read(mid+1,r,2*k+1)); } int dp[MAX]; int main() { //freopen("out.txt","w",stdout); int T,n; scanf("%d",&T); while(T--) { scanf("%d",&n); init(1,n,1); int ans = 0; for(int i=1;i<=n;i++) { int t; scanf("%d",&t); dp[i]=read(1,t-1,1)+1; ans = max(ans,dp[i]); update(t,dp[i],1); } printf("%d\n",ans); } return 0; }
HDU 2993 斜率优化DP
http://acm.hdu.edu.cn/showproblem.php?pid=2993
这道题自己想了很久都没有想到怎么做,后来看了解题报告才知道有一篇关于数形结合的论文就是以这题为例题,于是果断看论文。
论文的大概思路我们对于一个点在距离合法的范围内,逐个加点维护一个凸包,论文中证明了维护凸包的原因,因为上凸点对于增大斜率是没有帮助的,所以维护一个凸包就是删除上凸点的结果。
最后我们是要求一个点对应凸包上一个点的斜率最大,由于点的纵坐标递增,因此可以利用一个指针p记录切点,后面的监测点进行求解时直接从该点往后面走就行了。
具体见代码:
#include <iostream> #include <cstring> #include <cstdlib> #include <cstdio> #define MAX 100010 using namespace std; typedef struct POINT { int x,y; POINT(){} POINT(int tx,int ty) { x=tx,y=ty; } }Point; Point bag[MAX]; int sum[MAX]; int dblcmp(Point p1,Point p2,Point p0) { return (p1.x-p0.x)*(p2.y-p0.y)-(p1.y-p0.y)*(p2.x-p0.x); } double get(Point p1,Point p2) { double ty = (p2.y-p1.y),tx= (p2.x-p1.x); return ty/tx; } int main() { int n,k; while(scanf("%d%d",&n,&k)!=EOF) { for(int i=1;i<=n;i++) scanf("%d",&sum[i]); sum[0]=0; for(int i=1;i<=n;i++) sum[i]+=sum[i-1]; int front=0,rear=-1; int p=0; double ans = 0,tans = 0; for(int i=k;i<=n;i++) { while(front<=(rear-1)&&dblcmp(bag[rear],Point(i-k,sum[i-k]),bag[rear-1])<=0) rear--; bag[++rear]=Point(i-k,sum[i-k]); if(p>rear) p=rear; tans = get(bag[p],Point(i,sum[i])); for(int j=p+1;j<=rear;j++) { double t = get(bag[j],Point(i,sum[i])); if(t>tans) { tans = t; p++; } } ans = max(ans,tans); } printf("%.2f\n",ans); } return 0; }