DP练习

黑书对应题目。

page section no title submit
113 1.5.1 例题1 括号序列 POJ1141
116 1.5.1 例题2 棋盘分割 POJ1191
117 1.5.1 例题3 决斗 Sicily1822
117 1.5.1 例题4 “舞蹈家”怀特先生 ACM-ICPC Live Archive
119 1.5.1 例题5 积木游戏 http://202.120.80.191/problem.php?problemid=1244
123 1.5.2 例题1 方块消除 http://poj.org/problem?id=1390
123 1.5.2 例题2 公路巡逻 http://202.120.80.191/problem.php?problemid=1600
125 1.5.2 例题3 并行期望值 POJ1074
131 1.5.2 例题6 不可分解的编码 http://acmicpc-live-archive.uva.es/nuevoportal/data/problem.php?p=2475
133 1.5.2 例题7 青蛙的烦恼 http://codewaysky.sinaapp.com/problem.php?id=1014
135 1.5.2 例题9 最优排序二叉树 http://judge.noi.cn/problem?id=1059
138 1.5.2 例题10 Bugs公司 POJ1038
139 1.5.2 例题11 迷宫统计 http://uva.onlinejudge.org/index.php?option=com_onlinejudge&Itemid=8&category=70&page=show_problem&problem=1472
142 1.5.2 例题12 贪吃的九头龙 http://judge.noi.cn/problem?id=1043
151 1.5.3 问题2 最长上升子序列问题 http://uva.onlinejudge.org/index.php?option=com_onlinejudge&Itemid=8&category=17&page=show_problem&problem=1475
151 1.5.3 问题3 最优二分检索树 http://uva.onlinejudge.org/index.php?option=com_onlinejudge&Itemid=8&category=15&page=show_problem&problem=1245
152 1.5.3 问题4 任务调度问题 POJ1180
121 1.5.1 1.5.8 艺术馆的火灾 http://221.192.240.23:9088/showproblem?problem_id=1366
144 1.5.2 1.5.10 快乐的蜜月 http://judge.noi.cn/problem?id=1052
145 1.5.2 1.5.12 佳佳的筷子 http://uva.onlinejudge.org/index.php?option=com_onlinejudge&Itemid=8&category=14&page=show_problem&problem=1212
146 1.5.2 1.5.13 偷懒的工人 POJ1337
146 1.5.2 1.5.15 平板涂色 POJ1691
147 1.5.2 1.5.16 道路重建 POJ1947
147 1.5.2 1.5.17 圆和多边形 http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemCode=1679
148 1.5.2 1.5.18 Jimmy落地 POJ1661
148 1.5.2 1.5.19 免费糖果 http://uva.onlinejudge.org/index.php?option=com_onlinejudge&Itemid=8&category=13&page=show_problem&problem=1059
157 1.5.3 1.5.22 回文词 POJ1159
157 1.5.3 1.5.24 邮局 POJ1160
158 1.5.3 1.5.26 奶牛转圈 POJ1946
158 1.5.3 1.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 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#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
#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 ;
}



你可能感兴趣的:(Dynamic,Programming)