
壁纸链接
题目链接:https://atcoder.jp/contests/abc468/tasks
A - Maximal Value
直接遍历序列中除首尾外的每个位置,判断是否同时满足和。满足条件就将答案加一。
int a[110];
void solved() { int n; cin >> n; int cnt = 0; for(int i = 1; i <= n;i++) cin >> a[i]; for(int i = 2; i < n;i++){ if(a[i] > a[i - 1] && a[i] > a[i + 1]) cnt++; } cout << cnt << endl;}B - Corridor Watch
我们先记录字符串中所有初始为的位置。对于每个位置,将区间内的字符全部标记为,最后统计仍为的位置数量即可。
这里需要先保存所有初始的,再进行区间标记,避免把标记过程中产生的新错误地当成警卫位置继续扩展。
void solved() { int D, M; cin >> M >> D; string s; cin >> s; vector<int> pos; for(int i = 0; i < (int)s.size();i++){ if(s[i] == 'G') pos.push_back(i); } int ans = 0; for(auto it1 : pos){ for(int i = max(0, it1 - D); i <= min((int)s.size() - 1, it1 + D);i++) s[i] = 'G'; } for(auto it : s) if(it == '.') ans++; cout << ans << endl;}C - Between P and Q
由于,可以直接枚举全排列。会将当前排列修改为字典序严格大于它的下一个排列,所以从开始不断调用该函数,直到得到,期间经过的排列数量就是严格位于之间的排列数量。
如果一开始就有,不存在同时满足大于且小于的排列,答案为。否则每生成一个不等于的新排列就将答案加一。
void solved() { int N; cin >> N; vector<int> P(N), Q(N);
for(auto &it : P) cin >> it; for(auto &it : Q) cin >> it; if(P >= Q) {cout << 0 << endl; return;}
int ans = 0; vector<int> cur = P; while(next_permutation(cur.begin(), cur.end())){ if(cur == Q) break; ans++; } cout << ans << endl;}D - Pre-Palindrome
一个字符串能够通过至多修改一个字符变成回文,当且仅当对称位置字符不同的组数不超过。因此我们只需要统计每个子串中有多少组不匹配的对称字符。
定义表示上一种长度下,以为左端点的子串中不匹配的对称字符组数。给子串的左右两端各扩展一个字符后,新的不匹配组数为
。因为只关心这个值是否不超过,所以代码中将大于等于的状态统一截断为。
奇数长度与偶数长度的中心不同,因此分别从长度和长度开始,每次将长度增加。长度为的子串一定满足条件,先将答案初始化为;其余子串在转移后状态不超过时计入答案。
整个转移只依赖前一种长度,所以可以使用滚动数组将空间复杂度降为,时间复杂度为。
void solved() { string s; cin >> s; int n = s.size();
ll ans = n; for(int len : {2, 3}){ vector<int> dp(n + 1, 0), ndp(n + 1, 0);
for(int i = len; i <= n;i += 2){ int substr_cnt = n - i + 1;
for(int j = 0; j < substr_cnt;j++){ int r = j + i - 1; ndp[j] = min(dp[j + 1] + (s[j] != s[r]), 2); if(ndp[j] <= 1) ans++; } dp.swap(ndp); } } cout << ans << endl;}E - Sum of Average
直接枚举所有区间并计算平均值的复杂度至少为,所以这里考虑分别计算每个对答案的贡献。对于一个包含位置的区间,产生的系数为。因此答案可以写成
,其中
。
记调和数。当时,包含的区间只有,所以。从移动到时,新增加的是左端点为的区间,其系数和为;被删除的是右端点为的区间,其系数和为。因此有
。
我们预处理到在模意义下的逆元,再求出所有调和数,就可以在线性时间内递推每个位置的系数并累加贡献。时间复杂度为,空间复杂度为。
vector<ll> get_inverse(ll n, ll MOD){ vector<ll> inverse(n + 1); inverse[1] = 1; for(ll i = 2; i <= n;i++){ inverse[i] = MOD - (MOD / i) * (inverse[MOD % i]) % MOD; } return inverse;}
void solved() { int N; cin >> N; vector<ll> inverse = get_inverse(N, mod);
vector<ll> harmonic(N + 1, 0); for(int i = 1; i <= N;i++){ harmonic[i] = (harmonic[i - 1] + inverse[i]) % mod; }
ll ans = 0, coeffic = harmonic[N]; for(int i = 1; i <= N;i++){ ll val; cin >> val; ans = (ans + val * coeffic) % mod;
if(i < N){ coeffic += harmonic[N - i] - harmonic[i]; coeffic %= mod; if(coeffic < 0) coeffic += mod; } } cout << ans << endl;}F - Chmax
每个元素都会被分配给中的一个变量,只有当它大于对应变量此前的最大值时才会产生一次贡献。等价地说,我们需要将原排列划分为两个子序列,最大化两个子序列中前缀最大值的数量之和。
先考虑原排列中的全局前缀最大值,即的位置。它出现时一定大于当前的值,所以无论分配给哪一个变量都会产生贡献。代码用维护当前全局最大值,并用统计这部分必然能够取得的贡献。
对于不是全局前缀最大值的元素,中至少有一个已经保存了比它更大的全局最大值。若想让该元素产生贡献,只能把它放入另一个较小的变量中,而且后续在这个变量中产生贡献的非前缀最大值必须严格递增。因此这部分最多能选出非前缀最大值序列的一条最长上升子序列。反过来,将所有全局前缀最大值放入一个变量,将这条最长上升子序列放入另一个变量,就能取得这个上界。
所以最终答案等于全局前缀最大值的数量加上其余元素的长度。代码使用数组和二分查找维护,总时间复杂度为,空间复杂度为。
void solved() { int N; cin >> N; int premax = 0, cnt = 0;
vector<int> tails; for(int i = 1; i <= N;i++){ int val; cin >> val; if(premax < val){ premax = val; cnt++; } else{ auto it = lower_bound(tails.begin(), tails.end(), val); if(it == tails.end()){ tails.push_back(val); } else *it = val; } } cout << cnt + (int)tails.size() << endl;}G - Restricted Permutation
我们按照数值从小到大,将依次放入最终排列的各个位置。已经放置后,它们在排列中的最左位置到最右位置构成一个最小覆盖区间。因为区间内共有个位置而已经放置了个数,所以是否连续只需要判断。
定义表示已经放置,对于任意一个固定的长度为的最小覆盖区间,合法放置方案的数量。接下来放置,设新的覆盖区间长度为,转移分为两类:
第一类是把放在原覆盖区间内部的空位,此时覆盖区间长度不变,共有个空位,贡献为。
第二类是把放在新覆盖区间的左端点或右端点。删除这个新端点后,原来的最小覆盖区间长度可以是任意小于的值,左右两种放法产生的贡献为。这个前缀和可以在枚举时同步维护,使每次转移降为。
完成转移后,连续当且仅当。我们根据为或,只保留连续性与其相符的状态。特别地,集合以及完整集合在任何排列中都必然连续,所以或为时答案直接为。
初始状态为,最终只有覆盖整个排列的状态需要计入答案。时间复杂度为,空间复杂度为。
void solved() { int N; cin >> N; string s; cin >> s; // {1} 和 {1,2,...,n} 在任何排列中都一定连续。 if(s[0] == 'x' || s[N - 1] == 'x'){ cout << 0 << endl; return; } /* dp[span]表示已经放置数字 1...placed,它们的最小覆盖区间长度为 span; 对于任意一个固定的、长度为 span 的区间,满足此前条件的放置顺序数量。 */ vector<ll> dp(N + 1, 0), ndp(N + 1, 0); dp[1] = 1; for(int placed = 1; placed < N;placed++){ fill(ndp.begin(), ndp.end(), 0); // prefix = dp[1] + dp[2] + ... + dp[new_span - 1]; ll prefix = 0; for(int new_span = 1; new_span <= N;new_span++){ if(new_span >= placed + 1){
//在原覆盖区域内容有new_span - placed个空位 ll ways = (ll)(new_span - placed) * dp[new_span] % mod; //新位置为新区间的左端点和右端点两种情况,所以乘2 ways = (ways + 2 * prefix) % mod;
//判断连续(不连续)是否满足'o'('x'),满足才更新 bool is_contiguous = (new_span == placed + 1); bool required = (s[placed] == 'o'); if(is_contiguous == required) ndp[new_span] = ways; } prefix += dp[new_span]; if(prefix >= mod) prefix -= mod; } dp.swap(ndp); }
cout << dp[N] << endl;}模板:
#include <bits/stdc++.h>
using namespace std;using ll = long long;using ull = unsigned long long;#define endl '\n'#define mod 998244353typedef pair<ll, ll> pll;typedef pair<int, int> pii;
ll fast_pow(ll a, ll b, ll MOD) { ll ans = 1; while (b) { if (b & 1) ans = (ans * a) % MOD; b >>= 1; a = (a * a) % MOD; } return ans;}
vector<ll> get_inverse(ll n, ll MOD){ vector<ll> inverse(n + 1); inverse[1] = 1; for(ll i = 2; i <= n;i++){ inverse[i] = MOD - (MOD / i) * (inverse[MOD % i]) % MOD; } return inverse;}
void solved() {}
int main() { ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr); int _ = 1; //cin >> _; while (_--) { solved(); } return 0;}