牛客周赛 Round 102题解
D、小红的华撃串
题意:
将给定的01串通过翻转某些字符,将字符串变为恰好包含三个“01”或“10”子串,且翻转次数最小。
思路:
可知最终的字符串一定形如 0101或者 1010。(每个字符可以有多个,例如00001000011)。
故考虑dp
dp[i][j]表示第i个序列分到第j块,最终的字符串形如0101所需要的最小翻转次数
dp2[i][j]表示第i个序列分到第j块,最终的字符串形如1010所需要的最小翻转次数
又或者:
使用三维动态规划数组 dp[i][j][k],其中:
i:处理到第 i 个字符(0-indexed)。
j:当前段数(1到4)。
k:当前字符是0或1。
状态值表示达到该状态的最小翻转次数。
代码
#include<bits/stdc++.h>
#define ll long long
#define ce cerr
#define ull unsigned long long
#define lll __int128
using namespace std;
const int inf = 0x3f3f3f3f;
const ll iinf = 1e18;
//cin.ignore(std::numeric_limits< streamsize >::max(), '\n');
int t;
void solve() {
int n;
cin >> n;
string s;
cin >> s;
s = ' ' + s;
vector<vector<int> > dp (n + 1, vector<int> (4, inf));
vector<vector<int> > dp2 (n + 1, vector<int> (4, inf));
dp[0][0] = 0;
dp2[0][0] = 0;
for (int i = 1; i <= n; ++i) {
if (s[i] == '1') {
dp[i][0] = dp[i - 1][0];
if (i >= 2) dp[i][1] = min (dp[i - 1][1], dp[i - 1][0]) + 1;
if (i >= 3) dp[i][2] = min (dp[i - 1][2], dp[i - 1][1]);
if (i >= 4) dp[i][3] = min (dp[i - 1][3], dp[i - 1][2]) + 1;
}else if (s[i] == '0') {
dp[i][0] = dp[i - 1][0] + 1;
if (i >= 2) dp[i][1] = min (dp[i - 1][1], dp[i - 1][0]);
if (i >= 3) dp[i][2] = min (dp[i - 1][1], dp[i - 1][2]) + 1;
if (i >= 4) dp[i][3] = min (dp[i - 1][2], dp[i - 1][3]);
}
}
for (int i = 1; i <= n; ++i) {
if (s[i] == '0') {
dp2[i][0] = dp2[i - 1][0];
if (i >= 2) dp2[i][1] = min (dp2[i - 1][1], dp2[i - 1][0]) + 1;
if (i >= 3) dp2[i][2] = min (dp2[i - 1][1], dp2[i - 1][2]);
if (i >= 4) dp2[i][3] = min (dp2[i - 1][2], dp2[i - 1][3]) + 1;
}else if (s[i] == '1') {
dp2[i][0] = dp2[i - 1][0] + 1;
if (i >= 2) dp2[i][1] = min (dp2[i - 1][1], dp2[i - 1][0]);
if (i >= 3) dp2[i][2] = min (dp2[i - 1][2], dp2[i - 1][1]) + 1;
if (i >= 4) dp2[i][3] = min (dp2[i - 1][3], dp2[i - 1][2]);
}
}
cout << min(dp[n][3], dp2[n][3]) << "\n";
}
int main() {
ios::sync_with_stdio (false);
cin.tie(NULL);
cout.tie(NULL);
t = 1;
//cin >> t;
while (t --) {
solve();
}
return 0;
}
E、小红的01串(easy)
题意:
求出使得贡献为k的,序列的最小长度,其中贡献为\(\frac{len \times (len + 1)}{2}\),\(len\)为连续'1'的长度
思路:
长度为value, 贡献为wight, 考虑完全背包
代码
#include<bits/stdc++.h>
#define ll long long
#define ce cerr
#define ull unsigned long long
#define lll __int128
using namespace std;
const int inf = 0x3f3f3f3f;
const ll iinf = 1e18;
const int N = 640 + 10;
int w[N];
int v[N];
//cin.ignore(std::numeric_limits< streamsize >::max(), '\n');
int t;
vector<int> dp (2e5 + 10, inf);
void init () {
for (int i = 1; i < N; ++i) {
w[i] = (i + 1) * i / 2;
v[i] = i;
}
dp[0] = 0;
for (int i = 1; i < N; ++i) {
for (int j = w[i]; j <= 2e5 + 9; ++j) {
dp[j] = min (dp[j], dp[j - w[i]] + (v[i] + 1));
}
}
}
void solve() {
int k;
cin >> k;
cout << dp[k] - 1 << "\n";
}
int main() {
ios::sync_with_stdio (false);
cin.tie(NULL);
cout.tie(NULL);
t = 1;
init ();
cin >> t;
while (t --) {
solve();
}
return 0;
}
F、小红的01串(hard)
题意:
在上一题的基础上,给出构造的序列
思路:
在dp的时候,记录当前wight是从哪里转移过来的
代码
#include<bits/stdc++.h>
#define ll long long
#define ce cerr
#define ull unsigned long long
#define lll __int128
using namespace std;
const int inf = 0x3f3f3f3f;
const ll iinf = 1e18;
const int N = 640 + 10;
const int M = 2e5 + 10;
int w[N];
int v[N];
int pre[M];
//cin.ignore(std::numeric_limits< streamsize >::max(), '\n');
int t;
int k;
vector<int> dp (2e5 + 10, inf);
void init () {
for (int i = 1; i < N; ++i) {
w[i] = (i + 1) * i / 2;
v[i] = i;
}
dp[0] = 0;
for (int i = 1; i < N; ++i) {
for (int j = w[i]; j <= 2e5 + 9; ++j) {
if (dp[j] > dp[j - w[i]] + v[i] + 1) {
dp[j] = dp[j - w[i]] + v[i] + 1;
pre[j] = i;
}
}
}
}
void solve() {
cin >> k;
vector<int> ans;
while (k > 0) {
ans.push_back (pre[k]);
k -= w[pre[k]];
}
int nn = ans.size ();
for (int i = 0; i < nn; ++i) {
if (i == nn - 1) {
for (int j = 1; j <= ans[i]; ++j) {
cout << "1";
}
}else{
for (int j = 1; j <= ans[i]; ++j) {
cout << "1";
}cout << "0";
}
}
cout << "\n";
return ;
}
int main() {
ios::sync_with_stdio (false);
cin.tie(NULL);
cout.tie(NULL);
t = 1;
init ();
cin >> t;
while (t --) {
solve();
}
return 0;
}
G、小红的双排列查询
题意:
小红拿到了一个长为 $ n $ 的数组 \(\{a_1, a_2, \ldots, a_n\}\)。
小芳对双排列很感兴趣,向小红提出了 $ q $ 个问题,每个问题由两个整数 $ l, r (1 \leq l < r \leq n) $ 组成,需要回答:
- 子数组 \(\{a_l, a_{l+1}, \ldots, a_r\}\) 是否为双排列。
小红被难住了,请你帮帮他。
【名词解释】
双排列:长度为 $ 2 \times n $ 的双排列为两个长度为 $ n $ 的排列打乱顺序后得到的数组。
排列:长度为 $ n $ 的排列是由 $ 1, 2, \ldots, n $ 这 $ n $ 个整数、按任意顺序组成的数组(每个整数均恰好出现一次)。例如,\(\{2, 3, 1, 5, 4\}\) 是一个长度为 5 的排列,而 \(\{1, 2, 2\}\) 和 \(\{1, 3, 4\}\) 都不是排列,因为前者存在重复元素,后者包含了超出范围的数。
思路:
\(mul(l, r) == targetmul, sum (l, r) == targetsum\)即可
代码
#include<bits/stdc++.h>
#define ll long long
#define ce cerr
#define ull unsigned long long
#define lll __int128
using namespace std;
const int inf = 0x3f3f3f3f;
const ll iinf = 1e18;
const int mod = 998244353;
const int N = 3e5 + 10;
//cin.ignore(std::numeric_limits< streamsize >::max(), '\n');
int t;
int a[N];
struct node {
ll l, r, mul;
};
node tr[N * 4];
ll S[N];
void pushup (int u) {
tr[u].mul = (tr[u << 1].mul * tr[u << 1 | 1].mul) % mod;
}
void build (int u, int l, int r) {
tr[u] = {l, r};
if (l == r) {
tr[u] = {l, r, a[l]};
}else{
int mid = l + r >> 1;
build (u << 1, l, mid);
build (u << 1 | 1, mid + 1, r);
pushup (u);
}
}
void init () {
S[0] = 1;
for (int i = 1; i < N; ++i) {
S[i] = S[i - 1] * i % mod;
}
}
ll query (int u, int l, int r) {
if (tr[u].l >= l && tr[u].r <= r) {
return tr[u].mul;
}else{
int mid = tr[u].l + tr[u].r >> 1;
ll temp = 1;
if (l <= mid) {
temp = temp * query (u << 1, l, r) % mod;
}
if (r > mid) {
temp = temp * query (u << 1 | 1, l, r) % mod;
}
return temp;
}
}
void solve() {
int n, m;
cin >> n >> m;
vector<ll> b (n + 1);
for (int i = 1; i <= n; ++i) {
cin >> a[i];
b[i] = (b[i - 1] + a[i]) % mod;
}
build (1, 1, n);
while (m --) {
int l, r;
cin >> l >> r;
ll len = r - l + 1;
if (len & 1) {
cout <<"No" <<"\n";
continue;
}
ll sumtarget = (len * len / 4 + len / 2) % mod;
ll multarget = S[len / 2] * S[len / 2] % mod;
if ((b[r] - b[l - 1] + mod) % mod == sumtarget && query (1, l, r) == multarget) {
cout << "Yes" << "\n";
}else{
cout << "No" << "\n";
}
}
}
int main() {
ios::sync_with_stdio (false);
cin.tie(NULL);
cout.tie(NULL);
t = 1;
init ();
//cin >> t;
while (t --) {
solve();
}
return 0;
}