几个DP



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
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值