AtCoder Beginner Contest 287
A - Majority (abc287 a)
题目大意
给定\(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;
int ans = 0;
while(n--){
string s;
cin >> s;
ans += (s[0] == 'F') * 2 - 1;
}
if (ans > 0)
cout << "Yes" << '\n';
else
cout << "No" << '\n';;
return 0;
}
B - Postal Card (abc287 b)
题目大意
给定\(n\)个字符串和 \(m\)个模板。
问有多少个字符串的后缀包含在这 \(m\)个模板内。
解题思路
用set
储存这些模板,然后对于每个字符串直接查找即可。
神奇的代码
#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, m;
cin >> n >> m;
vector<string> s(n);
for(auto &i : s)
cin >> i;
set<string> f;
for(int i = 0; i < m; ++ i){
string s;
cin >> s;
f.insert(s);
}
int ans = count_if(s.begin(), s.end(), [&](string& a){
return f.count(a.substr(3)) > 0;
});
cout << ans << '\n';
return 0;
}
C - Path Graph? (abc287 c)
题目大意
给定一张图,问是不是一个链。
解题思路
链的性质就是:
- 一个连通块
- 两个点度数为\(1\),其余点度数为 \(2\)。
神奇的代码
#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, m;
cin >> n >> m;
vector<int> du(n, 0);
vector<int> fa(n, 0);
iota(fa.begin(), fa.end(), 0);
function<int(int)> findfa = [&](int x){
return fa[x] == x ? x : fa[x] = findfa(fa[x]);
};
for(int i = 0; i < m; ++ i){
int u, v;
cin >> u >> v;
-- u;
-- v;
du[u] ++;
du[v] ++;
int fu = findfa(u);
int fv = findfa(v);
if (fu != fv)
fa[fu] = fv;
}
int one = count(du.begin(), du.end(), 1);
int two = count(du.begin(), du.end(), 2);
int block = 0;
for(int i = 0; i < n; ++ i)
block += (fa[i] == i);
if (one == 2 && two == n - 2 && block == 1)
cout << "Yes" << '\n';
else
cout << "No" << '\n';
return 0;
}
D - Match or Not (abc287 d)
题目大意
给定两个由小写字母和?
组成的字符串\(S,T\),
对于 \(x \in [0,|T|]\) ,问\(S^\prime\)和 \(T\)是否匹配。
其中 \(S^\prime\)由 \(S\)串的前 \(x\)个字符和后 \(|T| - x\)个字符组成。
两个字符串匹配,当且仅当将其中的 ?
替换成英文字母后,两个字符串相同。
解题思路
两个字符串匹配,当且仅当每一位要么是相同字母,要么至少有一个?
。于是我们可以储存所有不满足这些条件的位置。当且仅当该位置的数量为\(0\)时,两个字符串匹配。
注意到当\(x\)递增的时候,前后两个 \(S^\prime\)仅有一个位置不同,因此我们可以继承上一个状态,仅判断变化的位置能否匹配即可。
神奇的代码
#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);
string s, t;
cin >> s >> t;
int n = s.size();
int m = t.size();
unordered_set<int> bad;
int tp = 0;
for(int i = n - m; i < n; ++ i, ++ tp){
if (s[i] != '?' && t[tp] != '?' && s[i] != t[tp])
bad.insert(tp);
}
if (bad.empty())
cout << "Yes" << '\n';
else
cout << "No" << '\n';
for(int i = 0; i < m; ++ i){
bad.erase(i);
if (s[i] != '?' && t[i] != '?' && s[i] != t[i])
bad.insert(tp);
if (bad.empty())
cout << "Yes" << '\n';
else
cout << "No" << '\n';
}
return 0;
}
E - Karuta (abc287 e)
题目大意
给定\(n\)个字符串,对于每个字符串 \(s_i\),问 \(\max LCP(s_i, s_j)_{i \neq j}\),其中\(LCP\)是最长公共前缀。
解题思路
注意到最大的最长公共前缀一定在字典序上前后两个的字符串之间,因此将这\(n\)个字符串按字典序排序,求每个字符串与其相邻的字符串的\(LCP\),取最大值即可。
神奇的代码
#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<string> s(n);
for(auto &i : s)
cin >> i;
vector<int> id(n);
iota(id.begin(), id.end(), 0);
sort(id.begin(), id.end(), [&](int x, int y){
return s[x] < s[y];
});
vector<int> ans(n);
auto LCP = [&](string& l, string& r){
int tmp = 0;
while(tmp < l.size() && tmp < r.size() && l[tmp] == r[tmp])
++ tmp;
return tmp;
};
for(int i = 0; i < n - 1; ++ i){
int l = id[i], r = id[i + 1];
int tmp = LCP(s[l], s[r]);
ans[l] = max(ans[l], tmp);
ans[r] = max(ans[r], tmp);
}
for(auto &i : ans)
cout << i << '\n';
return 0;
}
貌似还可以建一棵Trie
树来求解。
F - Components (abc287 f)
题目大意
给定一棵有\(n\)个点的树,在所有\(2^n - 1\)的非空点集中,回答下列问题:
对于\(i \in [1,n]\) ,有多少个点集所形成的连通块个数恰好是\(i\)。
数量对 \(998244353\)取模。
解题思路
当设\(dp[u][i]\)表示以 \(u\)为根的子树,能形成连通块数量恰好为 \(i\)的,点集数时,会发现在合并子树的时候,如果点\(u\)和子树节点都选择的时候,连通块个数会减一,其余情况都不会,因此还要加上是否选择
点 \(u\)的状态。
设\(dp[u][i][0/1]\)表示以 \(u\)为根的子树,能形成连通块数量恰好为 \(i\)的,且不选择
/选择
点 \(u\)的点集数。
合并时枚举儿子树的连通块大小以及点\(u\)和儿子的选择情况。
咋一看这样的复杂度会是\(O(n^3)\),但如果每次枚举的范围都是儿子树的大小,可以证明这样的树型 \(dp\)的复杂度是 \(O(n^2)\)的。
但还是一直T把第三维的长度为\(2\)的 vector
换成了array
就过了(?
神奇的代码
#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;
cin >> n;
vector<vector<int>> edge(n);
for(int i = 1; i < n; ++ i){
int u, v;
cin >> u >> v;
-- u;
-- v;
edge[u].push_back(v);
edge[v].push_back(u);
}
vector<vector<array<int, 2>>> dp(n, vector<array<int, 2>>(n + 1, array<int, 2>{0, 0}));
function<int(int, int)> dfs = [&](int u, int fa){
int sz = 1;
dp[u][1][1] = 1;
dp[u][0][0] = 1;
for(auto &v : edge[u]){
if (v == fa)
continue;
int sonsz = dfs(v, u);
vector<array<int, 2>> tmp(n + 1, array<int, 2>{0, 0});
for(int i = 0; i <= sz; ++ i){
for(int j = 0; j <= sonsz; ++ j){
tmp[i + j][1] = (tmp[i + j][1] + 1ll * dp[u][i][1] * dp[v][j][0] % mo) % mo;
if (i + j - 1 >= 0)
tmp[i + j - 1][1] = (tmp[i + j - 1][1] + 1ll * dp[u][i][1] * dp[v][j][1] % mo) % mo;
tmp[i + j][0] = (tmp[i + j][0] + 1ll * dp[u][i][0] * (dp[v][j][1] + dp[v][j][0]) % mo) % mo;
}
}
dp[u].swap(tmp);
sz += sonsz;
}
return sz;
};
dfs(0, 0);
for(int i = 1; i <= n; ++ i)
cout << (dp[0][i][0] + dp[0][i][1]) % mo << '\n';
return 0;
}
G - Balance Update Query (abc287 g)
题目大意
有\(N\)种类型的卡,每种卡有\(10^{100}\)张。每种卡有分数
和大小
属性。
维护以下三种操作:
1 x y
,将第\(x\)种卡的分数
修改为\(y\)。2 x y
,将第\(x\)种卡的大小
修改为\(y\)。3 x
,选\(x\)张卡,要求最大化分数和,且每种类型的卡的数量不得超过其大小
。
解题思路
不考虑修改,仅考虑询问的话,很显然我们从分数
大的卡贪心取,直到取了\(x\)张。由于张数随着种类的增加而越来越多,因此可以二分取的卡的种类,计算一下取的卡的数量(其实就是大小
属性的前缀和)求得答案。
而如果加上操作 \(2\),实际上是要维护下大小
属性的前缀和,用线段树维护即可(线段树二分的话复杂度还是不变的)
但如果加上操作\(1\)的话,就要动态维护卡的顺序就寄了,后续再思索思索我傻了,可以事先给它预留个位置,大小
为\(0\)。
即事先对所有可能出现的分数
从大到小排序,对那些还没出现的分数
的大小
置为 \(0\)。然后维护大小
的前缀和以及分数
\(\times\) 大小
的前缀和。
神奇的代码
#include <bits/stdc++.h>
using namespace std;
using LL = long long;
const int N = 4e5 + 8;
class Segment{
#define lson (root << 1)
#define rson (root << 1 | 1)
LL cnt[N << 2], sum[N << 2];
public:
void add(int root, int l, int r, int pos, LL val, LL a){
if (l == r){
cnt[root] += val;
sum[root] += val * a;
return;
}
int mid = (l + r) >> 1;
if (pos <= mid)
add(lson, l, mid, pos, val, a);
else
add(rson, mid + 1, r, pos, val, a);
cnt[root] = cnt[lson] + cnt[rson];
sum[root] = sum[lson] + sum[rson];
}
struct Res{
int pos;
LL cnt;
LL sum;
};
Res query(int root, int l, int r, int k){
if (l == r){
if (cnt[root] <= k){
return {r, cnt[root], sum[root]};
}else{
return {r - 1, 0, 0};
}
}
int mid = (l + r) >> 1;
if (cnt[lson] <= k){
auto res = query(rson, mid + 1, r, k - cnt[lson]);
res.cnt += cnt[lson];
res.sum += sum[lson];
return res;
}else{
return query(lson, l, mid, k);
}
}
}seg;
int main(void) {
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
int n;
cin >> n;
vector<int> tmp, a(n), b(n);
for(int i = 0; i < n; ++ i){
cin >> a[i] >> b[i];
tmp.push_back(a[i]);
}
int q;
cin >> q;
vector<array<int, 3>> op(q);
for(auto &[o, x, y] : op){
cin >> o >> x;
if (o == 3)
y = 0;
else {
-- x;
cin >> y;
}
if (o == 1)
tmp.push_back(y);
}
sort(tmp.begin(), tmp.end(), greater<int>());
auto get_rank = [&](int val){
return lower_bound(tmp.begin(), tmp.end(), val, greater<int>()) - tmp.begin() + 1;
};
for(int i = 0; i < n; ++ i){
int pos = get_rank(a[i]);
seg.add(1, 1, tmp.size(), pos, b[i], a[i]);
}
for(auto &[o, x, y] : op){
if (o == 1){
int pos = get_rank(a[x]);
seg.add(1, 1, tmp.size(), pos, -b[x], a[x]);
a[x] = y;
pos = get_rank(a[x]);
seg.add(1, 1, tmp.size(), pos, b[x], a[x]);
}else if (o == 2){
int pos = get_rank(a[x]);
seg.add(1, 1, tmp.size(), pos, -b[x], a[x]);
b[x] = y;
seg.add(1, 1, tmp.size(), pos, b[x], a[x]);
}else{
auto [pos, cnt, sum] = seg.query(1, 1, tmp.size(), x);
if (pos == tmp.size() && cnt < x)
cout << -1 << '\n';
else{
cout << sum + (x - cnt) * tmp[pos] << '\n';
}
}
}
return 0;
}
Ex - Directed Graph and Query (abc287 h)
题目大意
给定一张有向图,回答\(q\)组询问。
每组询问包括 \(s,t\),问从 \(s\)到点 \(t\)的路径的代价最小值。
一条路径的代价是其经过的点的编号的最大值。
解题思路
如果是无向图,可以对原图跑一棵最小生成树,边权就是两点的点编号较大的那个。
然后每次询问其最大的,连接了这两个点的点,就是Kruscal
重构树中两点的LCA
。
但这是有向图再思索思索,有向图的问题就是最小生成树的生成办法不太一样。但其本质的思想还是从编号小的点开始考虑。
注意到floyd
算法中的第一层循环的含义就是仅考虑\(1..k\)号点时,各个点之间的距离,这个距离
可以换成连通性
,这样可以用 bitset
优化运算,复杂度是\(O(\frac{n^3}{64})\)。以及 \(nq\)只有 \(2e7\),那就每更新一次 \(k\)后更新一下所有询问的答案即可。时间复杂度是 \(O(\frac{n^3}{64} + nq)\)
神奇的代码
#include <bits/stdc++.h>
using namespace std;
using LL = long long;
const int N = 2e3 + 1;
int main(void) {
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
int n, m;
cin >> n >> m;
vector<bitset<N>> ok(n);
for(int i = 0; i < m; ++ i){
int u, v;
cin >> u >> v;
-- u;
-- v;
ok[u][v] = 1;
}
for(int i = 0; i < n; ++ i)
ok[i][i] = 1;
int q;
cin >> q;
vector<int> ans(q);
vector<array<int, 2>> query(q);
for(auto &[u, v] : query){
cin >> u >> v;
-- u;
-- v;
}
for(int k = 0; k < n; ++ k){
for(int i = 0; i < n; ++ i){
if (ok[i][k])
ok[i] |= ok[k];
}
for(int i = 0; i < q; ++ i){
auto &[u, v] = query[i];
if (ans[i] == 0 && ok[u][v])
ans[i] = k;
}
}
for(int i = 0; i < q; ++ i){
auto &[u, v] = query[i];
if (ans[i] == 0)
cout << -1 << '\n';
else
cout << max({u, v, ans[i]}) + 1 << '\n';
}
return 0;
}