比赛链接:
https://vjudge.net/contest/510447
A - Theramore
题意:
给定一个 01 字符串,可以选择奇数长度的区间进行翻转,问字符串字典序最小是多少。
思路:
因为选择的是奇数长度,所以每个 01 的奇偶性是不变的,最小就是奇数和偶数位置的 1 分别移动到最后面。
代码:
#include <bits/stdc++.h>
using namespace std;
#define LL long long
void solve(){
string s;
cin >> s;
LL n = s.size();
vector <LL> cnt(2);
for (int i = 0; i < n; i ++ ){
cnt[i & 1] += (s[i] == '1');
}
for (int i = n - 1; i >= 0; i -- ){
if (cnt[i & 1]){
cnt[i & 1] -- ;
s[i] = '1';
}
else{
s[i] = '0';
}
}
cout << s << "\n";
}
int main(){
ios::sync_with_stdio(false);cin.tie(0);
LL T = 1;
cin >> T;
while(T -- ){
solve();
}
return 0;
}
B - Darkmoon Faire
题意:
对于一个区间,如果它的最大值在奇数位置且最小值在偶数位置,那么称它是 的。
给定一个序列 ,问有多少种分法可以使得每段区间都是 的。
思路:
定义 表示将 划分为每段都是 的方案数。
容易想到暴力做法 ,当 是 的, 就可以从 转移过来。
找到一个区间的最大值/最小值的位置,可以通过单调栈实现。如果最大值在第 位,那么所有满足奇偶性与 相同的 都是符合最大值的要求的,对于最小值也是一样,当一个位置最大值和最小值的都是符合要求的,它就可以从前面的方案转移过来。
可以通过两棵线段树维护奇数/偶数位置上的信息。用 去记录奇数/偶数位置上的方案数,用 去记录奇数/偶数位置上是否满足最大值和最小值的要求。
最大值的 的更新就直接更新在当前位置对应的奇偶位置,最小值要取反,因为一段区间中最小值要在偶数位置上,与最大值是相反的。
代码:
#include <bits/stdc++.h>
using namespace std;
#define LL long long
const int P = 998244353, N = 3e5 + 10;
LL n, a[N], dp[N], top1, top2, stk_max[N], stk_min[N];
struct SegmentTree{
struct node{
LL l, r, sum, add, mx;
}tr[N << 2];
void pushup(LL u){
tr[u].mx = max(tr[u << 1].mx, tr[u << 1 | 1].mx);
tr[u].sum = 0;
if (tr[u].mx == tr[u << 1].mx)
tr[u].sum = (tr[u].sum + tr[u << 1].sum) % P;
if (tr[u].mx == tr[u << 1 | 1].mx)
tr[u].sum = (tr[u].sum + tr[u << 1 | 1].sum) % P;
}
void pushdown(LL u){
auto &root = tr[u], &left = tr[u << 1], &right = tr[u << 1 | 1];
left.add = (left.add + root.add) % P;
left.mx = (left.mx + root.add) % P;
right.add = (right.add + root.add) % P;
right.mx = (right.mx + root.add) % P;
root.add = 0;
}
void build(LL u, LL l, LL r){
if (l == r){
tr[u] = {l, r, 0, 0, 0};
return;
}
tr[u] = {l, r, 0, 0, 0};
LL mid = (l + r) >> 1;
build(u << 1, l, mid);
build(u << 1 | 1, mid + 1, r);
pushup(u);
}
void update(LL u, LL p, LL k){
if (tr[u].l == tr[u].r){
tr[u].sum = k;
return;
}
LL mid = (tr[u].l + tr[u].r) >> 1;
pushdown(u);
if (p <= mid) update(u << 1, p, k);
else update(u << 1 | 1, p, k);
pushup(u);
}
void update(LL u, LL l, LL r, LL k){
if (tr[u].l >= l && tr[u].r <= r){
tr[u].mx = (tr[u].mx + k) % P;
tr[u].add = (tr[u].add + k) % P;
}
else {
pushdown(u);
LL mid = (tr[u].l + tr[u].r) >> 1;
if (l <= mid) update(u << 1, l, r, k);
if (r > mid) update(u << 1 | 1, l, r, k);
pushup(u);
}
}
}segt[2];
void solve(){
cin >> n;
for (int i = 1; i <= n; i ++ )
cin >> a[i];
segt[0].build(1, 1, n);
segt[1].build(1, 1, n);
top1 = top2 = 0;
dp[0] = 1;
for (int i = 1; i <= n; i ++ ){
segt[i & 1].update(1, i, dp[i - 1]);
while(top1 && a[stk_max[top1]] < a[i]){
segt[stk_max[top1] & 1].update(1, stk_max[top1 - 1] + 1, stk_max[top1], -1);
top1 -- ;
}
stk_max[ ++ top1] = i;
segt[i & 1].update(1, stk_max[top1 - 1] + 1, stk_max[top1], 1);
while(top2 && a[stk_min[top2]] > a[i]){
segt[(stk_min[top2] & 1) ^ 1].update(1, stk_min[top2 - 1] + 1, stk_min[top2], -1);
top2 -- ;
}
stk_min[ ++ top2] = i;
segt[(i & 1) ^ 1].update(1, stk_min[top2 - 1] + 1, stk_min[top2], 1);
dp[i] = 0;
if (segt[0].tr[1].mx == 2)
dp[i] = (dp[i] + segt[0].tr[1].sum) % P;
if (segt[1].tr[1].mx == 2)
dp[i] = (dp[i] + segt[1].tr[1].sum) % P;
}
cout << dp[n] << "\n";
}
int main(){
ios::sync_with_stdio(false);cin.tie(0);
LL T = 1;
cin >> T;
while(T -- ){
solve();
}
return 0;
}
D - Quel'Thalas
题意:
二维平面上有横纵坐标都是 的整数点,问至少用几条不经过 (0, 0) 的直线能经过所有点。
思路:
就是 2 * 。
代码:
#include <bits/stdc++.h>
using namespace std;
#define LL long long
void solve(){
LL n;
cin >> n;
cout << 2 * n << "\n";
}
int main(){
ios::sync_with_stdio(false);cin.tie(0);
LL T = 1;
cin >> T;
while(T -- ){
solve();
}
return 0;
}
E - Ironforge
题意:
有 个点,第 个点的值为 ,第 个点和第 个点之间有边相连,边的权值为一个质数 ,初始可以选择一个点,可以获得这个点上值的所有质因数,当自己已有的质因数中包含边上的数时,可以走这条边,每走到一个点都可以或者这个点上的数的所有质因子, 次询问,每次问能不能从第点 走到点 。
思路:
记第 个点向左最远能走到第 ,向右最远能走到 。
对于每个点,先考虑它向右能走到多远的地方,通过二分判断,能不能走这条边。如果能走的区间中包含了这个边的质数,说明这条边能走,所以要预处理出每个质数对应的点有哪些。
接下来往左走,从小到大处理,如果左边的点能够走到现在这个点,那么现在这个点的 其实就不用处理了,用之前即可。
如果走不到,那就暴力处理,先向左走,再向右走,直到该点能走的区间不发生变化的时候,结束处理。
代码:
#include <bits/stdc++.h>
using namespace std;
#define LL long long
const int N = 2e5 + 10;
LL n, m, a[N], b[N], L[N], R[N];
vector <LL> pos[N];
bool check(LL p, LL l, LL r){
if (!pos[p].size() || pos[p].back() < l) return false;
LL x = *lower_bound(pos[p].begin(), pos[p].end(), l);
return x <= r;
}
void solve(){
cin >> n >> m;
for (int i = 1; i <= 200000; i ++ )
pos[i].clear();
for (int i = 1; i <= n; i ++ )
cin >> a[i];
for (int i = 1; i < n; i ++ )
cin >> b[i];
for (int i = 1; i <= n; i ++ ){
LL x = a[i];
for (int j = 2; j * j <= x; j ++ ){
if (x % j == 0){
while(x % j == 0){
x /= j;
}
pos[j].push_back(i);
}
}
if (x > 1) pos[x].push_back(i);
}
for (int i = n; i >= 1; i -- ){
R[i] = i;
while(R[i] < n && check(b[R[i]], i, R[i])){
R[i] = R[R[i] + 1];
}
}
for (int i = 1; i <= n; i ++ ){
L[i] = i;
if (i > 1 && R[i - 1] >= i){
if (check(b[i - 1], i, R[i])){
L[i] = L[i - 1];
R[i] = R[i - 1];
}
}
else{
while(1){
bool flag = false;
while(L[i] > 1 && check(b[L[i] - 1], L[i], R[i])){
flag = true;
L[i] = L[L[i] - 1];
}
while(R[i] < n && check(b[R[i]], L[i], R[i])){
flag = true;
R[i] = R[R[i] + 1];
}
if (!flag) break;
}
}
}
while(m -- ){
LL x, y;
cin >> x >> y;
if (L[x] <= y && y <= R[x]){
cout << "Yes\n";
}
else{
cout << "No\n";
}
}
}
int main(){
ios::sync_with_stdio(false);cin.tie(0);
LL T = 1;
cin >> T;
while(T -- ){
solve();
}
return 0;
}
G - Darnassus
题意:
有 个点,第 个点的值为 ,序列 为一个排列,连接点 和点 的花费为 ,问连接所有点的最小花费为多少。
思路:
容易想到最小生成树,将所有边排列后跑 显然会超时,考虑一个特殊情况,连接第 和第 个点,那么每条边的花费都小于 。
即 < ,所以 和 中必有一个小于 ,所以枚举满足条件的边,花费 的时间,接着跑 。
PS:数组开 是因为 为 224,可能插入两次(具体可以看代码),所以要 448 以上。
代码:
#include <bits/stdc++.h>
using namespace std;
#define LL long long
const int N = 5e4 + 10;
int n, fa[N], p[N], pos[N], head[N], tot;
struct Edge{ //要开 int,开 long long 会 MLE
int u, v, nxt;
}e[N * 450];
void add(int u, int v, int w){ //将边 (u, v) 放到长度 w 中,即所有长度相同的边存在一起
e[++ tot].u = u;
e[tot].v = v;
e[tot].nxt = head[w];
head[w] = tot;
}
int get(int x){
return (x == fa[x] ? x : (fa[x] = get(fa[x])));
}
void solve(){
cin >> n;
for (int i = 1; i <= n; i ++ ){
cin >> p[i];
fa[i] = i;
pos[p[i]] = i;
head[i] = 0;
}
int k = sqrt(n);
tot = 0;
for (int i = 1; i <= n; i ++ ){
for (int j = i + 1; j <= min(i + k, n); j ++ ){
LL w = (j - i) * abs(p[i] - p[j]); //将 i 和 j 相连
if (w < n){
add(i, j, w);
}
w = abs(pos[i] - pos[j]) * (j - i); //将 pos[i] 和 pos[j] 相连
if (w < n){
add(pos[i], pos[j], w);
}
}
}
LL ans = 0;
int cnt = 0;
for (int i = 1; i < n; i ++ ){ //长度由小到大枚举
for (int j = head[i]; j; j = e[j].nxt){
LL u = get(e[j].u), v = get(e[j].v);
if (u != v){
fa[u] = v;
ans += i;
cnt ++ ;
}
}
if (cnt == n - 1) break;
}
cout << ans << "\n";
}
int main(){
ios::sync_with_stdio(false);cin.tie(0);
LL T = 1;
cin >> T;
while(T -- ){
solve();
}
return 0;
}
H - Orgrimmar
题意:
给定一棵树,从中选择若干个点,使得每个点最多连一条边,问最多能选多少个点。
思路:
容易想到树形 。
定义 表示不选 ,选择 作为起点,选择 作为终点情况下能选择的最多点的数量。
这里的起点指选择的这个点前面没有边相连,终点指前面有边相连。
代码:
#include <bits/stdc++.h>
using namespace std;
#define LL long long
void solve(){
LL n;
cin >> n;
vector < vector<LL> > e(n + 1);
for (int i = 0; i < n - 1; i ++ ){
LL u, v;
cin >> u >> v;
e[u].push_back(v);
e[v].push_back(u);
}
vector < array<LL, 3> > dp(n + 1);
LL ans = 0;
function<void(LL, LL)> dfs = [&](LL u, LL fa){
dp[u][0] = 0; //不选
dp[u][1] = 1; //起点
dp[u][2] = 0; //终点
LL sum0 = 0, sum1 = 0;
for (auto v : e[u]){
if (v == fa) continue;
dfs(v, u);
sum0 += max(dp[v][0], max(dp[v][1], dp[v][2]));
sum1 += dp[v][0];
}
dp[u][0] = max(dp[u][0], sum0); //不选 u 的话,所有相连的节点不论什么状态都可以
dp[u][1] = max(dp[u][1], sum1 + 1); //选了 u 作为起点,那么所有相连的节点都不能选
for (auto v : e[u]){ //找最大的方案
if (v == fa) continue;
dp[u][2] = max(dp[u][2], sum1 - dp[v][0] + dp[v][1] + 1); //选 u 作为终点,那么要找一个起点
}
for (int i = 0; i < 3; i ++ )
ans = max(ans, dp[u][i]);
};
dfs(1, 0);
cout << ans << "\n";
}
int main(){
ios::sync_with_stdio(false);cin.tie(0);
int size(512<<20);
__asm__ ( "movq %0, %%rsp\n"::"r"((char*)malloc(size)+size));
LL T = 1;
cin >> T;
while(T -- ){
solve();
}
exit(0);
}
K - Stormwind
题意:
有一个 的矩形,可以沿水平或者竖直方向画线,将矩形分成若干个小矩形,问每块小矩形的面积都 >= 的情况下最多能划几条线。
思路:
枚举小矩形的长,可以得到宽,然后取画线最多的方案即可。
代码:
#include <bits/stdc++.h>
using namespace std;
#define LL long long
void solve(){
LL n, m, k;
cin >> n >> m >> k;
LL ans = 0;
for (int x = 1; x <= k; x ++ ){
LL y = (k + x - 1) / x;
if (x > n || y > m) continue;
ans = max(ans, n / x + m / y - 2);
}
cout << ans << "\n";
}
int main(){
ios::sync_with_stdio(false);cin.tie(0);
LL T = 1;
cin >> T;
while(T -- ){
solve();
}
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· 从HTTP原因短语缺失研究HTTP/2和HTTP/3的设计差异
· 三行代码完成国际化适配,妙~啊~