好久没写题了,今晚即兴做了一场。出题3/4,记录下。
比赛题目链接:
https://hihocoder.com/contest/offers49/problems
相似颜色
题意:
给你一串字符串#abcdef,让你求字符串#rrggbb,使得 (ab−rr)2+(cd−gg)2+(ef−bb)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 个数中取出个数,使得任意两个数相减之差是 k k 的倍数,问总共有多少种取法,答案对取模。
思路:
- 任意2数满足差是k的倍数的集合,一定是以一个数为首项的公差为k的等差数列的子集合,所以我们通过枚举首项,找出所有集合。
- 基于1,本题做法如下:枚举一个没用过的数为基准 x x ,扫其他数累计符合条件(即 (yi−x)%k==0 ( y i − x ) % k == 0 )的个数 sum s u m , sum s u m 如果大于等于 m m ,那么答案累加上组合数 C(sum,m) C ( s u m , m )
- 注意取模溢出。
代码:
#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
行列的矩阵
mp[][]
m
p
[
]
[
]
,从左上角出发,每次只能右移或下移,求到达右下角时,最少代价。
代价1:所经过点的值
mp[i][j]
m
p
[
i
]
[
j
]
。
代价2:更改方向(
2k−1
2
k
−
1
,其中
k
k
为当前更改方向的次数)。
思路:
- 本题比常规搜索/DP题多了层套路:更改方向的代价
- 由于和
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) 表示从左边状态递推到点,当左边是从左边(0)继承,则无需付出代价;当左边是从上方(1)继承,则需要付出改变方向的代价,注意此时左边的状态是 k−1 k − 1 的状态。
dp[i][j][0][k]=min(dp[i][j][0][k],min(dp[i][j−1][0][k],dp[i][j−1][1][k−1]+2k−1) 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[i−1][j][1][k],dp[i−1][j][0][k−1]+2k−1) 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; }