几个DP

来源:互联网 发布:软件开发项目管理流程 编辑:程序博客网 时间:2024/05/16 06:14



Xxx正在玩一款游戏,游戏地图上有N个点,这些点之间有M条边。游戏系统会在一定时间在某点刷新出一定量的怪,系统刷新出来的怪只会存在1秒,下一秒就会消失。如果那个时间xxx正好在那个点,那么xxx可以秒杀这个点上的所有怪。另外,xxx还有B次放大招的机会,每次放大招可以秒杀掉当前所在点及与其相邻点上的所有怪。大招有5秒的冷却时间,也就是说每次放大后要经过5秒钟才能再次放大。Xxx想知道T时间内他最多可以杀掉多少只怪。Xxx可以从任意点开始。系统时间从1开始。
输入的第一行包含一个整数CT(CT<=50), 表示一共有CT组测试数据。
对于每组测试数据,第一行包含5个整数N、M、T、K、B(1 <= N, T <= 50, 0<=M<= (N-1)*N/2, 0<=K<=10000, 0<=B<=5)。N、M、T意义如上所述,K表示有K个系统刷新。
接下来是M行,每行有3个整数u、v、t(1<=u, v<=N, u!=v, 1<=t<=10)表示从u走到v或者从v走到u需要花费t的时间。数据保证没有重边。
然后是K行,每行有3个整数t、p、c(1<=t<=50, 1<=p<=N, 1<=c<=100)表示t时间 在p点刷新出c个怪。
Output

对于每组测试数据,输出在T时间内xxx最多可以杀掉多少只怪

const   int maxn = 58 ;int     cnt[maxn][maxn] ;int     dist[maxn][maxn]  ;int     sum[maxn][maxn] ;int     dp[maxn][maxn][6][6] ;void    init(){        memset(cnt , 0 , sizeof(cnt)) ;        memset(dist , -1 , sizeof(dist)) ;        memset(sum , 0 , sizeof(sum)) ;        memset(dp , -1 , sizeof(dp)) ;}int    checkmax(int &a , int b){       if(a < b) a = b ;}int    N , M , T , B ;int    DP(){       int ans = 0 ;       for(int i = 1 ; i <= N ; i++) dp[0][i][0][5] = 0 ;       for(int ti = 0 ; ti <= T ; ti++){           for(int pi = 1 ; pi <= N ; pi++){               for(int bi = 0 ; bi <= B ; bi++){                   for(int li = 0 ; li <= 5 ; li++){                        if(dp[ti][pi][bi][li] == -1) continue ;                        checkmax(ans , dp[ti][pi][bi][li]) ;                        for(int np = 1 ; np <= N ; np++){                             if(dist[pi][np] != -1 && ti + dist[pi][np] <= T){                                 int nt = ti + dist[pi][np] ;                                 int nl = li + dist[pi][np] ;                                 if(nl > 5) nl = 5 ;                                 checkmax(dp[nt][np][bi][nl] , dp[ti][pi][bi][li] + cnt[np][nt]) ;                                 if(nl == 5 && bi != B)                                    checkmax(dp[nt][np][bi+1][0] , dp[ti][pi][bi][li] + sum[np][nt]) ;                             }                        }                   }               }           }       }       return ans ;}int     main(){        int  cas , i , j , u , v , t , k , p , c ;        cin>>cas ;        while(cas--){              scanf("%d%d%d%d%d" ,&N,&M,&T,&k,&B) ;              init() ;              for(i = 1 ; i <= M ; i++){                   scanf("%d%d%d" ,&u,&v,&t) ;                   dist[u][v] = dist[v][u] = t ;              }              for(i = 1 ; i <= N ; i++) dist[i][i] = 1 ;              while(k--){                   scanf("%d%d%d" ,&t ,&p ,&c) ;                   cnt[p][t] += c ;              }              for(i = 1 ; i <= N ; i++){                  for(j = 1 ; j <= N ; j++){                      if(dist[i][j] != -1){                          for(k = 0 ; k <= T ; k++)                              sum[i][k] += cnt[j][k] ;                      }                  }              }              printf("%d\n" ,DP()) ;        }        return 0 ;}

给n个数,从这n个数中选择i个数,共有c(n , i)种情况,将每种情况中的i个数异或,将这c(n , i)个异或结果求和,就得到第i个输出结果,i属于[1  n]。

求x个数的异或,等于分别对x个数的同一二进制位进行异或,然后加权求和。于是将n个数表示成二进制的形式,对于本题,32位就够。因为,奇数个1的异或 = 1 , 偶数个1的异或 = 0 。 统计每位上1的个数 ,然后对于第j个二进制位,枚举所选中的1的个数,加权求和,即可得结果。将对n个数的处理,转化成对32个位的处理。


typedef long long LL ;const int mod = 1000003 ;int  bit[32] ;LL  C[1001][1001] ;LL  ans[1001] ;void getC(){     C[0][0] = C[1][0] = C[1][1] = 1  ;     for(int i = 2 ; i <= 1000 ; i++){         C[i][0] = C[i][i] = 1  ;         for(int j = 1 ; j < i ; j++){              C[i][j] = (C[i-1][j-1] + C[i-1][j]) % mod ;         }     }}int  main(){     getC() ;     int n , x , i  , j  , k ;     while(cin>>n){          memset(bit , 0 , sizeof(bit)) ;          memset(ans , 0 , sizeof(ans)) ;          for(i = 1 ; i <= n ; i++){               scanf("%d" , &x) ;               for(j = 0 ; j < 32 ; j++){                    if(x & (1<<j)) bit[j]++ ;               }          }          for(k = 1 ; k <= n ; k++){              for(i = 0 ; i < 32 ; i++){                 for(j = 1 ; j <= bit[i] && j <= k ;  j += 2){                      ans[k] += ((C[bit[i]][j] * C[n-bit[i]][k-j]  % mod )* ( (1<<i) % mod ));                      ans[k] %= mod ;                 }              }          }          printf("%I64d" , ans[1]) ;          for(i = 2 ; i <= n ; i++) printf(" %I64d" , ans[i]) ;          puts("") ;     }    return 0 ;}


给定一个大小为n的数组,数组的元素a[i]代表第i天的股票价格。计算在最多允许买卖k次(一买一卖记为一次)的条件下的最大收益。
需要注意的是,你不能同时拥有两份股票。也就是说在下次买入前,你必须把手头上原有的股票先卖掉。

解题思路:

令dp[i][j]表示前i天买卖j次可以获得的最大收入。对于第i天,如果不卖则dp[i][j] = dp[i-1][j];

dp[i][j] = dp[k][j-1] + value[i] - value[k]。

dp[i][j] = max{ dp[i-1][j] ,dp[k][j-1] + value[i] - value[k] },0<k<i;这个地方可以直接优化


const  int  maxn = 1008  ;int    dp[maxn][maxn] ;int    x[maxn] , n ,  k ;int    DP(){       int i , j , mx ;       memset(dp , 0 , sizeof(dp)) ;       for(j = 1 ; j <= k ; j++){           mx = dp[1][j-1] - x[1] ;           for(i = 1 ; i <= n ; i++){                dp[i][j] = max(dp[i-1][j] , x[i] + mx) ;                mx = max(mx , dp[i][j-1] - x[i]) ;           }       }       return  dp[n][k] ;}


密码锁,给初始状态和目标状态。每次可正向或者反向旋转连续的 1 , 2 , 3 位。数字在0 ---9 之间循环,即9+1 -> 0, 0-1 -> 9。问从初始状态到目标状态的最少旋转次数。
dp[ i ][j ][ k ]表示[0 ,i-1]位已经匹配了,第i 的数字是 j ,第 i +1 目前的数字是k ,到目标状态需要的最少旋转次数。显然dp[ len ][ 0 ] [ 0 ] 中的最小值为要求的值,其中j 在[ 0 ,9] ,k在 [ 0 , 9];

int   up[10][10] , down[10][10] ;int  UP(int u , int v){      int s = 0 ;      while(u != v){           s++ ;           u++ ;           if(u == 10) u = 0 ;      }      return s ;}int  DOWN(int u , int v){      int s = 0 ;      while(u != v){           s++ ;           u-- ;           if(u == -1) u = 9 ;      }      return s ;}void  get(){      for(int i = 0 ; i <= 9 ; i++){        for(int j = 0 ; j <= 9 ; j++){            up[i][j] = UP(i , j) ;            down[i][j] = DOWN(i , j) ;         }      }}void   checkmin(int &a , int b){       if(a > b) a = b ;}const  int maxn = 1008 ;char  S[maxn] , T[maxn] ;int   dp[maxn][10][10] ;int   DP(){      int n = strlen(S)  , i , j , k  , t  , jj , kk ;      S[n] = S[n+1] = T[n] = T[n+1] = '0' ;      memset(dp , 63 , sizeof(dp)) ;      dp[0][S[0]-'0'][S[1]-'0'] = 0  ;      for(i = 1 ; i <= n ; i++){          for(j = 0 ; j <= 9 ; j++){              for(k = 0 ; k <= 9 ; k++){                   t = up[j][T[i-1] - '0'] ;                   for(jj = 0 ; jj <= t ; jj++){                       for(kk = 0 ; kk <= jj ; kk++)                          checkmin(dp[i][(k+jj)%10][(S[i+1]-'0'+kk)%10] ,  dp[i-1][j][k] + t) ;                   }                   t = down[j][T[i-1] - '0'] ;                   for(jj = 0 ; jj <= t ; jj++){                       for(kk = 0 ; kk <= jj ; kk++)                          checkmin(dp[i][(k-jj+10)%10][(S[i+1]-'0'+10-kk)%10] ,  dp[i-1][j][k] + t) ;                   }              }          }      }      return dp[n][0][0] ;}int   main(){      get() ;      int i ,  j ;      while(scanf("%s%s" , S ,T) != EOF){           printf("%d\n" ,DP()) ;      }      return 0 ;}

子序列的定义:对于一个序列a=a[1],a[2],......a[n]。则非空序列a'=a[p1],a[p2]......a[pm]为a的一个子序列,其中1<=p1<p2<.....<pm<=n。
例如4,14,2,3和14,1,2,3都为4,13,14,1,2,3的子序列。
对于给出序列a,请输出不同的子序列的个数。

         memset(pos , -1 , sizeof(pos)) ;         dp[0] = 0 ;         for(i = 1 ; i <= n ; i++){             x = getint() ;             if(pos[x] == -1)  dp[i] = (2*dp[i-1] + 1) % mod ;             else   dp[i] = (2*dp[i-1] - dp[pos[x]-1] + mod) % mod ;             pos[x] = i ;         }         cout<<dp[n]<<endl ;

求i在[a,b]与j在[c,d]之间, xor值大于e的 sum += i ^ j;

typedef long long LL ;typedef  pair<LL , LL>  pLL ;const   int  maxn = 65 ;const   LL mod = 1000000007 ;int     bita[maxn] , bitb[maxn] , bitc[maxn] ;void    getbit(int  dig[] , LL x , int &len){        len = 0  ;        while(x){             dig[len++] =  x%2 ;             x /= 2 ;        }}pLL    dp[maxn][2][2][2] ;pLL    DP(int pos , int enda , int endb , int endc){       if(pos < 0){            return endc ? make_pair(0 , 0) : make_pair(1 , 0) ;       }       if(dp[pos][enda][endb][endc].first != -1 &&          dp[pos][enda][endb][endc].second != -1)          return dp[pos][enda][endb][endc] ;       int da = enda ? bita[pos] : 1 ;       int db = endb ? bitb[pos] : 1 ;       int dc = endc ? bitc[pos] : -1 ;       pLL s = make_pair(0 , 0) ;       for(int i = 0 ; i <= da ; i++){           for(int j = 0 ; j <= db ; j++){                int t = i ^ j ;                if(t >= dc){                    pLL k = DP(pos-1 , enda&&i==da , endb&&j==db , endc&&t==dc) ;                    s.first = (s.first + k.first) % mod ;                    s.second = ((s.second + k.second) % mod)  + (1LL<<pos)*t%mod*k.first%mod ;                    s.second %= mod ;                }           }       }       return dp[pos][enda][endb][endc] = s ;}LL   answer(LL a , LL b , LL c){     for(int i = 0 ; i < maxn ; i++)        for(int j = 0 ; j <= 1 ; j++)           for(int k = 0 ; k <= 1 ; k++)              for(int p = 0 ; p <= 1 ; p++)               dp[i][j][k][p] = make_pair(-1 , -1) ;     int la , lb , lc  , len ;     getbit(bita , a , la) ;     getbit(bitb , b , lb) ;     getbit(bitc , c , lc) ;     len = max(la , max(lb , lc)) ;     while(la < len)  bita[la++] = 0 ;     while(lb < len)  bitb[lb++] = 0 ;     while(lc < len)  bitc[lc++] = 0 ;     return DP(len-1 , 1 , 1 , 1).second  ;}int   main(){      int t  , i  , T = 1 ;      LL a , b, c ,  d , e  , s ;      cin>>t  ;      while(t--){           cin>>a>>b>>c>>d>>e ;           s = ((answer(b,d,e) - answer(a-1,d,e) - answer(b,c-1,e) + answer(a-1 , c-1,e)) %mod + mod) % mod ;           printf("Case %d: %I64d\n" , T++ , s) ;      }      return 0 ;}

题目大意:给一定区间[A,B],一串由/,\,-组成的符号串。求满足符号串的数字个数。
/表示数字从左到右递增
\表示数字从左到右递减
-表示数字从左到右相等
例如:
/-\ 可以表示
1221,123455543
数据规模:

A,B<=10^100

const   int  maxn = 101 ;const   int  mod  = 100000000  ;char    str[maxn] ;int     Len ;int     bit[maxn] ;int     dp[maxn][maxn][10] ;inline  int  ok(int i , int u , int v){        if(str[i] == '/') return u < v ;        if(str[i] == '-') return u == v ;        if(str[i] == '\\') return u > v ;}int    DP(int pos , int id , int pre , int islead , int isend){       if(pos == -1)  return id == Len ;       if(!isend && dp[pos][id][pre] != -1) return dp[pos][id][pre] ;       int s = 0 ;       int d = isend ? bit[pos] : 9 ;       for(int i = 0 ; i <= d ; i++){           if(islead) s += DP(pos-1 , id , i , islead&&i==0 , isend&&i==d) ;           else if(id < Len && ok(id , pre , i)) s += DP(pos-1 , id+1 , i , islead , isend&&i==d) ;           else if(id > 0 && ok(id-1 , pre , i)) s += DP(pos-1 , id , i , islead , isend&&i==d) ;           s %= mod ;       }       if(! isend) dp[pos][id][pre] = s ;       return s ;}int    answer(char s[] , int d){       int i = 0 , j , ls = strlen(s)  , len = 0 ;       while(s[i] == '0') i++ ;       for(j = ls-1 ; j >= i ; j--) bit[len++] = s[j] - '0' ;       if(d&&len>0){           for(i = 0 ; i < len ; i++){                if(bit[i]){                      bit[i]-- ;                      break ;                }                else  bit[i] = 9 ;           }       }       return DP(len-1 , 0 , 0 , 1 , 1) ;}int   main(){      char  a[maxn] , b[maxn] ;      while(scanf("%s" , str) != EOF){           Len = strlen(str) ;           scanf("%s%s" , a , b) ;           memset(dp , -1 , sizeof(dp)) ;           printf("%08d\n" , (answer(b , 0) - answer(a , 1) + mod) % mod ) ;      }      return 0 ;}


问从L到R有长度为K的严格最长上升子序列的数字的个数

dp[i][j][k]:严格最长上升子序列的数目。

    i:当前进行到数字的位数,最低位为0。

    j: 状态压缩,10个数字中出现过哪些数字。

    k:当前最长上升子序列的长度。

LL     dp[20][1025][11]  ;int    k ;int    bit[20] ;int   GetLen(int state){      int s = 0 ;      while(state){            if(state & 1) s++ ;            state >>= 1 ;      }      return s ;}int   update(int x , int state){      for(int i = x ; i <= 9 ; i++){           if(state & (1<<i))  return (state - (1<<i)) | (1<<x) ;      }      return state | (1<<x) ;}LL    DP(int pos , int state , int islead , int isend){      if(pos == -1) return GetLen(state) == k ;      if(!isend && dp[pos][state][k] != -1) return dp[pos][state][k] ;      LL s = 0 ;      int d = isend ? bit[pos] : 9 ;      for(int i = 0 ; i <= d ; i++){           s += DP(pos-1 , islead&&i==0 ? 0 : update(i , state) , islead&&i==0 , isend&&i==d) ;      }      if(! isend) dp[pos][state][k] = s ;      return s  ;}LL   answer(LL x){      int len = 0 ;      while(x){           bit[len++] = x % 10 ;           x /= 10 ;      }      return DP(len-1 , 0 , 1 , 1) ;}int   main(){      memset(dp , -1 , sizeof(dp)) ;      LL l , r  ; int  t , T = 1 ;      cin>>t ;      while(t--){           cin>>l>>r>>k  ;           printf("Case #%d: " , T++)  ;           cout<< answer(r) - answer(l-1) << endl ;      }      return 0 ;}

最长回文子串,含环形。
const int maxn = 3001 ;int   s[maxn] , dp[maxn][maxn] ;int   dfs(int l  , int r){      if(l > r) return dp[l][r] = 0 ;      if(dp[l][r] != -1) return dp[l][r] ;      dp[l][r] = max(dfs(l+1 ,r) , dfs(l , r-1)) ;      if(s[l] == s[r]) dp[l][r] = max(dp[l][r] , dfs(l+1,r-1)+1) ;      return dp[l][r] ;}int   main(){      int i  , ans  , n ;      while(cin>>n && n){           for(i = 1 ; i <= n ; i++){               scanf("%d" ,&s[i]) ;               s[n+i] = s[n+n+i] = s[i] ;           }           memset(dp , -1 , sizeof(dp)) ;           ans = 0 ;           for(int i = 1 ; i <= n ; i++)               ans = max(ans , dfs(i , i+2*n-1)) ;           printf("%d\n" , ans) ;      }      return 0 ;}

问从L到R有回文数字的个数

搜索的时候是从高位到低位,所以一旦遇到非0数字,也就确定了数的长度,这样就知道回文串的中心点。


ll dp[20][20][2];int bit[20],num[20];ll dfs(int pos,int m,int s,bool f){    if(!pos) return 1;    if(!f&&dp[pos][m][s]!=-1) return dp[pos][m][s];    ll ans=0;    int e=f?bit[pos]:9;    for(int i=0;i<=e;i++){        if(!s){            num[pos]=i;            ans+=dfs(pos-1,m-!i,s||i,f&&i==e);        }        else{            int t=(m+1)>>1;            bool flag=m&1?pos<t:pos<=t;            if(flag){                if(num[m+1-pos]==i)                    ans+=dfs(pos-1,m,s,f&&i==e);            }            else{                num[pos]=i;                ans+=dfs(pos-1,m,s,f&&i==e);            }        }    }    if(!f) dp[pos][m][s]=ans;    return ans;}ll cal(ll n){    int m=0;    while(n){        bit[++m]=n%10;        n/=10;    }    return dfs(m,m,0,1);}int main(){    int t,ca=0;    ll a,b;    memset(dp,-1,sizeof(dp));    scanf("%d",&t);    while(t--){        scanf("%lld%lld",&a,&b);        if(a>b) swap(a,b);        printf("Case %d: %lld\n",++ca,cal(b)-cal(a-1));    }    return 0;}

1:在区间[x,y]中b进制数,各数位和为m的数的个数

2:在区间[x,y]中b进制数,第k个各数位和为m的数是谁。

int   dp[32][308] ;int   b  , bit[32] , m ;int   DP(int pos , int s , int isend){      if(pos == -1)  return s == m ;      if(!isend && dp[pos][s] != -1) return dp[pos][s] ;      int sum = 0 ;      int d = isend ? bit[pos] : (b-1) ;      for(int i = 0 ; i <= d ; i++){            sum += DP(pos-1 , s+i , isend&&i==d) ;      }      if(! isend) dp[pos][s] = sum ;      return sum ;}int   answer(int x){      int len = 0 ;      while(x){           bit[len++] = x % b ;           x /= b ;      }      return DP(len-1 , 0 , 1) ;}int   main(){      int kind , l , r , k  , T = 1 ;      while(scanf("%d" , &kind) != EOF){           memset(dp , -1 , sizeof(dp)) ;           if(kind == 1){                scanf("%d%d%d%d" ,&l ,&r , &b ,&m) ;                printf("Case %d:\n" , T++) ;                if(l > r) swap(l , r) ;                printf("%d\n" , answer(r) - answer(l-1)) ;           }           else{               scanf("%d%d%d%d%d" ,&l ,&r , &b ,&m , &k) ;               printf("Case %d:\n" , T++) ;               if(l > r) swap(l , r) ;               int low = answer(l-1) ; int high = answer(r) ;               if(high - low < k){                      puts("Could not find the Number!") ; continue  ;               }               int L = l , R = r , M , s ;               while(L <= R){                     M = int(((LL)L + (LL)R)>>1) ;                     if(answer(M)-low >= k) s = M , R = M -1 ;                     else  L = M + 1 ;               }               printf("%d\n" , s) ;           }      }      return 0 ;}




0 0
原创粉丝点击