AtCoder Beginner Contest 221 复盘
A. Seismic magnitude scales
一遍 AC。
int a, b;
int main() {
std::ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
cin >> a >> b;
if (a > b) std::swap(a, b);
cout << (1ll << (5 * (b - a))) << endl;
return 0;
}
B. typo
一遍 AC。
std::string a, b;
int main() {
std::ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
cin >> a >> b;
if (a == b) {
cout << "Yes" << endl;
return 0;
}
for (int i = 0, siz = (int) a.size(); i < siz - 1; ++i) {
std::swap(a[i], a[i + 1]);
if (a == b) {
cout << "Yes" << endl;
return 0;
}
std::swap(a[i], a[i + 1]);
}
cout << "No" << endl;
return 0;
}
C. Select Mul
读完题稍微楞了一下然后才开始写的。写得有些麻烦,还出了点 typo。
#错误警示:操作数位的时候,一定要记清乘 base 的多少次方!尤其是 Hash 的时候!
int digs[10 + 5], cnt;
int ord[10 + 5];
lli pow10[100];
void extract(int x) {
while (x) {
digs[++cnt] = x % 10;
x /= 10;
}
}
int main() {
std::ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
int x; cin >> x; extract(x);
pow10[0] = 1;
for (int i = 1; i <= cnt; ++i) {
ord[i] = i;
pow10[i] = pow10[i - 1] * 10ll;
}
lli ans = 0;
do {
lli num1 = 0, num2 = 0;
for (int i = 1; i <= cnt; ++i) num2 = num2 * 10 + digs[ord[i]];
// DEBUG(num2);
for (int cut = 1; cut < cnt; ++cut) {
// cut between x, x + 1
num1 = num1 * 10 + digs[ord[cut]];
num2 -= digs[ord[cut]] * pow10[cnt - cut]; // remember to -1
// DEBUG(digs[ord[cut]]);
// DEBUG(num1); DEBUG(num2);
if ((cut != 1 && digs[ord[1]] == '0') || (cut != cnt - 1 && digs[ord[cut + 1]] == '0')) continue;
ans = std::max(ans, num1 * num2);
}
} while (std::next_permutation(ord + 1, ord + 1 + cnt));
cout << ans << endl;
return 0;
}
D. Online games
第一次过不去样例三,又读了一遍题才发现不能直接差分,还需要加上原值的差。
一遍 AC,虽然有点运气成分。
const int MAXN = 2e5 + 10;
const int MAXNUM = 4e5 + 10;
int n, aa[MAXN], end[MAXN];
int nums[MAXNUM], cnt;
int diff[MAXNUM];
int ans[MAXN];
int main() {
std::ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
cin >> n;
rep (i, 1, n) {
int len;
cin >> aa[i] >> len; end[i] = aa[i] + len;
nums[++cnt] = aa[i];
nums[++cnt] = aa[i] + len;
}
std::sort(nums + 1, nums + 1 + cnt);
cnt = (std::unique(nums + 1, nums + 1 + cnt) - nums - 1);
int maxend = 0;
rep (i, 1, n) {
aa[i] = std::lower_bound(nums + 1, nums + 1 + cnt, aa[i]) - nums;
end[i] = std::lower_bound(nums + 1, nums + 1 + cnt, end[i]) - nums;
++diff[aa[i]]; --diff[end[i]];
maxend = std::max(maxend, end[i]);
}
rep (i, 1, maxend) {
diff[i] += diff[i - 1];
ans[diff[i]] += nums[i + 1] - nums[i];
}
rep (i, 1, n) {
cout << ans[i] << ' ';
} cout << endl;
return 0;
}
E. LEQ
首先可以把题意转成求
似乎可以用类似树状数组求逆序对一样的思想搞一搞?但是到这我就不会了。
赛后才知道,需要拆掉 \(2^{j - i - 1}\) 这个式子,得到 \(\frac{2^j}{2^{i + 1}}\) 这个东西,然后我们从后往前固定 \(i\) ,式子就变成了求
后面这个东西就很好用权值树状数组维护了:从后往前扫到了 \(j\),在 \(a_j\) 这个位置存上一个 \(2^j\),统计所有 \(a_j + 1\) 到 \(\max a\) 的和即可;再把这个乘上一个 \(2^{i + 1}\) 的逆元加入总答案即可。
Special thanks to @plokim.
const int MAXN = 3e5 + 10;
const int HA = 998244353;
void add(int &x, int y) {
x = (1ll * x + 1ll * y) % HA;
}
int n, aa[MAXN];
struct BIT {
int seq[MAXN];
#define lb(x) ((x) & (-(x)))
void insert(int pos, int x) {
for (; pos <= n; pos += lb(pos)) add(seq[pos], x);
}
int qry(int pos) {
int r = 0; for (; pos; pos -= lb(pos)) add(r, seq[pos]); return r;
}
} bit;
int fastpow(int a, int b, int p) {
int r = 1; while (b) {if (b & 1) r = 1ll * r * a % p; a = 1ll * a * a % p; b >>= 1;} return r;
}
int main() {
std::ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
cin >> n;
std::map<int, int> lisan;
rep (i, 1, n) {cin >> aa[i]; lisan[aa[i]] = 0;}
int cnt = 0;
for (auto &x : lisan) {
x.second = ++cnt;
}
rep (i, 1, n) aa[i] = lisan[aa[i]];
int ans = 0;
for (int i = n; i >= 1; --i) {
int tmp = 1ll * bit.qry(cnt) - 1ll * bit.qry(aa[i] - 1); // 因为是 ai <= aj 所以这里求的是 [aa[i], maxa]
(tmp += HA) %= HA;
tmp = 1ll * tmp * fastpow(fastpow(2, i + 1, HA), HA - 2, HA) % HA;
add(ans, tmp);
bit.insert(aa[i], fastpow(2, i, HA));
}
cout << ans << endl;
return 0;
}
F. Diameter set
首先考虑直径 \(d\) 为偶数的情况,此时是存在直径中点的,把它挑起来当根,那么此时树的最大深度就是 \(\frac{d}{2}\),所有可以选的点就是深度为 \(\frac{d}{2}\) 的点,而且根的每棵子树里至多能选一个。
设根节点的度为 \(k\),也就是 \(k\) 棵子树,每棵子树可选的点是 \(D_k\) 个,那么答案即为 \(\prod (D_i + 1) - \sum D_i - 1\),也就是子树里任选一点(可以不选)的方案数乘起来,减去只选一个点的方案数和所有点都不选的方案数。
直径为奇数的情况类似,只不过我们这次不是找直径中点挑起来当根,而是找直径中心边的两个端点,断掉这条边,形成两棵子树,显然这两棵子树的最大深度是 \(\frac{d - 1}{2}\),能选的点即是最大深度的点,每棵子树恰好选一个。答案即是 \(D_1D_2\)。
所有东西都可以通过跑 DFS 搞定,时间复杂度 \(O(n)\).
代码里处理偶数的做法和上面说的不大一样,而和奇数类似,是把直径中点的所有边都断了,形成 \(k\) 棵子树,每棵子树最大深度即是 \(\frac{d}{2} - 1\),找这些点即可。有很多地方可以代码复用,但我懒了,直接复制粘贴改改就过了。
const int MAXN = 2e5 + 10;
const int HA = 998244353;
int n;
int diameter;
struct Edge {
int v, next; bool disabled;
} edge[MAXN << 1]; int head[MAXN], cnt;
inline void addEdge(int u, int v) {
edge[cnt++] = {v, head[u]}; head[u] = cnt - 1;
}
int dist[MAXN];
void dfs(int u, int fa, int *dis) {
for (int e = head[u]; e != -1; e = edge[e].next) {
int v = edge[e].v;
if (v == fa || edge[e].disabled) continue;
dis[v] = dis[u] + 1;
dfs(v, u, dis);
}
}
std::vector<int> path;
bool dfsCollect(int u, int fa, int end) {
if (u == end) {
path.push_back(u); return true;
}
bool flag = false;
for (int e = head[u]; e != -1; e = edge[e].next) {
int v = edge[e].v;
if (v == fa || edge[e].disabled) continue;
if (dfsCollect(v, u, end)) { flag = true; break; }
}
if (flag) path.push_back(u);
return flag;
}
namespace Task_getDiameter {
void _main() {
dfs(1, 0, dist);
int fx = 1;
rep (i, 1, n) if (dist[fx] < dist[i]) fx = i;
dist[fx] = 0; dfs(fx, 0, dist);
int fy = 1;
rep (i, 1, n) if (dist[fy] < dist[i]) fy = i;
dfsCollect(fx, 0, fy);
}
}
int bel[MAXN], blks;
int cnt_dist[MAXN];
void dfsBlocks(int u, int fa, int blk) {
bel[u] = blk;
for (int e = head[u]; e != -1; e = edge[e].next) {
int v = edge[e].v;
if (v == fa || edge[e].disabled) continue;
dfsBlocks(v, u, blk);
}
}
namespace Task_Odd {
void _main() {
int da = path[path.size() / 2], db = path[path.size() / 2 - 1];
for (int e = head[da]; e != -1; e = edge[e].next) {
int v = edge[e].v;
if (v != db) continue;
edge[e].disabled = edge[e ^ 1].disabled = true;
break;
}
dfsBlocks(da, 0, ++blks); dfsBlocks(db, 0, ++blks);
dist[da] = dist[db] = 0; dfs(da, 0, dist); dfs(db, 0, dist);
for (int i = 1; i <= n; ++i) {
if (dist[i] == diameter / 2) ++cnt_dist[bel[i]];
}
cout << 1ll * cnt_dist[1] * cnt_dist[2] % HA << endl;
}
}
namespace Task_Even {
void _main() {
int dx = path[diameter / 2];
std::vector<int> roots;
for (int e = head[dx]; e != -1; e = edge[e].next) {
int v = edge[e].v;
edge[e].disabled = edge[e ^ 1].disabled = true;
roots.push_back(v);
}
for (auto v : roots) {
dist[v] = 0; dfs(v, 0, dist);
dfsBlocks(v, 0, ++blks);
}
for (int i = 1; i <= n; ++i) {
if (dist[i] == diameter / 2 - 1) ++cnt_dist[bel[i]];
}
int mul = 1, sum = 0;
for (int i = 1; i <= blks; ++i) {
mul = 1ll * mul * (cnt_dist[i] + 1) % HA;
(sum += cnt_dist[i]) %= HA;
}
cout << (mul - sum - 1 + HA) % HA;
}
}
int main() {
std::ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
memset(head, -1, sizeof head);
cin >> n;
rep (i, 1, n - 1) {
int u, v; cin >> u >> v;
addEdge(u, v); addEdge(v, u);
}
Task_getDiameter::_main();
diameter = (int) path.size() - 1;
if (diameter & 1) Task_Odd::_main();
else Task_Even::_main();
return 0;
}
G. Jumping sequence
首先把坐标系逆时针旋转 45°,那么上下左右跳 \(D\) 步就变成了坐标变换 \((X \pm D, Y \pm D)\)(正负号任取一种,一共 4 种,对应上下左右)。
于是这里就可以把两维分开考虑了:我们需要对于(旋转后的,下同)X 轴找到一个系数序列 \(t_i \in \{-1, 1\}\) 使得 \(\sum t_iD_i = N + M\)(Y 轴同理,只不过是 \(N - M\))
从这里我们就能得出 \((X \pm D, Y \pm D)\) 的方向选择了:对于 X 轴,如果 \(t_i = -1\) 则方向从 L 和 D 中选;对于 Y 轴,如果 \(t_i = -1\) 则方向从 D 和 R 中选;反之亦然。通过对两个 \(t_i\) 的确定,我们就能唯一确定当前的方向了,这是我们最后输出方案的关键。
但是这样做还是不好做,直接 DP 复杂度太高还没法优化,于是我们考虑进一步转化:设 \(S = \sum D_i\),于是题目转化成了求系数序列 \(c_i\) 使得 \(\sum c_iD_i = \frac{S + N + M}{2}\) (Y 轴为 \(\frac{S + N - M}{2}\)),其中 \(c_i \in \{0, 1\}\)。显然,当 \(c_i = 1\) 时 \(t_i = 1\),\(c_i = 0\) 时 \(t_i = -1\)。无解情况就是右面的东西不是非负整数。
这个东西就是一个背包求是否有可行解,时间复杂度是 \(O(NS)\) 的,看似和上面没有变化,但是可以用 bitset 优化,时空都刚好能卡过。
最后输出方案的时候,从后往前找,如果 \(c_i\) 可以为 \(0\) 就尽量选择 \(0\),否则为 \(1\),然后就知道了对应的 \(t_i\),进而知道了当前走的是哪个方向。
const int MAXN = 2000 + 10;
const int MAXS = 3600000 + 10;
const char cho[] = { 'L', 'U', 'D', 'R' };
int n, a, b;
int dd[MAXN];
std::bitset<MAXS> dp[MAXN];
int main() {
std::ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
cin >> n >> a >> b;
int s = 0;
rep (i, 1, n) {
cin >> dd[i];
s += dd[i];
}
if ((s + a + b) & 1 || std::abs(a + b) > s || std::abs(a - b) > s) {
cout << "No" << endl; return 0;
}
dp[0][0] = 1;
rep (i, 1, n) {
dp[i] = dp[i - 1] | (dp[i - 1] << dd[i]);
}
int fx = (s + a + b) >> 1, fy = (s + a - b) >> 1;
if (!dp[n][fx] || !dp[n][fy]) {
cout << "No" << endl; return 0;
}
cout << "Yes" << endl;
std::string ans;
for (int i = n; i >= 1; --i) {
int k = 0;
// DEBUG(fx); DEBUG(fy);
if (!dp[i - 1][fx]) fx -= dd[i], ++k;
if (!dp[i - 1][fy]) fy -= dd[i], k += 2;
// DEBUG(dp[i].to_string());
ans = cho[k] + ans;
}
cout << ans << endl;
return 0;
}