Codeforces Round #642 (Div. 3)
D. Constructing the Array
题意: 一个全零序列,每次操作选择最长靠左的子段[l,r],将a[(l+r)/2]赋值,然后分成2个子段,求最终序列。
题解: 模拟。直接使用优先队列进行模拟。
代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
int const MAXN = 2e5 + 10;
int n, m, T, res[MAXN];
struct Node {
int len, left;
bool operator<(const Node &w) const{
if (len != w.len) return len < w.len;
else return left > w.left;
}
};
priority_queue<Node> q;
int main() {
ios_base::sync_with_stdio(false);
cin.tie(NULL);
cin >> T;
while(T--) {
while(q.size()) q.pop();
cin >> n;
for (int i = 1; i <= n; ++i) res[i] = 0;
q.push({n, 1});
int idx = 0;
while(q.size()) {
auto t = q.top();
q.pop();
int l = t.left, r = l + t.len - 1;
int mid = 0;
if ((r - l + 1) & 1) mid = (l + r) / 2;
else mid = (l + r - 1) / 2;
res[mid] = ++idx;
if (mid - l > 0) q.push({mid - l, l});
if (r - mid > 0) q.push({r - mid, mid + 1});
}
for (int i = 1; i <= n; ++i) cout << res[i] << " ";
cout << endl;
}
return 0;
}
E. K-periodic Garland
题意: 给一个长度为n的01字符串和一个整数k,每次操作可以将字符串中0变成1或者1变成0,代价是1。问最少多少次操作之后,可以将字符串中最近1的位置差恰好全部变成k。
题解: 状态机dp。对于每一位来说,只有2种状态,要不然为1,要不然为0,考虑状态机dp处理。
状态定义:
f [ i ] [ 0 ] f[i][0] f[i][0] : 前i个数据合法且第i个数据为0的最小改变次数
f [ i ] [ 1 ] f[i][1] f[i][1] : 前i个数据合法且第i个数据为1的最小改变次数
状态转移:
f [ i ] [ 0 ] = m i n ( f [ i − 1 ] [ 0 ] , f [ i − 1 ] [ 1 ] ) + ( s [ i ] = = ′ 1 ′ ) ; f[i][0] = min(f[i - 1][0], f[i - 1][1]) + (s[i] == '1'); f[i][0]=min(f[i−1][0],f[i−1][1])+(s[i]==′1′);
f [ i ] [ 1 ] = m i n ( f [ i − k ] [ 1 ] + s u m [ i − 1 ] − s u m [ i − k ] , s u m [ i − 1 ] ) + ( s [ i ] = = ′ 0 ′ ) ; f[i][1] = min(f[i - k][1] + sum[i - 1] - sum[i - k], sum[i - 1]) + (s[i] == '0'); f[i][1]=min(f[i−k][1]+sum[i−1]−sum[i−k],sum[i−1])+(s[i]==′0′);
s u m [ i ] sum[i] sum[i]维护到第i个时1的数目。
其中 f [ i ] [ 0 ] f[i][0] f[i][0]比较简单,不解释; f [ i ] [ 1 ] f[i][1] f[i][1]有2种选择,要不然从上一个1转移来,那么在 i − k ∼ i i - k \sim i i−k∼i这段内的1就必须改为0,因此需要修改 s u m [ i − 1 ] − s u m [ i − k ] sum[i - 1] - sum[i - k] sum[i−1]−sum[i−k]次,要不然当前这个1就是第一个1,那么前面的1就必须全部改掉。
代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
int const MAXN = 1e6 + 10;
int n, m, T, k, f[MAXN][2], sum[MAXN];
string s;
int main() {
ios_base::sync_with_stdio(false);
cin.tie(NULL);
cin >> T;
while(T--) {
cin >> n >> k;
for (int i = 0; i <= n; ++i) f[i][0] = f[i][1] = 0;
cin >> s;
s = " " + s;
for (int i = 1; i <= n; ++i) sum[i] = sum[i - 1] + (s[i] == '1');
for (int i = 1; i <= n; ++i) {
f[i][0] = min(f[i - 1][0], f[i - 1][1]) + (s[i] == '1');
f[i][1] = min(f[max(i - k, 0)][1] + sum[i - 1] - sum[max(i - k, 0)], sum[i - 1]) + (s[i] == '0');
}
cout << min(f[n][0], f[n][1]) << endl;
}
return 0;
}
F. Decreasing Heights
题意: 给定一个n∗m的地图,每个格子有初始高度。只能向右或向下移动,且要求第i+1步的高度必须比第i步恰好高1。每次操作可以使得任意一个格子的高度减1。问最少需要几次操作,使得地图中存在由(1,1)到(n,m)的路径。
题解: 从(1, 1)到(i, j)一定存在一个点,这个点的高度不需要减一(因为大家如果都减一,那么等价于大家都不减一,所以至少有一个点不需要减一)。那么去枚举这个点为(x, y),则就能够推导出(1, 1)点的高度,然后由(1, 1)点的高度去推导出所以点的高度。所有枚举的(x, y)的操作次数取个min即可。
代码:
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int INF = 1e18;
const int MAXN = 100 + 10;
int Map[MAXN][MAXN], dp[MAXN][MAXN];
int n, m;
int solve(int begin) {
//对于每一个不同的起点值,都要初始化dp数组
//注意从0开始初始化,否则更新dp[2][1]与dp[1][2]时会出错
for (int i = 0; i <= n; i++) {
for (int j = 0; j <= m; j++) {
dp[i][j] = INF;
}
}
dp[1][1] = Map[1][1] - begin;
//起点格子的花销
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
if (i == 1 && j == 1) continue;
// dp[1][1]算过了,跳过
int nowh = begin + i + j - 2;
//以标准高度为基准计算(i,j)格的应有高度
if (Map[i][j] < nowh) continue;
//实际高度比应有高度还小,没救了
int cost = Map[i][j] - nowh;
//计算(i,j)格的花费
dp[i][j] = min(dp[i - 1][j] + cost, dp[i][j - 1] + cost);
}
}
return dp[n][m];
}
signed main() {
int t;
cin >> t;
while (t--) {
int ans = INF;
cin >> n >> m;
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
cin >> Map[i][j];
}
}
//尝试通过遍历所有可能的标准高度后计算得出起点值,再从这个起点值开始dp到终点
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
int begin = Map[i][j] - i - j + 2;
if (begin <= Map[1][1]) ans = min(ans, solve(begin));
//否则实际起点值比理论起点值还小,这个起点值是不可用的
}
}
cout << ans << endl;
}
return 0;
}