2024.09.17模拟赛总结
破防了破防了破防了破防了破防了破防了破防了破防了破防了破防了破防了破防了破防了破防了破防了破防了破防了破防了破防了破防了破防了破防了破防了破防了破防了
难度整体波动,局部递增。$ T2 < T4 < T1 < T3 $,我竟然对着 $ T1 $ 想这么久。
$ T1 $
怎么每次 $ rfy $ 模拟赛,$ T1 $ 都这么难。
想了大半场比赛,结果还没做出来,要是换成 $ T2 $ 应该能过。
似乎被样例 $ hack $ 了。
首先考虑 spj 判断答案是否正确的过程,大概是贪心的往后找,直接判,如下:
int x=0,y=0,ba=0;
for(auto c:w) {
while(x<a.size()&&a[x]!=c) ++x;
if(x==a.size()) ba|=1; else ++x;
while(y<b.size()&&b[y]!=c) ++y;
if(y==b.size()) ba|=2; else ++y;
}
if(ba!=3) s.quitf(_wa,"subseq req not filled");
于是不难想到 $ 80 $ 分的做法,设 $ f_{i, j} $ 表示第一个串的 $ x $ 跳到了位置 $ i $,第二个串的 $ y $ 跳到位置 $ j $ 时,最长不坏串长度。
但是这样做是 $ \Theta(26n^2) $ 的,所以会 TLE。
因为答案串长度是比较小的,大概是 $ n/13 $ 级别的,于是考虑交换第二维和答案,贪心的想,设 $ f_{i, j} $ 表示第一个串的 $ x $ 跳到了位置 $ i $,长度为 $ j $ 时,$ y $ 跳到最靠右的位置是多少。
转移时,记录对于每个位置,它后面每个字符出现的第一个位置,边界设 $ nxt_{n + 1, i, 0} = n + 1 $,最后符合即为 $ f_{n + 1, pos} = m + 1 $ 的 $ pos $ 是最小长度,然后在转移同时记录转移过来的位置,不断往前跳,记录答案即可。
#include <bits/stdc++.h>
using namespace std;
const int N = 5010, inf = -0x3f3f3f3f;
int n, m;
string a, b;
struct sb
{
int j, pre, lst_ch;
} f[N][N];
int nxt[N][26][2];
sb Max(sb a, sb b)
{
if (a.j < b.j) return b;
else return a;
}
int main()
{
freopen("string.in", "r", stdin);
freopen("string.out", "w", stdout);
int T;
cin >> T;
while (T -- )
{
cin >> a >> b;
n = a.size(), m = b.size();
a = " " + a, b = " " + b;
for (int i = 0; i < 26; i ++ )
nxt[n + 1][i][0] = n + 1, nxt[n + 2][i][0] = n + 1,
nxt[m + 1][i][1] = m + 1, nxt[m + 2][i][1] = m + 1;
for (int i = n; i; i -- )
{
for (int j = 0; j < 26; j ++ )
nxt[i][j][0] = nxt[i + 1][j][0];
nxt[i][a[i] - 'a'][0] = i;
}
for (int i = m; i; i -- )
{
for (int j = 0; j < 26; j ++ )
nxt[i][j][1] = nxt[i + 1][j][1];
nxt[i][b[i] - 'a'][1] = i;
}
int lx = max(n, m) / 13 + 1;
for (int i = 0; i <= n + 1; i ++ )
for (int j = 0; j <= lx; j ++ )
f[i][j] = {inf, -1, 0};
for (int i = 0; i <= lx; i ++ )
f[0][i].j = 0;
for (int i = 0; i <= n; i ++ )
for (int j = 0; j <= lx; j ++ )
{
if (f[i][j].j == inf) continue;
for (int k = 0; k < 26; k ++ )
f[nxt[i + 1][k][0]][j + 1] = Max(f[nxt[i + 1][k][0]][j + 1], {nxt[f[i][j].j + 1][k][1], i, k});
}
int k = 0;
for (int j = 0; j <= lx; j ++ )
if (f[n + 1][j].j == m + 1)
{
k = j;
break;
}
// cout << k << '\n';
stack<char> stk;
int tmp = n + 1;
while (tmp)
{
stk.push(f[tmp][k].lst_ch + 'a');
tmp = f[tmp][k].pre;
k -- ;
}
while (stk.size()) cout << stk.top(), stk.pop();
cout << '\n';
}
return 0;
}
$ T2 $
一直在想 $ T1 $,结果 $ T2 $ 只打了个暴力,$ T2 $ 相对 $ T1 $ 还是容易的。
设 $ f_{i, x, y} $ 表示前 $ i $ 个数,$ 1 $ 抽出 $ x $ 个,$ 2 $ 抽出 $ y $ 个的车数和最后一辆车的装载量(是 $ pair $ 类型,甚至没有看到 $ a_i = 1/2 $)。
然后转移先不考虑抽出的装载,只考虑在原序列中的装载,抽出的到最后再计算。
则 $ f_{i, x, y} = min(f_{i - 1, x - 1, y}[x > 0], f_{i - 1, x, y - 1}[y > 0], f[i - 1][x][y] + a[i]) $, $ + a_i $ 可以重载运算符一下,$ x, y $ 的范围用前缀和预处理出前 $ i $ 个数中 $ 1/2 $ 的个数。
最后答案在 $ f[n][x][y] $ 统计即可。
注意到会 $ MLE $,所以需要开个滚动数组优化一下空间。
#include <bits/stdc++.h>
#define inf 0x3f3f3f3f
#define need(x) (bool)x.flow + x.cnt_car
#define sum(x) x.cnt_car * w + x.flow
using namespace std;
const int N = 410;
int n, w;
int a[N], sum1[N], sum2[N];
struct sb
{
int cnt_car, flow;
} f[2][N][N];
sb operator + (sb a, int b)
{
return {a.flow + b >= w ? a.cnt_car + 1 : a.cnt_car, a.flow + b == w ? 0 : a.flow + b > w ? b : a.flow + b};
}
sb mn(sb a, sb b)
{
return a.cnt_car >= inf ? b : b.cnt_car >= inf ? a : sum(a) > sum(b) ? b : a;
}
int main()
{
freopen("pack.in", "r", stdin);
freopen("pack.out", "w", stdout);
cin >> n >> w;
for (int i = 1; i <= n; i ++ ) cin >> a[i];
for (int i = 1; i <= n; i ++ )
sum1[i] = sum1[i - 1] + (a[i] == 1),
sum2[i] = sum2[i - 1] + (a[i] == 2);
// for (int i = 1; i <= n; i ++ ) cout << sum1[i] << ' ' << sum2[i] << '\n';
memset(f, 0x3f, sizeof f);
f[0][0][0] = {0, 0};
for (int i = 1; i <= n; i ++ )
{
for (int x = 0; x <= sum1[i]; x ++ )
for (int y = 0; y <= sum2[i]; y ++ )
{
f[i & 1][x][y] = {inf, inf};
if (x && a[i] == 1) f[i & 1][x][y] = mn(f[i & 1][x][y], f[(i - 1) & 1][x - 1][y]);
if (y && a[i] == 2) f[i & 1][x][y] = mn(f[i & 1][x][y], f[(i - 1) & 1][x][y - 1]);
f[i & 1][x][y] = mn(f[i & 1][x][y], f[(i - 1) & 1][x][y] + a[i]);
// cout << (f[(i - 1) & 1][x][y] + a[i]).cnt_car << '\n';
}
}
// cout << inf * 10 << '\n';
// for (int x = 0; x <= sum1[n]; x ++ )
// for (int y = 0; y <= sum2[n]; y ++ )
// cout << f[n & 1][x][y].cnt_car*w+f[n&1][x][y].flow << "\n"[y != sum2[n]];
int res = inf, mi = inf;
for (int x = 0; x <= sum1[n]; x ++ )
for (int y = 0; y <= sum2[n]; y ++ )
{
auto tmp = f[n & 1][x][y];
// cout << tmp.cnt_car << ' ' << tmp.flow << '\n';
int tx = x, ty = y;
while (tx || ty)
{
if (tx && (!ty || tmp.flow + 2 > w)) tx -- , tmp = tmp + 1;
else ty -- , tmp = tmp + 2;
}
// cout << tmp.cnt_car << ' ' << tmp.flow << '\n';
// cout << need(tmp) << '\n';
if (mi > need(tmp))
{
mi = need(tmp);
res = x + y;
}
else if (mi == need(tmp)) res = min(res, x + y);
}
cout << res << '\n';
return 0;
}
$ T3 $
考虑一个特殊性质:每段 $ L/R $ 只有 $ 1 $ 个。
不难想到直接从大到小放即可,然后在每段后面加入 $ L/R $。
将 $ L/R $ 抽象成 $ -/+ $,那么每段就是不能跨越 $ x $ 轴,且要使得值尽量靠近 $ x $ 轴。
不难发现,直接在每段中按整个序列的开头,进行正负交错放是可行的。
#include <bits/stdc++.h>
using namespace std;
const int N = 100010;
int n, a[N];
string s;
char ch[2] = {'L', 'R'};
pair<int, char> ch1[N];
map<char, int> mp = {{'L', 0}, {'R', 1}};
vector<pair<int, char> > pos;
bool cmp(int a, int b)
{
return a > b;
}
int main()
{
freopen("weight.in", "r", stdin);
freopen("weight.out", "w", stdout);
cin >> n;
for (int i = 1; i <= n; i ++ ) cin >> a[i];
cin >> s;
int num = 0;
s = " " + s;
for (int i = 1; i <= n; i ++ )
if (s[i] != s[i - 1])
{
num ++ ;
pos.push_back({i, s[i]});
}
sort(a + 1, a + 1 + n, cmp);
int num1 = num;
for (int i = 0; i < pos.size(); i ++ ) ch1[pos[i].first] = {a[num1 -- ], pos[i].second};
pos.push_back({n + 1, 'a'});
int tmp = 0;
cout << ch1[pos[0].first].first << ' ' << ch1[pos[0].first].second << '\n';
// cout << pos[0].second << ' ';
for (int i = 1; i < (int)pos.size(); i ++ )
{
int lst = pos[i - 1].first, nw = pos[i].first;
for (int j = lst + 1; j < nw; j ++ )
tmp ^= 1, cout << a[ ++ num] << ' ' << ch[(mp[pos[0].second] + tmp) & 1] << '\n';
// cout << pos[i].second << '\n';
if (i != pos.size() - 1) cout << ch1[pos[i].first].first << ' ' << ch1[pos[i].first].second << '\n';
}
return 0;
}
$ T4 $
考虑根号分治,设置一个阈值 $ t = \sqrt n $,对于出现次数小于 $ t $ 的,直接暴力即可;对于大于的,使用树状数组。注意到一个条件:当一个颜色 $ col $ 满足其是区间 $ [j, i] $ 的众数时,一定有 $ (sum[i][col] - sum[j - 1][col]) \times 2 > i - j + 1 $,用树状数组维护即可,因为会有负数,所以加上偏移量 $ n $。
#include <bits/stdc++.h>
#define int long long
#define lowbit(i) (i & -i)
using namespace std;
const int N = 400010;
int n;
int a[N], b[N];
map<int, int> mp;
int tr[N];
bool st[N];
int cnt, num[N], sum[N];
void add(int x, int v)
{
for (int i = x; i <= n * 2; i += lowbit(i)) tr[i] += v;
}
int query(int x)
{
int res = 0;
for (int i = x; i; i -= lowbit(i)) res += tr[i];
return res;
}
signed main()
{
freopen("sequence.in", "r", stdin);
freopen("sequence.out", "w", stdout);
cin >> n;
for (int i = 1; i <= n; i ++ ) scanf("%lld", a + i);
int t = sqrt(n) * 2, res = 0;
for (int i = 1; i <= n; i ++ ) b[i] = a[i];
sort(b + 1, b + n + 1);
for (int i = 1; i <= n; i ++ )
if (!mp.count(b[i])) mp[b[i]] = ++ cnt;
for (int i = 1; i <= n; i ++ )
a[i] = mp[a[i]], num[a[i]] ++ ;
for (int i = 1; i <= cnt; i ++ )
{
if (num[i] <= t) continue;
add(n + 1, 1);
st[i] = 1;
for (int j = 1; j <= n; j ++ ) sum[j] = 0;
for (int j = 1; j <= n; j ++ )
{
sum[j] = sum[j - 1] + (bool)(a[j] == i);
res += query(sum[j] * 2 - j + n);
add(sum[j] * 2 - j + n + 1, 1);
}
add(n + 1, -1);
for (int j = 1; j <= n; j ++ )
add(sum[j] * 2 - j + n + 1, -1);
}
// cout << res << '\n';
for (int i = 1; i <= n; i ++ ) num[i] = 0;
for (int i = 1; i <= n; i ++ )
{
int mx = 0, col = 0;
for (int j = i; j <= min(n, i + 2 * t - 1); j ++ )
{
num[a[j]] ++ ;
if (num[a[j]] > mx)
{
mx = num[a[j]];
col = a[j];
}
if (j - i + 1 < 2 * mx && !st[col]) res ++ ;
}
for (int j = i; j <= i + 2 * t - 1; j ++ )
num[a[j]] = 0;
}
cout << res << '\n';
return 0;
}
本文来自博客园,作者:爱朝比奈まふゆ的MafuyuQWQ。 转载请注明原文链接:https://www.cnblogs.com/MafuyuQWQ/p/18417083

浙公网安备 33010602011771号