1928C - Physical Education Lesson
讨论一下 n n n 出现在队列中的位置是上坡还是下坡,枚举所有 k k k 然后判断一下第 n n n 为是否为 x x x 即可
void solve(int cs) {
int n, x;
cin >> n >> x;
set<int> s;
auto jg = [&](int k) {
if (k & 1 || k == 0) return;
s.insert((k + 2) / 2);
};
for (int i = 1; i * i <= n - x; i++) {
if ((n - x) % i == 0) {
jg(i);
jg((n - x) / i);
}
}
for (int i = 1; i * i <= n + x - 2; i++) {
if ((n + x - 2) % i == 0) {
jg(i);
jg((n + x - 2) / i);
}
}
int ans = 0;
for (auto k : s) {
int r = (n % (2 * k - 2) ? n % (2 * k - 2) : 2 * k - 2);
int t = (r <= k ? r : 2 * k - 2 - r + 2);
ans += (t == x);
}
cout << ans << endl;
}
1921E - Eat the Chip
两个棋子相向而行,必有一刻会到达到一行,而一个棋子想要吃掉另一个棋子,就要晚一步到达第二行。
于是我们可以发现,在A先手的情况下,如果他们两个直接相隔行数为奇数时,那么B会比A晚一步到达同一行,从而B有机会吃掉A,反之则A有机会吃掉B。
我们已经确定了谁吃谁,现在来讨论一下能否吃掉:
如果A吃B,那么A就要往B的方向移动,而B也要远离A,B吃A同理,如果吃方把被吃方逼到墙后追上他,就可以把被吃方吃掉,于是我们可以推出: A 和 B 相隔行数除以 2 ≥ 吃方与墙之间的列数 − 1 A和B相隔行数除以2\geq吃方与墙之间的列数-1 A和B相隔行数除以2≥吃方与墙之间的列数−1 时,吃方就能吃掉被吃方。
最后特判一下吃方能在路上就吃掉被吃方的情况即可
void solve(int cs) {
int n = input(), m = input(), xa = input(), ya = input(), xb = input(), yb = input();
if (xa >= xb) {cout << "Draw\n"; return;}
int d = xb - xa - 1;
if (d & 1) { //h差奇数B追A
if (abs(ya - yb) == 0) {
cout << "Bob\n";
return;
}
if (yb > ya) {
if (d / 2 >= yb - 2) {
cout << "Bob\n";
}
else {
cout << "Draw\n";
}
}
else {
if (d / 2 >= m - yb - 1) {
cout << "Bob\n";
}
else {
cout << "Draw\n";
}
}
}
else { //h差偶数A追B
if (abs(ya - yb) <= 1) {
cout << "Alice\n";
return;
}
if (yb > ya) {
if (d / 2 >= m - ya - 1) {
cout << "Alice\n";
}
else {
cout << "Draw\n";
}
}
else {
if (d / 2 >= ya - 2) {
cout << "Alice\n";
}
else {
cout << "Draw\n";
}
}
}
}
1920C - Partitioning the Array
两个不同的数模他们差的绝对值得到的结果相等
故我们枚举所有
k
k
k ,求取
m
=
gcd
(
∣
a
i
−
a
i
+
k
∣
)
m=\gcd(|a_i-a_{i+k}|)
m=gcd(∣ai−ai+k∣),
m
m
m 满足要求则ans++
void solve(int cs) {
int n = input();
VI a(n);
for (int i = 0; i < n; i++) {
a[i] = input();
}
int ans = 0;
for (int k = 1; k <= n; k++) {
if (n % k == 0) {
int m = 0;
for (int j = k; j < n; j++) {
m = __gcd(m, abs(a[j] - a[j - k]));
}
ans += (m >= 2 || m == 0);//m得0时所有数相等
}
}
cout << ans << endl;
}
1907E - Good Triples
分解方法必然为将每位单独分解,所以用隔板法预处理1~9所有数字分解成三个数字的情况数,然后将各个位上的情况数累乘起来
int ans[10];
void ini() {
ans[0] = 1;
for (int i = 1; i < 10; i++) {
ans[i] = (i + 2) * (i + 1) / 2;
}
}
void solve(int cs) {
string s;
cin >> s;
int a = 1;
for (auto c : s) {
a *= ans[c - '0'];
}
cout << a << endl;
}
1899F - Alex’s whims
首先构造一条链,我们拿好 n n n 号点,距离是多少我们就把他安到哪个点上
void solve(int cs) {
int n = in(), q = in();
for (int i = 2; i <= n; i++) {
cout << i - 1 << ' ' << i << endl;
}
int now = n - 1;
pii lk = make_pair(n, n - 1);
while(q--) {
int d = in();
cout << lk.first << ' ' << lk.second << ' ' << d << endl;
lk.second = d;
now = d;
}
}
1886C - Decreasing String
前缀和比较一下确定删几个字符,并且更新一下pos,然后单调栈处理一下就好
void solve(int cs) {
string s;
cin >> s;
int pos = in(), n = s.size();
VI d(n + 1);
d[0] = n;
for (int i = 1; i <= n; i++) {
d[i] = d[i - 1] + n - i;
}
int num = 0;
for (int i = 0; i <= n; i++) {
if (pos <= d[0]) break;
if (pos > d[i] && pos <= d[i + 1]) {
num = i + 1;
pos -= d[i];
break;
}
}
//删num个 位置更新为删num个后字符串的pos
deque<char> st;
int cnt = 0;
for(auto c : s) {
if (st.empty()) {st.push_back(c); continue;}
if (st.back() <= c) {st.push_back(c); continue;}
while (st.size() && cnt < num && st.back() > c) {
st.pop_back();
cnt++;
}
st.push_back(c);
}
cnt = 1;
for (auto c : st) {
if (cnt == pos) {
cout << c;
return;
}
cnt++;
}
}
1878D - Reverse Madness
我们通过观察可以发现:对于每一个 x x x 的翻转都是在他所对应的区间里,以 l i l_i li 与 r i r_i ri 的中点为轴, x x x 为边界进行翻转,所以对于每一个 x x x,我们二分找到他所对应的区间,然后对翻转区间的端点进行标记,最后根据标记进行翻转即可
void solve(int cs) {
int n = in(), k = in();
string s;
cin >> s;
VI l(k);
VI r(k);
for (int i = 0; i < k; i++) cin >> l[i];
for (int i = 0; i < k; i++) cin >> r[i];
int q = in();
VI re(n + 5);
while (q--) {
int x = in();
int pos = upper_bound(l.begin(), l.end(), x) - l.begin();
pos--;
int ll = l[pos], rr = r[pos];
re[min(x, ll + rr - x)]++;
re[max(x, ll + rr - x) + 1]--;
}
int cnt = 0;
for (int i = 0; i < k; i++) {
for (int j = l[i]; j <= r[i]; j++) {
cnt += re[j];
if (cnt & 1 && j <= (l[i] + r[i]) / 2) swap(s[j - 1], s[l[i] + r[i] - j - 1]);
}
}
cout << s << endl;
}
1875D - Jellyfish and Mex
我们首先可以得出,如果想删一个数就需要一直删他直到把他删完才是最优的,其次我们可以发现,在删数的过程中mex的值是单调不增的,于是我们可以先算出删数前的mex,然后往回dp即可。
我们设 d p i dp_i dpi 为将mex变成 i i i 需要的最小花费, c n t i cnt_i cnti 记录原数组中 i i i 的数量,对于 j < i j < i j<i 可以得出状态转移方程 d p j = m i n ( d p j , d p i + ( c n t j − 1 ) ∗ i + j ) dp_j = min(dp_j,\ dp_i+(cnt_j - 1)*i+j) dpj=min(dpj, dpi+(cntj−1)∗i+j)
最后输出 d p 0 dp_0 dp0 即可。
void solve(int cs) {
int n = in();
VI a(n);
VI cnt(n + 5);
VI dp(n + 5, inf);
for (int i = 0; i < n; i++) {
a[i] = in();
if (a[i] <= n) cnt[a[i]]++;
}
int mex = 0;
for (int i = 0; i <= n; i++) {
if (cnt[i] == 0) {mex = i; break;}
}
dp[mex] = 0;
for (int i = mex; i >= 0; i--) {
for (int j = i - 1; j >= 0; j--) {
dp[j] = min(dp[j], dp[i] + (cnt[j] - 1) * i + j);
}
}
print(dp[0]);
}
1856C - To Become Max
由于最后一个数的值无法改变,所以我们可以确定所有数在无限操作下可到达的最大值,然后对于每个数二分出 k k k 次操作能到达的最大值,取max即可
void solve(int cs) {
int n = in(), k = in();
int ans = 0;
VI a(n);
for (int i = 0; i < n; i++) {cin >> a[i]; ans = max(ans, a[i]);}
VI mx(n);
mx[n - 1] = a[n - 1];
for (int i = n - 2; i >= 0; i--) mx[i] = max(a[i], mx[i + 1] + 1);
auto ok = [&](int val, int id) {
int t = k;
while(id < n) {
if (a[id] >= val) break;
t -= (val - a[id]);
val--;
id++;
}
return t >= 0;
};
for (int i = 0; i < n; i++) {
int l = a[i], r = mx[i];
while (l < r) {
int mid = (l + r + 1) >> 1;
if (ok(mid, i)) {
l = mid;
}
else {
r = mid - 1;
}
}
ans = max(ans, l);
}
cout << ans << endl;
}
1849C - Binary String Copying
我们通过观察可以发现,对于一个区间
[
l
,
r
]
[l,\ r]
[l, r],我们只需要找到他最左面的
1
1
1 的位置
l
′
l'
l′ 和最右面的
0
0
0 的位置
r
’
r’
r’,我们就可以把这个区间等价为
[
l
′
,
r
′
]
[l',\ r']
[l′, r′] ,我们用set
存一下等价后的区间,记一下是否有输入的区间已经有序,最后输出种类即可
void solve(int cs) {
int n = in(), op = in();
string s;
cin >> s;
s = " " + s;
set<pii > ans;
VI z(n + 1), o(n + 1);
int cnt = 0;
for (int i = n; i > 0; i--) {
if (s[i] == '0') z[i] = (++cnt);
else cnt = 0;
}
cnt = 0;
for (int i = 0; i <= n; i++) {
if (s[i] == '1') o[i] = (++cnt);
else cnt = 0;
}
int f = 0;
while (op--) {
int l = in(), r = in();
l += z[l];
r -= o[r];
if (l >= r) f = 1;
else ans.insert(make_pair(l, r));
}
cout << ans.size() + f << endl;
}
1941E - Rudolf and k Bridges
每座桥之间的关系是独立的,故我们可以单独处理每座桥的最小造价,然后用前缀和计算一下连续 k k k 个桥的最小造假即可
于是我们可以把问题转换成求单个桥的最小造假,我们用 d p i dp_i dpi 表示在第 i i i 个点上建支架,且小于 i i i 的点全部满足条件时的最小花费,我们用单调队列来储存 i i i 点前的 d d d 范围内的最小 d p dp dp 值的下标,状态转移方程为 d p i = d p q . f r o n t ( ) + a i + 1 dp_i = dp_{q.front()} + a_i+1 dpi=dpq.front()+ai+1
void solve(int cs) {
int n, m, k, d;
cin >> n >> m >> k >> d;
VI sum(n + 1);
for (int _ = 1; _ <= n; _++) {
VI a(m);
for (int i = 0; i < m; i++) cin >> a[i];
VI dp(m);
deque<int> q;
q.pb(0);
for (int i = 0; i < m; i++) {
while(!q.empty() && i - q.front() - 1 > d) q.pop_front();
dp[i] = dp[q.front()] + a[i] + 1;
while(!q.empty() && dp[i] <= dp[q.back()]) q.pop_back();
q.push_back(i);
}
sum[_] = sum[_ - 1] + dp[m - 1];
}
int ans = llinf;
for (int i = k; i <= n; i++) {
ans = min(ans, sum[i] - sum[i - k]);
}
cout << ans << endl;
}