DP练习

黑书对应题目。

pagesectionno title submit
1131.5.1例题1括号序列POJ1141
1161.5.1例题2棋盘分割POJ1191
1171.5.1例题3决斗Sicily1822
1171.5.1例题4“舞蹈家”怀特先生ACM-ICPC Live Archive
1191.5.1例题5积木游戏http://202.120.80.191/problem.php?problemid=1244
1231.5.2例题1方块消除http://poj.org/problem?id=1390
1231.5.2例题2公路巡逻http://202.120.80.191/problem.php?problemid=1600
1251.5.2例题3并行期望值POJ1074
1311.5.2例题6不可分解的编码http://acmicpc-live-archive.uva.es/nuevoportal/data/problem.php?p=2475
1331.5.2例题7青蛙的烦恼http://codewaysky.sinaapp.com/problem.php?id=1014
1351.5.2例题9最优排序二叉树http://judge.noi.cn/problem?id=1059
1381.5.2例题10Bugs公司POJ1038
1391.5.2例题11迷宫统计http://uva.onlinejudge.org/index.php?option=com_onlinejudge&Itemid=8&category=70&page=show_problem&problem=1472
1421.5.2例题12贪吃的九头龙http://judge.noi.cn/problem?id=1043
1511.5.3问题2最长上升子序列问题http://uva.onlinejudge.org/index.php?option=com_onlinejudge&Itemid=8&category=17&page=show_problem&problem=1475
1511.5.3问题3最优二分检索树http://uva.onlinejudge.org/index.php?option=com_onlinejudge&Itemid=8&category=15&page=show_problem&problem=1245
1521.5.3问题4任务调度问题POJ1180
1211.5.11.5.8艺术馆的火灾http://221.192.240.23:9088/showproblem?problem_id=1366
1441.5.21.5.10快乐的蜜月http://judge.noi.cn/problem?id=1052
1451.5.21.5.12佳佳的筷子http://uva.onlinejudge.org/index.php?option=com_onlinejudge&Itemid=8&category=14&page=show_problem&problem=1212
1461.5.21.5.13偷懒的工人POJ1337
1461.5.21.5.15平板涂色POJ1691
1471.5.21.5.16道路重建POJ1947
1471.5.21.5.17圆和多边形http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemCode=1679
1481.5.21.5.18Jimmy落地POJ1661
1481.5.21.5.19免费糖果http://uva.onlinejudge.org/index.php?option=com_onlinejudge&Itemid=8&category=13&page=show_problem&problem=1059
1571.5.31.5.22回文词POJ1159
1571.5.31.5.24邮局POJ1160
1581.5.31.5.26奶牛转圈POJ1946
1581.5.31.5.27元件折叠http://uva.onlinejudge.org/index.php?option=com_onlinejudge&Itemid=8&category=14&page=show_problem&problem=1180

转载出处:http://www.cnblogs.com/vongang/archive/2012/05/03/2481308.html



UVA Dance Dance Revolution——08.03

舞蹈家怀特先生。

题意看黑书吧。

好吧。。其实上面那个链接是我好不容易找到可以提交该题的。。

不知道为什么UVA挂了。

这道题在黑书里面介绍的非常详细,我这里贴一下使用滚动数组来优化的代码。

用dp[x][y][z]存,当第z步的时候左脚在x,右脚在y的情况。

dp[x][y][z] = min(dp[a[i]][y][i + 1] + doit(x , a[i]) , dp[x][a[i]][i + 1] + doit(y , a[i])) ;

#define N 100005
int dp[5][5][2] ;
int a[N] ;
int doit(int a ,int b) {
    if(a == b)return 1 ;
    if(a == 0 || b == 0)return 2 ;
    if(a == 1) {
        if(b == 2 || b == 4 || b == 0)return 3 ;
        return 4 ;
    }
    if(a == 2) {
        if(b == 3 || b == 1 || b == 0)return 3 ;
        return 4 ;
    }
    if(a == 3) {
        if(b == 2 || b == 4 || b == 0)return 3 ;
        return 4 ;
    }
    if(a == 4) {
        if(b == 1 || b == 3 || b == 0)return 3 ;
        return 4 ;
    }
}
int main() {
    while(cin >> a[0] , a[0]) {
        int num = 1 ;
        while(1) {
            cin >> a[num] ;
            num ++ ;
            if(a[num - 1] == 0)break ;
        }
        num -- ;
        for (int i = 0 ; i < 2; i ++ ){
            for (int j = 0 ; j < 5 ; j ++ ){
                for (int k = 0 ; k < 5; k ++ ){
                    dp[j][k][i] = inf ;
                }
            }
        }
        for (int i = 0 ; i < 5 ; i ++ )dp[i][a[num - 1]][num % 2] = dp[a[num - 1]][i][num % 2] = 0 ;
        for (int i =  num - 1 ; i >= 0 ; i -- ) {
            for (int j = 0 ; j < 5 ; j ++ ) {
                for (int k = 0 ; k < 5 ; k ++ ) {
                    dp[j][k][i % 2] = min(dp[a[i]][k][(i + 1) % 2] + doit(j , a[i]) , dp[j][a[i]][(i + 1) % 2] + doit(k ,a[i])) ;
                }
            }
        }
        cout << dp[0][0][0] << endl;
    }
    return 0 ;
}

POJ 3311 裸TSP ——08.07

#include <set>
#include <map>
#include <stack>
#include <cmath>
#include <queue>
#include <cstdio>
#include <string>
#include <vector>
#include <iomanip>
#include <cstring>
#include <iostream>
#include <algorithm>
#define Max 2505
#define ll long long
#define PI acos(-1.0)
#define inf 0x2fffffff
#define LL(x) ( x << 1 )
#define bug puts("here")
#define PII pair<int,int>
#define RR(x) ( x << 1 | 1 )
#define mp(a,b) make_pair(a,b)
#define mem(a,b) memset(a,b,sizeof(a))
#define REP(i,s,t) for( int i = ( s ) ; i <= ( t ) ; ++ i )

using namespace std;

inline void RD(int &ret) {
    char c;
    do {
        c = getchar();
    } while(c < '0' || c > '9') ;
    ret = c - '0';
    while((c=getchar()) >= '0' && c <= '9')
        ret = ret * 10 + ( c - '0' );
}

inline void OT(int a) {
    if(a >= 10)OT(a / 10) ;
    putchar(a % 10 + '0') ;
}
#define N 12
int Map[N][N] ;
int n ;
int dp[1 << N][N] ;
int main() {
    while(cin >> n , n){
        for (int i = 0 ; i < n + 1 ; i ++ ){
            for (int j = 0 ; j < n + 1 ; j ++ ){
                cin >> Map[i][j] ;
            }
        }//floyd,注意MAP[I][J] != MAP[J][I]
        for (int i = 0 ; i < n + 1 ; i ++ ){
            for (int j = 0 ; j < n + 1 ; j ++ ){
                for (int k = 0 ; k < n + 1 ; k ++ ){
                    Map[i][j] = min(Map[i][j] , Map[i][k] + Map[k][j]) ;
                }
            }
        }
        for (int i = 0 ; i < (1 << (n + 1)) ; i ++){
            for (int j = 0 ; j < n + 1 ; j ++ )dp[i][j] = inf ;
        }//从0出发,回到0。可重复走路径
        dp[0][0] = 0 ;
        for (int i = 0 ; i < (1 << (n + 1)) ; i ++ ){
            for (int j = 0 ; j < n + 1 ; j ++ ){
                if(i & (1 << j) == 0)continue ;
                for (int k = 0 ; k < n + 1 ; k ++ ){
                    if(k == j)continue ;//所以这里不需要判断if(i & (1 << k))continue ;
                    int tt = i | (1 << k) ;
                    dp[tt][k] = min(dp[i][j] + Map[j][k] , dp[tt][k]) ;
                }
            }
        }
        cout << dp[(1 << (n + 1)) - 1][0] << endl;
    }
    return 0 ;
}

POJ 1185 炮兵阵地 ——08.07

经典的状压dp。

网上的解释很多也很详细,这里就不多解释了。

花了好久理解了这种做法。


这里用到了很多压缩,首先,预处理出每行所有可行的状态,因为一行之中,可行的状态就是每两个炮兵之间间隔2以上,所以只要简单的移位运算就可以处理出来。

然后记录每种状态炮兵的数量,我们用1表示这里有炮兵0表示没有,那么这种状态的炮兵数量就是二进制1的个数,很好处理。

接下来对于每行的图,也进行压缩,将不可以放置炮兵的地方用1表示,那么用来判断这个位置是否可以放置炮兵的时候就可以使用位运算来简单的操作了。

下面对一些变量进行一下解释:

Count[i]:表示这第i状态的炮兵数量。

st[i]:表示第i状态。1表示有炮兵,0表示没炮兵。

MM[i]:表示第i行压缩后的地图。用1表示不可放置,0表示可放置。

dp[i][j][k]:表示i行,当前状态是k,上一行状态是j,总的可行方案。

对于每个状态的转移,假设当前状态是k,上一状态是j,那么首先判断这两个状态是否会互相攻击,即(st[k] & st[j]),然后判断该行是否可以放置这个状态。即(MM[i] & st[k]) ,还要判断上一状态是否可以放到i - 1行,即(MM[i - 1]  & st[j]) 。

然后枚举上上状态l,同样判断是否合法。那么首先判断i - 2 行是否可以放置状态l,即(MM[i - 2] & st[l]) ,然后是上上状态是否和当前状态,上一状态互相攻击,即(st[j] & st[l] ) , (st[k] & st[l])。

这样判断之后,那么k状态就可以由j 和 l状态推过来,那么更新该状态即可。

状态转移方程:dp[i][j][k] = max(dp[i][j][k] , dp[i - 1][l][j] + Count[k]) ;

int st[111] ;//每行所有的可行的状态
int top = 0 ;
int Count[111] ;//每个状态对应的个数
int n , m ;
char Map[111][15] ;
int dp[111][65][65] ;//dp[i][j][k] .第i行状态是k, i - 1 行状态是j的可行总数
int MM[111] ;//压缩地图
void init() {
    top = 0 ;
    mem(st ,0) ;
    mem(Count ,0) ;
    mem(dp ,0) ;
    mem(MM ,0) ;
}
void isok() {//计算出每行所有可行的状态数。
    for (int i = 0 ; i < 1 << m ; i ++ ) {
        if((i & (i << 1))|| (i & (i << 2)))continue ;
        int tt = i ;
        int nn = 0 ;
        while(tt) {
            nn += tt % 2 ;
            tt /= 2 ;
        }
        Count[top] = nn ;
        st[top ++ ] = i ;
    }
}
int main() {

    cin >> n >> m ;
    init() ;
    for (int i = 1 ; i <= n ; i ++ ) {
        scanf("%s",Map[i]) ;
        for (int j = 0 ; j < m ; j ++ ) {
            if(Map[i][j] == 'H')MM[i] += (1 << j) ;
        }
    }
    isok() ;
    //第一行
    int ans = 0 ;
    for (int i = 0 ; i < top ; i ++ ) {
        if(st[i] & MM[1]) continue ;
        dp[1][0][i] = Count[i] ;//第1行所有可行的状态。
        ans = max(ans ,dp[1][0][i]) ;
    }
    for (int i = 2 ; i <= n ; i ++ ) { //当前行
        for (int j = 0 ; j < top ; j ++ ) { //上一状态
            for (int k = 0 ; k < top ; k ++ ) { //当前状态
                if(st[k] & MM[i] || st[j] & st[k] || MM[i - 1] & st[j])continue ;
                for (int l = 0 ; l < top ; l ++ ) { //上上状态
                    if(st[j] & st[l] || st[l] & st[k] || st[l] & MM[i - 2] || !dp[i - 1][l][j])continue ;
                    dp[i][j][k] = max(dp[i][j][k] , dp[i - 1][l][j] + Count[k]) ;
                    ans = max(ans , dp[i][j][k]) ;
                }
            }
        }
    }
    printf("%d\n",ans) ;
    return 0 ;
}

HDU 3853 求期望 ——8.16

http://www.cnblogs.com/kuangbin/archive/2012/10/02/2710606.html

这个博客不错,总结的很好。

求概率一般是从前往后推,求期望一般是从后往前推。

这道题加了优化300MS,不加优化4000MS,差点超时。


#define N 1005
int n , m ;
double dp[N][N] ;
struct M{
    double p[3] ;
}MM[N][N] ;

int main() {
    while(cin >> n >> m){
        for (int i = 1 ; i <= n ; i ++ ){
            for (int j = 1 ; j <= m ; j ++ ){
                dp[i][j] = 0 ;
                for (int k = 0 ; k < 3 ; k ++ ){
                    RD(MM[i][j].p[k]) ;
                }
            }
        }
        for (int i = n ; i >= 1 ; i -- ){
            for (int j = m ; j >= 1 ; j -- ){
                if(i == n && j == m)continue ;
                double xx = 1 - MM[i][j].p[0] ;
                if(xx < 1e-5)continue ;
                dp[i][j] = dp[i + 1][j] * ( MM[i][j].p[2] / xx ) + dp[i][j + 1] * ( MM[i][j].p[1] / xx ) + 2 / xx ;
            }
        }
        printf("%.3f\n",dp[1][1]) ;
    }
    return 0 ;
}

POJ 2096 求期望——8.16

求期望一道比较简单的题。用dp[i][j]表示i个系统,j个bug是的期望。

那么dp[i][j] 可以由下面几个状态得到

dp[i + 1][j] *j * 1.0 / n * (s - i) * 1.0 / s ,表示下个bug是相同的bug,但是是属于不同系统的,则概率就是(s - i) / s * j / n ;

dp[i][j + 1] * (n - j) * 1.0 / n * i * 1.0 / s) ,表示下个bug是不相同的bug,但是属于同一系统,概率为i / s * (n - j) / n ;

dp[i + 1][j + 1] * (n - j) * 1.0 / n * (s - i) * 1.0 / s,表示下个bug不相同,也不属于同一系统,概率为(s - i) / s * (n - j) / n ;

dp[i][j] * i / s * j / n ,表示下个bug出现过,也属于同一系统,概率为i / s * j / n ;

由dp[s][n] = 0 ,推出dp[0][0]即可。

#define N 1005
double dp[N][N] ;//dp[i][j] . i means s systems , j means n bugs .
int main() {
    int n , s ;
    while(cin >> n >> s ) {
        dp[s][n] = 0 ;
        for (int i = s ; i >= 0 ; i -- ) {
            for (int j = n ; j >= 0 ; j -- ) {
                if(i == s && j == n)continue ;
                dp[i][j] = //dp[i][j] * (double)(i * 1.0 / s * j * 1.0 / n) +
                    ( dp[i + 1][j] * (double)(j * 1.0 / n * (s - i) * 1.0 / s) +
                      dp[i + 1][j + 1] * (double)((n - j) * 1.0 / n * (s - i) * 1.0 / s) +
                      dp[i][j + 1] * (double)((n - j) * 1.0 / n * i * 1.0 / s) + 1 ) / (1 - i * 1.0 / s * j * 1.0 / n );
//                cout << dp[i][j] << endl;
            }
        }
        printf("%.4f\n",dp[0][0]) ;
    }
    return 0 ;
}

POJ 3254 状压DP ——8.16

类似炮兵阵地,不过更简单,这道题的攻击范围就上下左右四个格子,所以只需开二维的dp[][]即可。

dp[i][j]表示状态i ,当前在j行的可行总和。

这里因为求的不是最大放置的个数,所以转移方程有点不同,但是我觉得比那个更简单,dp[i][j] = dp[i][j] + dp[k][j - 1] .

int dp[1111][13] ;
int Map[20][20] ;
int Cmap[13] ;
int n , m ;
int fk[1111] ;
int nfk[1111] ;
int nk ;
ll c[1111] ;
int ok(int x) {
    if(x & (x << 1)) return 0 ;
    if(x & (x >> 1)) return 0 ;
    return 1 ;
}
void get_statu() {
    for (int i = 0 ; i < 1 << m ; i ++ ) {
        if(ok(i)) {
            int t = i ;
            int nn = 0 ;
            while(t) {
                nn += t % 2 ;
                t /= 2 ;
            }
            fk[nk] = i ;
            nfk[nk ++] = nn ;
        }
    }
}
void init() {
    mem(Cmap ,0) ;
    mem(fk ,0) ;
    mem(nfk ,0) ;
    nk = 0 ;
    mem(c ,0) ;
}

int main() {
    while(cin >> n >> m) {
        init() ;
        get_statu() ;
        for (int i = 1 ; i <= n ; i ++ ) {
            for (int j = 1 ; j <= m ; j ++ ) {
                cin >> Map[i][j] ;
                if(!Map[i][j])Cmap[i] += 1 << j - 1 ;
            }
        }
        for (int i = 0 ; i < nk ; i ++ ) {
            if((fk[i] & Cmap[1]))continue ;
            dp[i][1] = 1 ;
        }
        for (int i = 2 ; i <= n ; i ++ ) { //当前行
            for (int j = 0 ; j < nk ; j ++ ) { //上一状态
                if(fk[j] & Cmap[i - 1])continue ;
                for (int k = 0 ; k < nk ; k ++ ) { //当前状态
                    if(fk[k] & Cmap[i])continue ;
                    if(fk[j] & fk[k])continue ;
                    dp[k][i] = (dp[k][i] + dp[j][i - 1]) % 100000000 ;
                }
            }
        }
        ll ans = 0 ;
        for (int i = 0 ; i < nk ; i ++ ){
            ans = (ans + dp[i][n]) % 100000000 ;
        }
        cout << ans << endl ;
    }
    return 0 ;
}


HDU 4540 -8.20

水题,二维DP。

dp[i][j]表示i时间,打j这个老鼠。

int n , m ;
int M[22][22] ;
int dp[22][11] ;
int main() {
    while(cin >> n >> m) {
        mem(dp ,0) ;
        for (int i = 1 ; i <= n ; i ++ ) {
            for (int j = 1 ; j <= m ; j ++ ) {
                cin >> M[i][j] ;
            }
        }
        for (int i = 0 ; i <= n ; i ++ ) {
            for (int j = 0 ; j <= m ; j ++ ) {
                dp[i][j] = inf ;
            }
        }
        for (int i = 1 ; i <= m ; i ++ ) {
            dp[1][i] = 0 ;
        }
        for (int i = 2 ; i <= n ; i ++ ) {
            for (int j = 1 ; j <= m ; j ++ ) {
                for (int k = 1 ; k <= m ; k ++ ) {
                    dp[i][k] = min(dp[i][k] , dp[i - 1][j] + abs(M[i - 1][j] - M[i][k])) ;
                }
            }
        }
        int ans = inf ;
        for (int i = 1 ; i <= m ; i ++ ) {
            ans = min(ans , dp[n][i]) ;
        }
        cout << ans << endl;
    }
    return 0 ;
}


POJ 1159 LCS——09.14

最长公共子序列。

这道题是求加多少个字符使得他成为回文。

我们就可以求他和逆串的最长公共子序列,然后用长度减去他。就是需要加的最少的数量。

贴个LCS的模版。

short dp[2][5005] ;
string x , y ;
int main() {
    int n ;
    while(cin >> n){
        cin >> x ;
        y = x ;
        reverse(y.begin() , y.end()) ;
        mem(dp ,0) ;
        for (int i = 1 ; i <= n ; i ++ ){
            for (int j = 1 ; j <= n ; j ++ ){
                if(x[i - 1] == y[j - 1]){
                    dp[i % 2][j] = dp[(i - 1) % 2][j - 1] + 1 ;
                }
                else {
                    dp[i % 2][j] = max(dp[i % 2][j] , max(dp[(i - 1) % 2][j] , dp[i % 2][j - 1])) ;
                }
            }
        }
        cout << n - dp[n % 2][n] << endl;
    }
    return 0 ;
}



  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值