2023-01-28 23:24阅读: 754评论: 1推荐: 3

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[0,|T|] ,问ST是否匹配

其中 SS串的前 x个字符和后 |T|x个字符组成。

两个字符串匹配,当且仅当将其中的 ?替换成英文字母后,两个字符串相同。

解题思路

两个字符串匹配,当且仅当每一位要么是相同字母,要么至少有一个?。于是我们可以储存所有不满足这些条件的位置。当且仅当该位置的数量为0时,两个字符串匹配。

注意到当x递增的时候,前后两个 S仅有一个位置不同,因此我们可以继承上一个状态,仅判断变化的位置能否匹配即可。

神奇的代码
#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个字符串,对于每个字符串 si,问 maxLCP(si,sj)ij,其中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个点的树,在所有2n1的非空点集中,回答下列问题:

对于i[1,n] ,有多少个点集所形成的连通块个数恰好是i

数量对 998244353取模。

解题思路

当设dp[u][i]表示以 u为根的子树,能形成连通块数量恰好为 i的,点集数时,会发现在合并子树的时候,如果点u和子树节点都选择的时候,连通块个数会减一,其余情况都不会,因此还要加上是否选择u的状态。

dp[u][i][0/1]表示以 u为根的子树,能形成连通块数量恰好为 i的,且不选择/选择u的点集数。

合并时枚举儿子树的连通块大小以及点u和儿子的选择情况。

咋一看这样的复杂度会是O(n3),但如果每次枚举的范围都是儿子树的大小,可以证明这样的树型 dp的复杂度是 O(n2)的。

但还是一直T把第三维的长度为2vector换成了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种类型的卡,每种卡有10100张。每种卡有分数大小属性。

维护以下三种操作:

  • 1 x y,将第x种卡的分数修改为y
  • 2 x y,将第x种卡的大小修改为y
  • 3 x,选x张卡,要求最大化分数和,且每种类型的卡的数量不得超过其大小

解题思路

不考虑修改,仅考虑询问的话,很显然我们从分数大的卡贪心取,直到取了x张。由于张数随着种类的增加而越来越多,因此可以二分取的卡的种类,计算一下取的卡的数量(其实就是大小属性的前缀和)求得答案。

而如果加上操作 2,实际上是要维护下大小属性的前缀和,用线段树维护即可(线段树二分的话复杂度还是不变的)

但如果加上操作1的话,就要动态维护卡的顺序就寄了,后续再思索思索我傻了,可以事先给它预留个位置,大小0

即事先对所有可能出现的分数从大到小排序,对那些还没出现的分数大小置为 0。然后维护大小的前缀和以及分数× 大小的前缀和。

神奇的代码
#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(n364)。以及 nq只有 2e7,那就每更新一次 k后更新一下所有询问的答案即可。时间复杂度是 O(n364+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;
}


本文作者:~Lanly~

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

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

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