2022-12-18 13:49阅读: 649评论: 6推荐: 2

AtCoder Beginner Contest 282

A - Generalized ABC (abc282 a)

题目大意

给定n,输出一个字符串,从'A','B','C'...拼接得到的长度为 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;
string s;
for(int i = 0; i < n; ++ i)
s += 'A' + i;
cout << s << '\n';
return 0;
}


B - Let's Get a Perfect Score (abc282 b)

题目大意

给定一个01矩阵。选两行,其每一列至少有一个 1。问满足要求的选法。

解题思路

范围不大,暴力即可。

神奇的代码
#include <bits/stdc++.h>
using namespace std;
using LL = long long;
#define FOR(i, x, y) for (decay<decltype(y)>::type i = (x), _##i = (y); i < _##i; ++i)
#define FORD(i, x, y) for (decay<decltype(x)>::type i = (x), _##i = (y); i > _##i; --i)
int main(void) {
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
int n, m;
cin >> n >> m;
vector<string> qwq(n);
for(auto &i : qwq)
cin >> i;
int ans = 0;
auto check = [&](int x, int y){
int cnt = 0;
FOR(i, 0, m){
cnt += (qwq[x][i] == 'o' || qwq[y][i] == 'o');
}
return cnt == m;
};
FOR(i, 0, n)
FOR(j, i + 1, n){
ans += check(i, j);
}
cout << ans << '\n';
return 0;
}


C - String Delimiter (abc282 c)

题目大意

给定一个字符串,要求把非"括起来的,换成.

解题思路

模拟即可。

神奇的代码
#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;
string s;
cin >> n >> s;
bool ok = false;
for(auto &i : s){
if (i == '\"')
ok ^= 1;
else if (i == ',' && !ok)
i = '.';
}
cout << s << '\n';
return 0;
}


D - Make Bipartite 2 (abc282 d)

题目大意

给定一张n个点的无向图,给其加一条边,满足其是二分图。问满足该条件的方案数。

解题思路

注意原图不一定连通。

对原图进行黑白染色,如果颜色冲突则答案为0

否则,对于一个连通块内,假设点数为cnt,边数为 cntm,染了w个白点和 b个黑点,则满足要求的方案数为 wbcntm。该连通块还能连向其他连通块,且可以任意连,其方案数为cnt×(ncnt)

对所有连通块累计方案数即为答案。

神奇的代码
#include <bits/stdc++.h>
using namespace std;
using LL = long long;
#define FOR(i, x, y) for (decay<decltype(y)>::type i = (x), _##i = (y); i < _##i; ++i)
#define FORD(i, x, y) for (decay<decltype(x)>::type i = (x), _##i = (y); i > _##i; --i)
int main(void) {
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
int n, m;
cin >> n >> m;
vector<vector<int>> edge(n + 1);
vector<int> deep(n + 1, -1);
FOR(i, 0, m){
int u, v;
cin >> u >> v;
edge[u].push_back(v);
edge[v].push_back(u);
}
LL ans = 0;
vector<int> col(2, 0);
bool ok = true;
int cnt = 0;
set<pair<int, int>> ee;
function<void(int, int, int)> dfs = [&](int u, int fa, int cc){
deep[u] = cc;
col[cc] ++;
++ cnt;
for(auto &v : edge[u]){
if (v == fa)
continue;
ee.insert({u, v});
ee.insert({v, u});
if (deep[v] != -1){
if (deep[v] == deep[u])
ok = false;
}else
dfs(v, u, cc ^ 1);
}
};
int block = 0;
FOR(i, 1, n + 1){
if (deep[i] == -1){
++ block;
cnt = 0;
col[0] = col[1] = 0;
ee.clear();
dfs(i, i, 0);
int cntm = ee.size() / 2;
ans += 1ll * col[0] * col[1] - cntm;
ans += 1ll * cnt * (n - cnt);
n -= cnt;
}
}
if (!ok)
ans = 0;
cout << ans << '\n';
return 0;
}


E - Choose Two and Eat One (abc282 e)

题目大意

给定n个数,每个数范围[1,m1],每次选择两个数x,y,获得 xy+yxmodm 分数。再将其中一个数丢弃,直至剩一个数。问获得的分数最大值。

解题思路

对于每个数,可能被选择多次,其中仅一次会被干掉,其他的都能保留。一个与多个的关系与树节点的一个父亲和多个儿子非常类似。

给定一棵树,题意操作相当于每次将叶子节点去掉,同时获得叶子与父亲的边权值。

对原图跑一边最大生成树即可。

神奇的代码
#include <bits/stdc++.h>
using namespace std;
using LL = long long;
#define FOR(i, x, y) for (decay<decltype(y)>::type i = (x), _##i = (y); i < _##i; ++i)
#define FORD(i, x, y) for (decay<decltype(x)>::type i = (x), _##i = (y); i > _##i; --i)
int n, m;
LL qpower(LL a, LL b){
LL qwq = 1;
while(b){
if (b & 1)
qwq = qwq * a % m;
a = a * a % m;
b >>= 1;
}
return qwq;
}
LL calc(LL x, LL y){
return (qpower(x, y) + qpower(y, x)) % m;
}
int main(void) {
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
cin >> n >> m;
vector<vector<LL>> dis(n + 1, vector<LL>(n + 1, 0));
vector<LL> a(n + 1);
for(int i = 1; i <= n; ++ i){
cin >> a[i];
}
vector<pair<LL, pair<int, int>>> edge;
for(int i = 1; i <= n; ++ i)
for(int j = 1; j <= n; ++ j)
edge.push_back({calc(a[i], a[j]), {i, j}});
vector<int> fa(n + 1);
function<int(int)> findfa = [&](int x){
return fa[x] == x ? x : fa[x] = findfa(fa[x]);
};
iota(fa.begin(), fa.end(), 0);
sort(edge.begin(), edge.end(), [&](const pair<LL, pair<int, int>>& a, const pair<LL, pair<int, int>>& b){
return a.first > b.first;
});
LL ans = 0;
for(auto &i : edge){
int fu = findfa(i.second.first);
int fv = findfa(i.second.second);
if (fu != fv){
fa[fu] = fv;
ans += i.first;
}
}
cout << ans << '\n';
return 0;
}


F - Union of Two Sets (abc282 f)

题目大意

交互题。给定n,要求生成 m个区间,并用这些区间回答 q组询问。

每组询问给定 l,r,要求从 m个区间选出 2个区间,其并集为区间[l,r]

n4000,m500000

解题思路

其实RMQ里的ST表的做法就能达到题目所述的要求。

即为每个左端点生成长度为2的幂的区间。

设长度 len=rl+1 ,其最大的小于等于len2的幂为 x

则选择的区间为 [l,l+x1],[rx+1,r]

神奇的代码
#include <bits/stdc++.h>
using namespace std;
using LL = long long;
#define FOR(i, x, y) for (decay<decltype(y)>::type i = (x), _##i = (y); i < _##i; ++i)
#define FORD(i, x, y) for (decay<decltype(x)>::type i = (x), _##i = (y); i > _##i; --i)
inline int highbit(int x) { return 31 - __builtin_clz(x); }
int main(void) {
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
int n;
cin >> n;
map<pair<int, int>, int> area;
int m = 0;
for(int i = 1; i <= n; ++ i){
for(int j = 1; i + j - 1 <= n; j <<= 1){
++ m;
area[{i, i + j - 1}] = m;
}
}
cout << area.size() << '\n';
for(auto &i : area){
cout << i.first.first << ' ' << i.first.second << '\n';
}
cout.flush();
int q;
cin >> q;
while(q--){
int l, r;
cin >> l >> r;
int t = highbit(r - l + 1);
cout << area[{l, l + (1 << t) - 1}] << ' ' << area[{r - (1 << t) + 1, r}] << endl;
}
return 0;
}


G - Similar Permutation (abc282 g)

题目大意

定义两个排列的相似度为,差分数组下符号相同的标号数量。

给定n,k,p,问 长度为 n的相似度为 k的排列对数,对p取模。

解题思路

起初想的是从小到大插入每个数统计方案,但是一旦插入到前面的位置,就会影响到后面的相似度判断,需要记录前面的升降趋势状态数会非常大,该方向不可行。

然后考虑按一位一位填数。要知道该位填的数与上一位关系的大小关系,需要记录上一位数是多少,但这样的话也得记录目前已经填了哪些数,这样才能知道有多少种填法是小于上一个数。而已经填了哪些数这一状态是指数级的。局面一度陷入僵局。

细细一想,我们需要的状态:上一个数填的是什么,以及当前填了哪些数,是为了决定最后两个数字的大小关系(这关系到相似度是否+1),而并不关心这两个数具体是多少,我们只想知道,有多少种填法是小于,有多少种填法是大于

因此我们可以设计的状态是:当前已填的最后一个数在未填的数中排名第几位。通过这一状态,我们就知道:有多少种填法是小于,有多少种填法是大于

dp[i][j][a][b]表示我们填了 i个数,两个排列的相似度为 j, 第一个排列的最后一个数在未填数中排名为a,第二个排列的最后一个数在未填数中排名为 b的方案数。

dp[i][j][a][b]=xa,y>bdp[i1][j][x][y]+x>a,ybdp[i1][j][x][y]

dp[i][j][a][b]=xa,ybdp[i1][j1][x][y]+x>a,y>bdp[i1][j1][x][y]

即新填一个数

  • 相似度不增加,则是一小一大和一大一小两种情况
  • 相似度加一,则是一小一小和一大一大两种情况

朴素转移是O(n2),可以用二维前缀和优化成 O(1),因此总的时间复杂度为 O(n4)

神奇的代码
#include <bits/stdc++.h>
using namespace std;
using LL = long long;
const int N = 100 + 8;
int n, k, p, cur;
int dp[2][N][N][N], sum[N][N];
int main(void) {
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
cin >> n >> k >> p;
cur = 0;
for(int i = 1; i <= n; ++ i)
for(int j = 1; j <= n; ++ j)
dp[cur][0][i][j] = 1;
for(int i = 2; i <= n; ++ i){
cur ^= 1;
memset(dp[cur], 0, sizeof(dp[cur]));
for(int j = 0; j <= k; ++ j){
int up = n - i + 2; // 未填数数量+1,即排名的最大值
for(int a = 1; a <= up; ++ a)
for(int b = 1; b <= up; ++ b){
sum[a][b] = (0ll + sum[a - 1][b] + sum[a][b - 1] - sum[a - 1][b - 1] + dp[cur ^ 1][j][a][b]) % p;
if (sum[a][b] < 0)
sum[a][b] += p;
}
for(int a = 1; a <= up; ++ a)
for(int b = 1; b <= up; ++ b){
dp[cur][j][a][b] = (0ll + dp[cur][j][a][b] + sum[a][up] - sum[a][b] + sum[up][b] - sum[a][b]) % p;
if (dp[cur][j][a][b] < 0)
dp[cur][j][a][b] += p;
dp[cur][j + 1][a][b] = (0ll + dp[cur][j + 1][a][b] + sum[a][b] + sum[up][up] - sum[a][up] - sum[up][b] + sum[a][b]) % p;
if (dp[cur][j + 1][a][b] < 0)
dp[cur][j + 1][a][b] += p;
}
}
}
cout << dp[cur][k][1][1] << '\n';
return 0;
}


Ex - Min + Sum (abc282 h)

题目大意

给定两个长度为n的数组A,B和一个整数s。问有多少对 (l,r),满足

minlirAi+i=lrBis

解题思路

条件有两项,我们先考虑第一项。

考虑a[i]作为其区间的最小值,那么该区间 [l,r]需要满足一定条件。

用单调栈预处理 L[i],R[i]数组分别表示 a[i]左边第一个(最靠近的)小于等于 a[i]的下标,以及a[i]右边第一个小于 a[i]的下标, 那么满足L[i]<lir<R[i]的区间 [l,r]的最小值都是 a[i],这样就确定好了第一项。

考虑和式,首先通过前缀和将和式变成两数相减的形式。因为前缀和是单调递增的,如果我们枚举l,通过二分可以找到符合条件的 r,同理枚举 r,通过二分也能找到符合条件的 l

lr的枚举个数(即ilri的大小),取较小的那边枚举二分求解即可。咋一看这做法的复杂度可能有O(n2logn)的量级,但可以证明这样的复杂度其实是O(nlog2n)

考虑全局最小值,其枚举范围是整个数组。考虑完后该最小值将整个数组切分成两部分,然后递归考虑左边的最小值和右边的最小值。这里有分治的想法,这里我们主要关注合并。

考察每个端点被枚举的次数。因为它所在的区间是长度较小的,当枚举完后,下一个枚举到它的区间的长度(左右合并成一个大区间)至少翻倍,最多翻倍log次就到长度为 n了。所以每个端点被枚举的次数是 log次,加上每次枚举的二分有一个log。因此总的时间复杂度为 O(nlog2n)

神奇的代码
#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;
LL s;
cin >> n >> s;
vector<LL> a(n + 1), b(n + 1);
for(int i = 1; i <= n; ++ i)
cin >> a[i];
for(int i = 1; i <= n; ++ i)
cin >> b[i];
vector<LL> sumb(n + 1);
partial_sum(b.begin() + 1, b.end(), sumb.begin() + 1);
vector<int> l(n + 1), r(n + 1);
stack<int> pos;
for(int i = 1; i <= n; ++ i){
while(!pos.empty() && a[pos.top()] >= a[i]){
r[pos.top()] = i - 1;
pos.pop();
}
pos.push(i);
}
while(!pos.empty()){
r[pos.top()] = n;
pos.pop();
}
for(int i = n; i >= 1; -- i){
while(!pos.empty() && a[pos.top()] > a[i]){
l[pos.top()] = i + 1;
pos.pop();
}
pos.push(i);
}
while(!pos.empty()){
l[pos.top()] = 1;
pos.pop();
}
auto solve = [&](int l, int mid, int r){
LL cnt = 0;
int len1 = mid - l + 1;
int len2 = r - mid + 1;
vector<LL>::iterator sb = sumb.begin(), st = sb + l - 1, midd = sb + mid, en = sb + r + 1;
LL up = s - a[mid];
if (len1 < len2){
for(auto it = st; it != midd; it = next(it)){
auto pos = upper_bound(midd, en, up + *it);
cnt += pos - midd;
}
}else{
for(auto it = midd; it != en; it = next(it)){
auto pos = lower_bound(st, midd, *it - up);
cnt += midd - pos;
}
}
return cnt;
};
LL ans = 0;
for(int i = 1; i <= n; ++ i){
ans += solve(l[i], i, r[i]);
}
cout << ans << '\n';
return 0;
}


本文作者:~Lanly~

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

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

posted @   ~Lanly~  阅读(649)  评论(6编辑  收藏  举报
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
点击右上角即可分享
微信分享提示
💬
评论
📌
收藏
💗
关注
👍
推荐
🚀
回顶
收起
  1. 1 404 not found REOL
404 not found - REOL
00:00 / 00:00
An audio error has occurred.