2024-06-23 16:57阅读: 545评论: 0推荐: 4

AtCoder Beginner Contest 359

A - Count Takahashi (abc359 A)

题目大意

给定n个字符串,问有多少个字符串是Takahashi

解题思路

注意判断比较即可。

神奇的代码
#include <bits/stdc++.h>
using namespace std;
using LL = long long;
int main(void) {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int n;
cin >> n;
int ans = 0;
while (n--) {
string s;
cin >> s;
ans += s == "Takahashi";
}
cout << ans << '\n';
return 0;
}


B - Couples (abc359 B)

题目大意

给定n个数字,问有多少个数字,其左右两个数字相同。

解题思路

枚举中间的数字,然后判断其左右俩数字是否相同即可。

神奇的代码
#include <bits/stdc++.h>
using namespace std;
using LL = long long;
int main(void) {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int n;
cin >> n;
n *= 2;
vector<int> a(n);
for (auto& x : a)
cin >> x;
int ans = 0;
for (int i = 1; i < n - 1; ++i) {
ans += a[i - 1] == a[i + 1];
}
cout << ans << '\n';
return 0;
}


C - Tile Distance 2 (abc359 C)

题目大意

给定一个坐标系,有格子,如下:

格子

给定起点和终点,问从起点到终点,要穿过多少次蓝线。

解题思路

观察上述格子,可以发现在y轴移动,每移动一次,必定穿过一次蓝线。

由于每行格子交错排列的,每往上走一个,我左右可走的区间都扩大了1。比如我在(5,0),我可以左边往上走到(3,1)(5,1)的格子,也可以右边往上走到(5,1)(7,1)

这样,原本我左右走的横坐标区间是[4,6),往上走一格后,横坐标区间扩大为[3,7),往上走n格,可到达的横坐标区间范围为[4n,6+n),只要我终点的横坐标在这区间,那我就可以只花费y轴移动的代价就抵达终点了。而如果不在这个区间,那就再左右移动,每移动一次,横坐标区间就变动2

容易发现这样移动一定是最优的。y轴移动的蓝线穿过不可避免,然后x轴的蓝线穿过已经尽可能在移动y轴时避免了。

神奇的代码
#include <bits/stdc++.h>
using namespace std;
using LL = long long;
int main(void) {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
LL sx, sy, tx, ty;
cin >> sx >> sy >> tx >> ty;
LL dy = abs(sy - ty);
LL odd = (sx & 1);
LL l = sx - odd + (sy & 1) * (odd ? 1 : -1);
LL r = l + 2;
l -= dy, r += dy;
LL ans = dy + max(0ll, l - tx + 1) / 2 + max(0ll, tx - r + 2) / 2;
cout << ans << '\n';
return 0;
}


D - Avoid K Palindrome (abc359 D)

题目大意

给定一个包含AB?的字符串s,将?变成AB,问有多少种情况,使得s没有长度为k的回文子串。

解题思路

注意k10

从左到右考虑每个字符,如果当前是?,则考虑其变为A,B,是否出现长度为k的回文串。

我们需要知道该?k1位的情况,加上该字母,就可以判断出新增的子串是不是回文串。

即设dp[i][j]表示考虑前i位字符,其中?都已经替换成AB后,且后9位的字符状态为j(因为只有AB两种,可以编码成01,用二进制压缩表示)。

然后考虑当前位的情况,如果取值为A0,则判断j<<1状态是不是回文串,不是的话则有dp[i+1][(j<<1)&mask]+=dp[i][j],否则就状态非法,不转移。因为j<<1是后10个字符的状态信息,而j的含义是后9位,所以&mask是把第10位去掉。

同理,取值为B的话,即1,则判断(j<<1)|1是不是回文串,不是的话就转移,否则不转移。

可以事先预处理每个状态是否是回文串,然后当ik时再考虑转移的合法性。

神奇的代码
#include <bits/stdc++.h>
using namespace std;
using LL = long long;
const int mo = 998244353;
int main(void) {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int n, k;
string s;
cin >> n >> k >> s;
int up = (1 << k);
vector<int> p(up);
for (int i = 0; i < up; i++) {
vector<int> bit(k);
int num = i;
for (int j = 0; j < k; j++) {
bit[j] = (num & 1);
num >>= 1;
}
auto rev = bit;
reverse(rev.begin(), rev.end());
p[i] = rev == bit;
}
up = 1 << (k - 1);
int mask = up - 1;
vector<int> dp(up, 0);
dp[0] = 1;
for (int i = 0; i < n; ++i) {
int chr = s[i];
vector<int> dp2(up, 0);
for (int j = 0; j < up; j++) {
if (chr == '?') {
if (i + 1 < k || !p[j << 1]) {
int nxt = (j << 1) & mask;
dp2[nxt] = (dp2[nxt] + dp[j]) % mo;
}
if (i + 1 < k || !p[j << 1 | 1]) {
int nxt = (j << 1 | 1) & mask;
dp2[nxt] = (dp2[nxt] + dp[j]) % mo;
}
} else {
if (i + 1 < k || !p[j << 1 | (chr - 'A')]) {
int nxt = (j << 1 | (chr - 'A')) & mask;
dp2[nxt] = (dp2[nxt] + dp[j]) % mo;
}
}
}
dp.swap(dp2);
}
LL ans = 0;
for (int i = 0; i < up; i++) {
ans = (ans + dp[i]) % mo;
}
cout << ans << '\n';
return 0;
}


E - Water Tank (abc359 E)

题目大意

给定柱子长度。然后如下如所示。

example

每一时刻,0位会多一高度的水,如果该水高度高过柱子,且高过1位的水高度,则该高度的水会跑到1位,同理继续判断1位,该水是否跑到2位。

问每一位出现水的最早时刻。

解题思路

  • 考虑1位,其答案就是第一根柱子高度3(a1)+1

  • 考虑2位,需要0位水高31位水高1,答案就是3+1+1

  • 考虑3位,则需要0,1,2的水高均为4,答案就是4+4+4+1

  • 考虑4位,则需要0,1,2水高43位水高1,答案就是4+4+4+1+1

  • 考虑5位,则需要0,1,2,3,4,位水高5,答案就是5+5+5+5+5+1

观察上述例子的求解过程,如果要求第i位的答案,则要求第i1装满,装满的意思就是和柱子ai高度同高,而同高会连带着i2,i3,...位同高,但需要多少位呢?观察上述会发现,假设前面比柱子ai还高的柱子是aj,那么j,j+1,...,i1位的水高都必须是ai

因此,求解第i位的答案,则需要i1,i2,...,j位与ai同高,然后j1,j2,...,kaj同高,然后k1,k2,...ak同高,其中aiajak,也就是说需要维护一个从左到右,柱子高度单调递减的序列,这可以用栈维护,即单调栈,栈底是最左边,栈顶是最右边。在维护递减高度时,顺带维护每个递减区间的水高度总和即可。

, 神奇的代码
#include <bits/stdc++.h>
using namespace std;
using LL = long long;
int main(void) {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int n;
cin >> n;
vector<int> h(n + 1);
for (int i = 1; i <= n; ++i)
cin >> h[i];
h[0] = 1e9 + 8;
vector<int> hei;
hei.push_back(0);
LL ans = 0;
for (int i = 1; i <= n; ++i) {
while (!hei.empty() && h[hei.back()] <= h[i]) {
ans -= h[hei.back()] * (LL)(hei.back() - hei[hei.size() - 2]);
hei.pop_back();
}
ans += h[i] * (LL)(i - hei.back());
hei.push_back(i);
cout << ans + 1 << " \n"[i == n];
}
return 0;
}


F - Tree Degree Optimization (abc359 F)

题目大意

给定n个点的点权ai,构造一棵树,使得i=1ndi2ai最小,其中di表示点i的度。

解题思路

由于是一棵树,则有di=2n2,1din1

对于任意满足上述条件的di,都可以构造出对应的树,使得每个点的度数都是di。(构造方法为,每次选择度数为1和非1的点连边,然后更新剩余度数,归纳可证)

那剩下就是如何分配这些度数。

如果给点1分配一个度,是其d1=12,则代价是4a1a1,而如果是d1=23,则代价是9a14a1

这可以把问题抽象成每个点起始度数为1,然后把剩下的n2个度分配给每个点,使得代价最小,每次仅分配1的度,那我肯定是贪心的分配给代价最小的点。

用优先队列维护上述代价即可。

神奇的代码
#include <bits/stdc++.h>
using namespace std;
using LL = long long;
int main(void) {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int n;
cin >> n;
vector<int> a(n);
for (auto& x : a)
cin >> x;
priority_queue<pair<LL, int>, vector<pair<LL, int>>, greater<pair<LL, int>>>
q;
LL ans = accumulate(a.begin(), a.end(), 0ll);
for (int i = 0; i < n; i++) {
q.push({a[i] * 3ll, 2});
}
for (int i = 0; i < n - 2; i++) {
auto [x, y] = q.top();
q.pop();
ans += x;
if (y < n - 1) {
LL ori = x / (2 * y - 1);
LL nxt = ori * (2 * y + 1);
q.push({nxt, y + 1});
}
}
cout << ans << '\n';
return 0;
}


G - Sum of Tree Distance (abc359 G)

题目大意

给定一棵树,点有点权ai。求ijf(i,j),其中ai==ajf(i,j)表示点ij的距离,边权为1

解题思路

距离的最终来源是边数,考虑每条边被算入了多少次,即对答案贡献的次数。

ijf(i,j)=esume,其中sume表示边e对答案贡献的次数,考虑该次数怎么算。

考虑边(u,v),将该树分成了两个连通块,如果这两个连通块各有一点i,j,其ai==aj,那么从点ij必定经过该边,因此需要统计每个点权,在两个连通块的出现次数,其乘积的和则是该边的贡献。

问题就变成了统计一个子树里,各个点权的出现次数cci,事先预处理每个点权的出现次数cnti,对点权求和,即cci×(cnticci)就是该边对答案的贡献。

由于点权是稀疏的,用map来维护出现次数,合并儿子之间的map,采用启发式合并,即用数量少的合并到数量大的,这样每次合并最坏的复杂度是O(n2),而最坏的情况最多只有O(logn)次(每一次最坏情况,合并后的点数会翻倍,最多翻倍O(logn)次。

合并的时候,计算边贡献的式子,cci×(cnticci)只有一项发生变化,可以动态O(1)维护出更新后的贡献sum

最终的时间复杂度就是O(nlog2n),一个log是启发式合并,另一个logmap

代码里的sum是考虑父亲边ufa对答案的贡献。由于返回类型是pair,用map构造pair会复制构造造成巨大的性能损失,用move函数进行移动构造。或者返回值仅为map,编译器也会优化成移动构造。

神奇的代码
#include <bits/stdc++.h>
using namespace std;
using LL = long long;
int main(void) {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int n;
cin >> n;
vector<vector<int>> edge(n);
for (int i = 0; i < n - 1; i++) {
int u, v;
cin >> u >> v;
u--;
v--;
edge[u].push_back(v);
edge[v].push_back(u);
}
vector<int> a(n);
vector<int> cnt(n);
for (auto& x : a) {
cin >> x;
--x;
cnt[x]++;
}
LL ans = 0;
auto dfs = [&](auto& dfs, int u, int fa) -> pair<map<int, int>, LL> {
map<int, int> cc;
LL sum = 0;
for (auto v : edge[u]) {
if (v == fa)
continue;
auto&& [son_ret, son_sum] = dfs(dfs, v, u);
if (son_ret.size() > cc.size()) {
swap(son_ret, cc);
swap(son_sum, sum);
}
for (auto& [k, v] : son_ret) {
sum -= 1ll * cc[k] * (cnt[k] - cc[k]);
cc[k] += v;
sum += 1ll * cc[k] * (cnt[k] - cc[k]);
}
}
sum -= 1ll * cc[a[u]] * (cnt[a[u]] - cc[a[u]]);
cc[a[u]]++;
sum += 1ll * cc[a[u]] * (cnt[a[u]] - cc[a[u]]);
ans += sum;
return {move(cc), sum};
};
dfs(dfs, 0, 0);
cout << ans << '\n';
return 0;
}


本文作者:~Lanly~

本文链接:https://www.cnblogs.com/Lanly/p/18263628

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   ~Lanly~  阅读(545)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
💬
评论
📌
收藏
💗
关注
👍
推荐
🚀
回顶
收起
  1. 1 404 not found REOL
404 not found - REOL
00:00 / 00:00
An audio error has occurred.