HihoCoder 编程练习赛49

好久没写题了,今晚即兴做了一场。出题3/4,记录下。

比赛题目链接:

https://hihocoder.com/contest/offers49/problems

相似颜色

题意:

给你一串字符串#abcdef,让你求字符串#rrggbb,使得 (abrr)2+(cdgg)2+(efbb)2 ( a b − r r ) 2 + ( c d − g g ) 2 + ( e f − b b ) 2 最小,每两个字母代表的是十六进制数。最后输出#rgb

思路:

枚举构造十六进制数:高位(a、b、c),使其-1/+0/+1,低位和高位取一样得值。使构造出来使得数与原数相减差最小,即为答案。模拟转为10进制比较,注意边界。

代码:
#include <bits/stdc++.h>
using namespace std;
int getnum(char a, char b) {
    int sum = 0;
    if(isalpha(a)) {
        sum += a - 'a' + 10;
    } else {
        sum += a - '0';
    }
    if(isalpha(b)) {
        sum = sum * 16 + b - 'a' + 10;
    } else {
        sum = sum * 16 + b - '0';
    }
    return sum;
}
char solve(char x, int xy) {
    char ans = x;
    int cmp = 0x3f3f3f3f;
    for(int i = -1; i <= 1; ++ i) {
        x += i;
        if(!isalnum(x)) {
            x -= i;
            continue;
        }
        int num = getnum(x, x);
        if(abs(num - xy) < cmp) {
            cmp = abs(num - xy);
            ans = x;
        }
        x -= i;
    }
    return ans;
}
int main() {
    string s;
    cin >> s;
    string ans = "#";
    ans += solve(s[1], getnum(s[1], s[2]));
    ans += solve(s[3], getnum(s[3], s[4]));
    ans += solve(s[5], getnum(s[5], s[6]));
    cout << ans << endl;
}

挑选子集

题意:

n n 个数中取出m个数,使得任意两个数相减之差是 k k 的倍数,问总共有多少种取法,答案对1e9+9取模。

思路:
  1. 任意2数满足差是k的倍数的集合,一定是以一个数为首项的公差为k的等差数列的子集合,所以我们通过枚举首项,找出所有集合。
  2. 基于1,本题做法如下:枚举一个没用过的数为基准 x x ,扫其他数yi累计符合条件(即 (yix)%k==0 ( y i − x ) % k == 0 )的个数 sum s u m sum s u m 如果大于等于 m m ,那么答案ans累加上组合数 C(sum,m) C ( s u m , m )
  3. 注意取模溢出。
代码:
#include <bits/stdc++.h>
using namespace std;
int num[110];
bool vis[110];
int C[110][110];
const int mod = 1e9 + 9;
void init() {
    C[0][0] = 1;
    for(int i = 1; i < 110; ++ i) {
        C[i][0] = 1;
        for(int j = 0; j < 110; ++ j) {
            C[i][j] = (1LL * C[i - 1][j - 1] + C[i - 1][j]) % mod;
        }
    }
}
int main() {
    init();
    int n, m, k;
    scanf("%d%d%d", &n, &m, &k);
    for(int i = 0; i < n; ++ i) {
        scanf("%d", num + i);
    }
    int ans = 0;
    for(int i = 0; i < n; ++ i) {
        if(vis[num[i]] == true) {
            continue;
        }
        int sum = 1;
        for(int j = i + 1; j < n; ++ j) {
            if((num[j] - num[i]) % k == 0) {
                vis[num[j]] = true;
                sum++;
            }
        }
        if(sum >= m) {
            ans = (1LL * ans + C[sum][m]) % mod;
        }
    }
    cout << ans << endl;
}

矩阵迷宫

题意:

给定一个 n n n列的矩阵 mp[][] m p [ ] [ ] ,从左上角出发,每次只能右移或下移,求到达右下角时,最少代价。
代价1:所经过点的值 mp[i][j] m p [ i ] [ j ]
代价2:更改方向( 2k1 2 k − 1 ,其中 k k 为当前更改方向的次数)。

思路:

  1. 本题比常规搜索/DP题多了层套路:更改方向的代价
  2. 由于n mp[][] m p [ ] [ ] 都小于 100 100 ,极限数据为: 100 100 100 100 列全为 100 100 的矩阵,此时代价为 100×199+1<20000<215 100 × 199 + 1 < 20000 < 2 15 ,因此,这题顶多更改15次方向。

    • 通过2,很好想到用动态规划来做。用 dp[x][y][pos][k] d p [ x ] [ y ] [ p o s ] [ k ] 表示到达点 (x,y) ( x , y ) 时通过 pos p o s 方向( pos==0 p o s == 0 代表从左, pos==1 p o s == 1 代表从上)转移过来的,当前已经进行了 k k 次方向变化。
    • 递推方程如下:
      1) 表示从左边状态递推到点(i,j),当左边是从左边(0)继承,则无需付出代价;当左边是从上方(1)继承,则需要付出改变方向的代价,注意此时左边的状态是 k1 k − 1 的状态。
      dp[i][j][0][k]=min(dp[i][j][0][k],min(dp[i][j1][0][k],dp[i][j1][1][k1]+2k1) d p [ i ] [ j ] [ 0 ] [ k ] = m i n ( d p [ i ] [ j ] [ 0 ] [ k ] , m i n ( d p [ i ] [ j − 1 ] [ 0 ] [ k ] , d p [ i ] [ j − 1 ] [ 1 ] [ k − 1 ] + 2 k − 1 )
      2)同理,若是从上方递归到点 (i,j) ( i , j ) ,递推方程为: dp[i][j][1][k]=min(dp[i][j][1][k],min(dp[i1][j][1][k],dp[i1][j][0][k1]+2k1) d p [ i ] [ j ] [ 1 ] [ k ] = m i n ( d p [ i ] [ j ] [ 1 ] [ k ] , m i n ( d p [ i − 1 ] [ j ] [ 1 ] [ k ] , d p [ i − 1 ] [ j ] [ 0 ] [ k − 1 ] + 2 k − 1 )
    • 代码:
      #include <bits/stdc++.h>
      #define min3(x, y, z) min(x, min(y, z))
      using namespace std;
      int mp[110][110];
      int dp[110][110][2][20];  //x, y, 0left 1up, k次
      int main() {
          int n;
          scanf("%d", &n);
          for(int i = 1; i <= n; ++ i) {
              for(int j = 1; j <= n; ++ j) {
                  scanf("%d", &mp[i][j]);
              }
          }
          memset(dp, 0x3f3f3f3f, sizeof(dp));
          dp[1][1][0][0] = dp[1][1][1][0] = mp[1][1];
          for(int i = 2; i <= n; ++ i) {
              dp[1][i][0][0] = dp[1][i - 1][0][0] + mp[1][i];
              dp[i][1][1][0] = dp[i - 1][1][1][0] + mp[i][1];
          }
          for(int k = 1; k <= 15; ++ k) {
              for(int i = 2; i <= n; ++ i) {
                  for(int j = 2; j <= n; ++ j) {
                      if(k >= i || k >= j) continue;  //小剪枝。抵达i行最多只能转i - 1次方向,j列同理
                      dp[i][j][0][k] = min3(dp[i][j][0][k], dp[i][j - 1][0][k] + mp[i][j], dp[i][j - 1][1][k - 1] + (1 << k - 1) + mp[i][j]);
                      dp[i][j][1][k] = min3(dp[i][j][1][k], dp[i - 1][j][1][k] + mp[i][j], dp[i - 1][j][0][k - 1] + (1 << k - 1) + mp[i][j]);
                  }
              }
          }
          int ans = 0x3f3f3f3f;
          for(int k = 0; k <= 15; ++ k) {
              ans = min3(ans, dp[n][n][1][k], dp[n][n][0][k]);
          }
          cout << ans << endl;
      }
      
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值