AtCoder Beginner Contest 354 (PARTIAL) Editorial
AtCoder Beginner Contest 354 (PARTIAL) Editorial
0. 前言
这应该算是我第一次写整场比赛的题解。
然而,由于我不会这场比赛的 D 和 G 题,故没有这两题的题解。
1. Exponential Plant - 指数植物
题意
一棵植物在第 \(i\) 天的高度为 \((2^i-1)\text{cm}\),问最早在第几天植物高度高于指定高度 \(H\text{cm}\)。
思路
直接模拟即可。
代码
#include <iostream>
using namespace std;
const int N = 1e5 + 10;
int t, n, r = 1, i = 0;
int main()
{
cin >> n;
while (t <= n)
t += r, r <<= 1, i++;
printf("%d\n", i);
}
2. AtCoder Janken 2 - 石头剪刀布
题意
给定 \(n\) 个字符串 \(s_i\)、\(n\) 个整数 \(c_i\),求 \(s\) 中字典序第 \(\sum_{i=1}^n c_i\bmod n+1\) 小的字符串。
思路
直接模拟即可。
代码
#include <iostream>
#include <string>
#include <algorithm>
using namespace std;
const int N = 1e2 + 10;
int sm, n;
string s[N];
int main()
{
cin >> n;
for (int i = 0, x; i < n; i++)
{
cin >> s[i] >> x;
sm += x;
}
sort(s, s + n);
cout << s[sm % n] << '\n';
}
3. AtCoder Magics - 魔术
题意
给定 \(n\) 个整数 \(a_i,c_i\),在集合 \(\{1,2,...,n\}\) 内删去所有存在整数 \(x\) 满足 \(a_x>a_y\) 且 \(c_x<c_y\) 的整数 \(y\),求无法再次进行删除的集合。
思路
将 \((a_i,c_i)\) 绑定为 pair
并进行升序排序,之后进行暴力删除即可。
代码
#include <iostream>
#include <map>
#include <set>
#include <algorithm>
using namespace std;
const int N = 2e5 + 10;
map<int, set<int>, greater<int>> mp;
set<int> res;
int n;
using pii = tuple<int, int, int>;
pii a[N];
int main()
{
scanf("%d", &n);
for (int i = 1; i <= n; i++)
{
auto &[x, y, z] = a[i];
scanf("%d%d", &x, &y);
z = i;
}
sort(a + 1, a + n + 1);
for (int i = 1; i <= n; i++)
{
while (mp.size() and mp.begin()->first > get<1>(a[i]))
{
mp.erase(mp.begin());
}
mp[get<1>(a[i])].insert(get<2>(a[i]));
}
for (auto &[k, v] : mp)
{
for (auto &j : v)
res.insert(j);
}
printf("%d\n", res.size());
for (auto &i : res)
printf("%d ", i);
}
4. Remove Pairs - 卡牌配对
题意
高桥和青木的面前有 \(n\) 张卡牌,每张卡牌的正反面分别写着 \(a_i,b_i\) 。编号为 \(x,y\) 的卡牌可以被配对并移除,当且仅当满足 \(a_x=a_y\) 或 \(b_x=b_y\) 。无法再次移除卡牌的人输。高桥先手。判断拥有必胜策略的是高桥还是青木。
思路
比 D 简单。
题目中的数据范围为 \(1\le n\le 18\),故解法为状压+博弈论。
使用一个整数中的 \(n\) 个比特位表示一种游戏状态,即有哪些牌可用。
显然,根据可用的牌,可以枚举出所有该状态的下一步状态。
在一个状态下先手拥有必胜策略,当且仅当该状态至少存在一个后继状态先手必败。
于是暴力枚举的实现是容易的。不过需要一些记忆化,否则有超时的风险。
代码
#include <iostream>
using namespace std;
const int N = 20;
int n, a[N], b[N];
bool bt[1 << N], vis[1 << N];
bool dfs(int mem)
{
if (vis[mem])
return bt[mem];
vis[mem] = true;
for (int i = 0; i < n; i++)
{
if (mem & (1 << i))
continue;
for (int j = i + 1; j < n; j++)
{
if (mem & (1 << j) or (a[i] != a[j] and b[i] != b[j]))
continue;
bt[mem] |= !dfs(mem | (1 << i) | (1 << j));
if (bt[mem])
return true;
}
}
return false;
}
int main()
{
scanf("%d", &n);
for (int i = 0; i < n; i++)
{
scanf("%d%d", a + i, b + i);
}
puts(dfs(0) ? "Takahashi" : "Aoki");
}
5. Useless for LIS - 最长上升子序列并集
题意
给出 \(n\) 个整数 \(a_i\) ,求处于哪些下标上的 \(a_i\) 可以构成一个最长上升子序列。
思路
还是没有 D 难。
用树状数组求最长上升子序列长度显然是基操。
\(a_i\) 比较大,上树之前先做个离散化。
定义 \(d_i\) 为以 \(a_i\) 结尾的最长上升子序列的长度。
根据树状数组对最长上升子序列长度的计算方法可以得出一个结论。
对于所有满足 \(d_x\ge 2\) 的整数 \(x\) ,必然存在整数 \(y\) 满足 \(x>y\) 且 \(d_y+1=d_x\) 。
也就是说,我们可以从后往前枚举 \(y\),寻找符合条件的 \(x\),若 \(x\) 存在,这个下标就是答案之一。
代码
#include <iostream>
#include <algorithm>
#include <cstring>
#include <list>
using namespace std;
const int N = 2e5 + 10;
int n, a[N], b[N], m, tr[N], dp[N], req[N], res;
list<int> ret;
inline void update(int x, int v)
{
for (; x <= m; x += (x & -x))
tr[x] = max(tr[x], v);
}
inline int query(int x)
{
int res = 0;
for (; x; x -= (x & -x))
res = max(res, tr[x]);
return res;
}
void run()
{
scanf("%d", &n);
for (int i = 1; i <= n; i++)
{
scanf("%d", a + i);
b[i] = a[i];
}
sort(b + 1, b + n + 1);
m = unique(b + 1, b + n + 1) - b - 1;
memset(tr + 1, 0, m << 2);
memset(req + 1, 0, m << 2);
for (int i = 1; i <= n; i++)
{
a[i] = lower_bound(b + 1, b + m + 1, a[i]) - b;
// printf("%d%c", a[i], " \n"[i == n]);
}
res = 0;
for (int i = 1; i <= n; i++)
{
dp[i] = query(a[i] - 1) + 1;
// printf("%d%c", dp[i], " \n"[i == n]);
res = max(res, dp[i]);
update(a[i], dp[i]);
}
ret.clear();
req[res] = 0x3f3f3f3f;
for (int i = n; i; i--)
{
if (req[dp[i]] > a[i])
ret.push_front(i), req[dp[i] - 1] = max(req[dp[i] - 1], a[i]);
}
printf("%d\n", ret.size());
for (auto &i : ret)
printf("%d ", i);
putchar('\n');
}
int main()
{
int T = 1;
scanf("%d", &T);
while (T--)
run();
}