2025-11-17 ZYZ28-NOIP模拟赛-Round7 hetao1733837的record
好累😩
比赛链接:ZYZ28-NOIP模拟赛-Round7 - ZYZOJ
A.pmst
( ‵▽′ ) ψ ( ‵▽′)ψ (‵▽′)ψ l z lz lz竟然把原来的 T 2 T2 T2塞进了 T 1 T1 T1!是想坑谁?还好我盒到了原。
原题链接:Darnassus
提交链接:07-A - ZYZOJ
分析
思路还是比较简单的,最开始思考方向也是正确的,及由于是排列,差值的绝对值取在 1 1 1到 n − 1 n-1 n−1,连接 ( i , i + 1 ) (i,i+1) (i,i+1)的边,其边权一定小于 n n n。从极限的角度考虑, ∣ p i − p j ∣ ≤ n |p_i-p_j|\le \sqrt{n} ∣pi−pj∣≤n, ∣ i − j ∣ ≤ n |i-j|\le \sqrt{n} ∣i−j∣≤n,那么,每个点 O ( n ) O(n) O(n)建图,全局 O ( n 2 ) O(n^2) O(n2)建图就可以降到 O ( n ) O(\sqrt{n}) O(n),然后跑 K r u s k a l Kruskal Kruskal复杂度就是 O ( n n l o g n ) O(n\sqrt{n}logn) O(nnlogn)。边权范围 [ 1 , n ] [1,n] [1,n],开桶记录,不必排序,最终复杂度 O ( n n α ( n ) ) O(n\sqrt{n}\alpha(n)) O(nnα(n))。
正解
#include <bits/stdc++.h>
using namespace std;
const int N = 50005;
int n, p[N], fa[N], cnt[N];
vector<pair<int, int>> e[N];
int find(int x){
return x == fa[x] ? fa[x] : fa[x] = find(fa[x]);
}
int main(){
freopen("pmst.in", "r", stdin);
freopen("pmst.out", "w", stdout);
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
cin >> n;
for (int i = 1; i <= n; i++){
cin >> p[i];
cnt[p[i]] = i;
fa[i] = i;
}
int tmp = sqrt(n) + 1;
for (int i = 1; i < n; i++){
for (int j = i + 1; j <= min(n, i + tmp); j++){
long long w = 1ll * (j - i) * abs(p[i] - p[j]);
if (w <= n - 1)
e[w].push_back({i, j});
}
for (int j = i + 1; j <= min(n, i + tmp); j++){
long long w = 1ll * (j - i) * abs(cnt[i] - cnt[j]);
if (w <= n - 1)
e[w].push_back({cnt[i], cnt[j]});
}
}
int cnt = 0;
long long ans = 0;
for (int w = 1; w < n && cnt != n - 1; w++){
for (auto o : e[w]){
int u = find(o.first);
int v = find(o.second);
if (u == v)
continue;
fa[u] = v;
ans += w;
++cnt;
if (cnt == n - 1)
break;
}
}
cout << ans;
}
注意,QOJ本题是多测,需修改读入。
B.cardgame
这题没盒到原/(ㄒoㄒ)/~~
提交链接:07-B - ZYZOJ
题面
小 Y Y Y和小 Z Z Z在卡牌游戏中对战。小 Y Y Y有 N N N张卡牌而小 Z Z Z有 M M M张卡牌。每张卡牌均有一个由正整数表示的力量值。
每一回合,小 Y Y Y和小 Z Z Z各自展示一张卡牌,如果一名玩家的卡牌力量值大于对手的卡牌力量值,则该玩家被视为胜出此回合。如果展示的两张卡牌具有相同的力量值,则此回合被视为和局。 小 Y Y Y的第 i i i张卡牌的力量值为 A i A_i Ai。小Z的第 j j j张卡牌的力量值为 B j B_j Bj。 然后,他们将进行 N × M N \times M N×M轮的对战。他们都循环地展示卡牌。
小 Y Y Y依照以下顺序展示卡牌(共 M M M轮): A 1 → A 2 → ⋯ → A N → A 1 → A 2 → ⋯ → A N → ⋯ → A 1 → A 2 → ⋯ → A N A_1 \to A_2 \to \cdots \to A_N \to A_1 \to A_2 \to \cdots \to A_N \to \cdots \to A_1 \to A_2 \to \cdots \to A_N A1→A2→⋯→AN→A1→A2→⋯→AN→⋯→A1→A2→⋯→AN
小Z依照以下顺序展示卡牌(共 N N N轮): B 1 → B 2 → ⋯ → B M → B 1 → B 2 → ⋯ → B M → ⋯ → B 1 → B 2 → ⋯ → B M B_1 \to B_2 \to \cdots \to B_M \to B_1 \to B_2 \to \cdots \to B_M \to \cdots \to B_1 \to B_2 \to \cdots \to B_M B1→B2→⋯→BM→B1→B2→⋯→BM→⋯→B1→B2→⋯→BM
请你求出在这个过程中小 Y Y Y获胜、小 Z Z Z获胜及和局的回合数。
分析
显然思路又对了……为啥没过呢?心态浮躁?代码能力弱?思维还是不行?单纯运气不好?我不知道……但是,前段时间学习方法是绝对有问题的,一直在抄题解,没有自主思考,不热爱思考,必须纠正。
我们很容易想到,两者并不必须走 N × M N\times M N×M轮,只需走 l c m ( N , M ) lcm(N, M) lcm(N,M)轮即可,而且,相对应的位置,它们下标一定在模 g c d ( N , M ) gcd(N ,M) gcd(N,M)下同余。那么,对于每一种,排序,然后二分(实现上也可以用双指针)即可,时间复杂度 O ( ( N + M ) l o g ( N + M ) ) O((N+M)log(N+M)) O((N+M)log(N+M))。
正解
#include <bits/stdc++.h>
#define int long long
using namespace std;
int n, m;
signed main() {
freopen("cardgame.in", "r", stdin);
freopen("cardgame.out", "w", stdout);
cin >> n >> m;
vector<int> a(n), b(m);
for (auto &o: a)
cin >> o;
for (auto &o: b)
cin >> o;
int Y = 0, Z = 0;
int GCD = __gcd(n, m);
for (int mod = 0; mod < GCD; mod++) {
vector<int> A, B;
for (int j = mod; j < n; j += GCD)
A.push_back(a[j]);
for (int j = mod; j < m; j += GCD)
B.push_back(b[j]);
sort(A.begin(), A.end());
sort(B.begin(), B.end());
int pntb = 0;
for (auto x: A) {
while (pntb < (int)B.size() && B[pntb] < x)
++pntb;
Y += pntb;
}
int pnta = 0;
for (auto x: B) {
while (pnta < (int)A.size() && A[pnta] < x)
++pnta;
Z += pnta;
}
}
Y *= GCD;
Z *= GCD;
int delta = n * m - Y - Z;
cout << Y << "\n" << Z << "\n" << delta << "\n";
}
操了,场上连二分都写出来了,看了一眼std,tmd连实现都是计算两个再用总数减……
C.jump
操,死机没保存上/ll
题面
比特国由 N N N个城市构成,编号为 1 , 2 , … , N 1,2,\dots,N 1,2,…,N。 有 M M M条双向道路连接这些城市,第 i i i条连通城市 U i U_i Ui和城市 V i V_i Vi,通过这条道路需要花费 W i W_i Wi 的时间。 此外,比特国的人们还可以使用“比特跳跃”来通行于任意两个城市之间。比特跳跃所需的时间取决于两个常数 S S S和 K K K,两者均为整数。对于从城市 x x x跳跃到城市 y y y:
· S = 1 S = 1 S=1:传送需要 K × ( x & y ) K \times (x \& y) K×(x&y)单位时间( & \& &表示按位与(bitwise AND))。
· S = 2 S = 2 S=2:传送需要 K × ( x ⊕ y ) K \times (x \oplus y) K×(x⊕y)单位时间( ⊕ \oplus ⊕表示按位异或(bitwise XOR))。
· S = 3 S = 3 S=3:传送需要 K × ( x ∣ y ) K \times (x | y) K×(x∣y)单位时间( ∣ | ∣表示按位或(bitwise OR))。
请你计算从 1 1 1号城市移动到每个城市所需的最短时间。
分析
O ( n 2 ) O(n^2) O(n2)建图显然是很不错的那份手段,但不妨多想一想。
对于 N N N是 2 2 2的整数次幂,从 1 1 1跳到 N N N,代价为 0 0 0,从 N N N跳到任意点,代价均为 0 0 0,输出 N − 1 N-1 N−1个 0 0 0即可。
S=1
扩展一下,找出最大的 t t t使得 2 t − 1 ≤ N 2^t-1\le N 2t−1≤N,那么,这些点都价值为 0 0 0,剩余只需求补码即可。
但是出现了一个特例,就是说 N = 2 t + 1 − 1 N=2^{t+1}-1 N=2t+1−1,那么补码为 0 0 0,需要 O ( n ) O(n) O(n)建边,跑最短路。
S=2
对于异或,如果要从 a a a到达 b b b,若两者之间某一位二进制位不同,则可以通过 a ⊕ 2 ? a\oplus 2^? a⊕2?解决。同理,所以,变成了 O ( n l o g n ) O(nlogn) O(nlogn)建边,再跑最短路即可,非常划算。
S=3
或运算是三种位运算中唯一单调递增的,所以,一次跳跃不劣于多次跳跃。除非从其子集而来。那么,从 1 1 1使用 O ( n ) O(n) O(n)建图,先跑一遍 D i j k s t r a Dijkstra Dijkstra,再建好每个数二进制下的子集,再跑一遍最短路即可。
正解
STD写得比较扭曲,但是这种写法确实比较整齐但是也挺丑的。
#include <bits/stdc++.h>
using namespace std;
const int N = 100005;
template<typename T>
bool chmin(T &x, T val){
if (val < x){
x = val;
return true;
}
return false;
}
template<typename T>
bool chmax(T &x, T val){
if (x < val){
x = val;
return true;
}
return false;
}
int n, m, s;
long long k;
vector<pair<int, long long>> e[N];
long long d[N];
long long son[N];
int main(){
freopen("jump.in", "r", stdin);
freopen("jump.out", "w", stdout);
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
cin >> n >> m >> s >> k;
for (int i = 1; i <= m; i++){
int u, v;
long long w;
cin >> u >> v >> w;
e[u].push_back({v, w});
e[v].push_back({u, w});
}
memset(d, 0x3f, sizeof(d));
auto dijkstra = [&](){
priority_queue<pair<long long, int>, vector<pair<long long, int>>, greater<pair<long long, int>>> q;
d[1] = 0;
q.push({0, 1});
while (!q.empty()){
long long w = q.top().first;
int u = q.top().second;
q.pop();
if (d[u] != w)
continue;
for (auto tmp : e[u]){
int v = tmp.first;
long long weight = tmp.second;
if (chmin(d[v], w + weight))
q.push({d[v], v});
}
}
};
if (s == 1){
int x = n;
++x;
while (x % 2 == 0)
x /= 2;
if (x == 1){
for (int i = 2; i < n; i++){
e[1].push_back({i, 0});
e[i].push_back({1, 0});
}
for (int i = 1; i < n; i++){
e[i].push_back({n, k * (i & n)});
e[n].push_back({i, k * (i & n)});
}
dijkstra();
for (int i = 2; i <= n; i++)
cout << d[i] << " ";
}
else{
for (int i = 2; i <= n; i++)
cout << 0 << " ";
}
return 0;
}
if (s == 2){
int lg = 1;
while ((1 << lg) <= n)
++lg;
for (int i = 1; i <= n; i++){
for (int j = 0; j < lg; j++){
if ((1 << j) & i){
int x = i ^ (1 << j);
if (x >= 0 && x <= n){
e[i].push_back({x, k * (1 << j)});
e[x].push_back({i, k * (1 << j)});
}
}
}
}
dijkstra();
for (int i = 2; i <= n; i++)
cout << d[i] << " ";
return 0;
}
if (s == 3){
for (int i = 2; i <= n; i++){
e[1].push_back({i, k * (1 | i)});
e[i].push_back({1, k * (1 | i)});
}
dijkstra();
for (int i = 1; i <= n; i++){
son[i] = d[i];
for (int j = 1; j < i; j <<= 1)
if (i & j)
chmin(son[i], son[i ^ j]);
chmin(d[i], son[i] + k * i);
}
priority_queue<pair<long long, int>, vector<pair<long long, int>>, greater<pair<long long, int>>> q;
for (int i = 1; i <= n; i++)
q.push({d[i], i});
while (!q.empty()){
long long w = q.top().first;
int u = q.top().second;
q.pop();
if (d[u] != w)
continue;
for (auto tmp : e[u]){
int v = tmp.first;
long long weight = tmp.second;
if (chmin(d[v], w + weight))
q.push({d[v], v});
}
}
for (int i = 2; i <= n; i++)
cout << d[i] << " ";
}
}
D.interval
提交链接:07-D - ZYZOJ
题面
给定一个长度为 N N N的数列 A 1 , A 2 , … , A N A_1, A_2, \dots, A_N A1,A2,…,AN和一个长度为 N − 1 N-1 N−1的数列 B 2 , B 3 , … , B N B_2, B_3, \dots, B_N B2,B3,…,BN。 有 Q Q Q个询问,每次询问给出一个区间 [ L i , R i ] [L_i, R_i] [Li,Ri]。请你求出有多少二元组 ( l , r ) (l, r) (l,r)满足:
· L i ≤ l < r ≤ R i L_i \leq l < r \leq R_i Li≤l<r≤Ri
· ∀ i ∈ { l + 1 , l + 2 , … , r − 1 } , A l > A i \forall i \in \{l+1, l+2, \dots, r-1\}, A_l > A_i ∀i∈{l+1,l+2,…,r−1},Al>Ai (如果 l + 1 = = r l+1==r l+1==r则忽略这一条件,认为符合)
· $ \forall i \in {l, l+1, \dots, r-1}, B_r > A_i $
分析
N ≤ 400 , Q ≤ 400 N\le 400,Q \le 400 N≤400,Q≤400
预处理区间最大值,暴力枚举答案, O ( 1 ) O(1) O(1)判断。总复杂度 O ( N 2 Q ) O(N^2Q) O(N2Q)。
N ≤ 3000 , Q ≤ 300 N \le 3000,Q \le 300 N≤3000,Q≤300
枚举左端点 l l l,找到第一个不小于 A l A_l Al的位置 A x A_x Ax,那么 r r r最大取到 x x x。
查询 r ∈ [ l + 1 , x ] r\in [l+1,x] r∈[l+1,x]内 B r > A l B_r>A_l Br>Al的点的个数即可。
可以预处理从每个 l l l开始的答案,查询 O ( 1 ) O(1) O(1)。
总复杂度 O ( Q N l o g N ) O(QNlogN) O(QNlogN)或 O ( Q N ) O(QN) O(QN)(预处理每个点不小于自己最近的位置——二分或单调栈)。
A i , B i ≤ 3 A_i,B_i\le 3 Ai,Bi≤3
l + 1 = r l+1=r l+1=r的情况预处理,前缀和+差分直接 O ( 1 ) O(1) O(1)统计。
否则,长度不小于3,唯一的情况就是 A l = 2 , B r = 3 A_l=2,B_r = 3 Al=2,Br=3,区间里所有其他的 A i = 1 A_i=1 Ai=1。
因此对于每个 r r r,可能的 l l l最多只有1个。
把所有答案放在平面上,扫描线+二维数点。
复杂度 O ( ( N + Q ) l o g n ) O((N+Q)logn) O((N+Q)logn)。
2 ≤ N , Q ≤ 3 × 1 0 5 , 1 ≤ L , R ≤ N , 1 ≤ A i ≤ 1 0 9 , 1 ≤ B i ≤ 1 0 9 2\le N,Q \le 3\times 10^5,1\le L,R \le N,1\le A_i \le 10^9,1\le B_i \le 10^9 2≤N,Q≤3×105,1≤L,R≤N,1≤Ai≤109,1≤Bi≤109
把询问离线到右端点,枚举右端点,统一回答此处的询问。
对于每个 r r r,找到最小的 L [ r ] L[r] L[r]使得 ∀ i ∈ [ L [ r ] , r − 1 ] \forall i\in [L[r],r-1] ∀i∈[L[r],r−1]都有 A i ≤ B r A_i \le B_r Ai≤Br。
可以使用 S T ST ST表+二分。那么对于 r r r可能的l就在 [ L [ r ] , r − 1 ] [L[r],r-1] [L[r],r−1]中。
线段树维护,对于此时的最大右端点限制,区间里的可行起点个数。
考虑令 r r r为答案数对中的右端点,那么对于每个“当前向右看依然没找到跟自己一样高或者更高”的 A l A_l Al,都可以产生一个贡献。单调栈维护当前这类 A l A_l Al,在线段树维护区间内允许的起点数,每次相当于区间让对应的这些位置答案 + 1 +1 +1。
总复杂度 O ( ( N + Q ) l o g N ) O((N+Q)logN) O((N+Q)logN)。
正解
#include <bits/stdc++.h>
using namespace std;
#define fir first
#define sec second
#define mp make_pair
#define pb push_back
typedef long long int64;
typedef pair<int, int> PII;
const int Max_N = 300005;
int64 Ans[Max_N];
vector<PII> qry[Max_N];
int N, Q, A[Max_N], B[Max_N], L[Max_N];
namespace Sparse_table {
int st[20][Max_N], lg[Max_N];
void init() {
lg[0] = -1;
for (int i = 1; i <= N; i++)
lg[i] = lg[i >> 1] + 1;
for (int i = 1; i <= N; i++)
st[0][i] = A[i];
for (int i = 1; (1 << i) <= N; i++)
for (int j = 1; j + (1 << i) - 1 <= N; j++)
st[i][j] = max(st[i - 1][j], st[i - 1][j + (1 << (i - 1))]);
}
int query(int l, int r) {
int k = lg[r - l + 1];
return max(st[k][l], st[k][r - (1 << k) + 1]);
}
}
namespace Segment_tree {
#define lc (root << 1)
#define rc ((root << 1) | 1)
int cnt[Max_N << 2], tag[Max_N << 2];
int64 sum[Max_N << 2];
void pushdown(int root) {
sum[lc] += 1LL * tag[root] * cnt[lc];
sum[rc] += 1LL * tag[root] * cnt[rc];
tag[lc] += tag[root], tag[rc] += tag[root];
tag[root] = 0;
}
void modify(int root, int tl, int tr, int p, int d) {
if (tl == tr) {cnt[root] += d; return;}
pushdown(root);
int mid((tl + tr) >> 1);
if (p <= mid) modify(lc, tl, mid, p, d);
else modify(rc, mid + 1, tr, p, d);
cnt[root] += d;
}
void update(int root, int tl, int tr, int ql, int qr) {
if (ql <= tl && tr <= qr) {
tag[root]++;
sum[root] += cnt[root];
return;
}
pushdown(root);
int mid((tl + tr) >> 1);
if (ql <= mid)
update(lc, tl, mid, ql, qr);
if (qr > mid)
update(rc, mid + 1, tr, ql, qr);
sum[root] = sum[lc] + sum[rc];
}
int64 query(int root, int tl, int tr, int ql, int qr) {
if (ql <= tl && tr <= qr) return sum[root];
pushdown(root);
int64 ret(0);
int mid((tl + tr) >> 1);
if (ql <= mid)
ret += query(lc, tl, mid, ql, qr);
if (qr > mid)
ret += query(rc, mid + 1, tr, ql, qr);
return ret;
}
}
namespace Solve {
void init() {
scanf("%d", &N);
for (int i = 1; i <= N; i++)
scanf("%d", &A[i]);
for (int i = 2; i <= N; i++)
scanf("%d", &B[i]);
scanf("%d", &Q);
for (int l, r, i = 1; i <= Q; i++) {
scanf("%d%d", &l, &r);
assert(l < r);
qry[r].pb(mp(l, i));
}
Sparse_table::init();
for (int i = 2; i <= N; i++) {
int l = 1, r = i - 1, m, ret = i;
while (l <= r) {
m = (l + r) >> 1;
if (Sparse_table::query(m, i - 1) < B[i])
ret = m, r = m - 1;
else
l = m + 1;
}
L[i] = ret;
}
}
void work() {
static int stk[Max_N], top = 0;
for (int i = 1; i < N; i++) {
while (top > 0 && A[ stk[top] ] <= A[i])
Segment_tree::modify(1, 1, N, stk[top--], -1);
Segment_tree::modify(1, 1, N, stk[++top] = i, 1);
if (L[i + 1] != i + 1)
Segment_tree::update(1, 1, N, L[i + 1], i);
for (size_t j = 0; j < qry[i + 1].size(); j++)
Ans[ qry[i + 1][j].sec ] = Segment_tree::query(1, 1, N, qry[i + 1][j].fir, i);
}
for (int i = 1; i <= Q; i++)
printf("%lld\n", Ans[i]);
}
void __main__() {
init();
work();
}
}
int main() {
freopen("interval.in", "r", stdin);
freopen("interval.out", "w", stdout);
Solve::__main__();
return 0;
}
196

被折叠的 条评论
为什么被折叠?



