2025-11-17 ZYZ28-NOIP模拟赛-Round7 hetao1733837的record

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 n1,连接 ( i , i + 1 ) (i,i+1) (i,i+1)的边,其边权一定小于 n n n。从极限的角度考虑, ∣ p i − p j ∣ ≤ n |p_i-p_j|\le \sqrt{n} pipjn ∣ i − j ∣ ≤ n |i-j|\le \sqrt{n} ijn ,那么,每个点 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(nn logn)。边权范围 [ 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 A1A2ANA1A2ANA1A2AN

小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 B1B2BMB1B2BMB1B2BM

请你求出在这个过程中小 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×(xy)单位时间( ⊕ \oplus 表示按位异或(bitwise XOR))。

· S = 3 S = 3 S=3:传送需要 K × ( x ∣ y ) K \times (x | y) K×(xy)单位时间( ∣ | 表示按位或(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 N1 0 0 0即可。

S=1

扩展一下,找出最大的 t t t使得 2 t − 1 ≤ N 2^t-1\le N 2t1N,那么,这些点都价值为 0 0 0,剩余只需求补码即可。

但是出现了一个特例,就是说 N = 2 t + 1 − 1 N=2^{t+1}-1 N=2t+11,那么补码为 0 0 0,需要 O ( n ) O(n) O(n)建边,跑最短路。

S=2

对于异或,如果要从 a a a到达 b b b,若两者之间某一位二进制位不同,则可以通过 a ⊕ 2 ? a\oplus 2^? a2?解决。同理,所以,变成了 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 N1的数列 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 Lil<rRi

· ∀ 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,,r1},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 N400,Q400

预处理区间最大值,暴力枚举答案, 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 N3000,Q300

枚举左端点 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,Bi3

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 2N,Q3×105,1L,RN,1Ai109,1Bi109

把询问离线到右端点,枚举右端点,统一回答此处的询问。

对于每个 r r r,找到最小的 L [ r ] L[r] L[r]使得 ∀ i ∈ [ L [ r ] , r − 1 ] \forall i\in [L[r],r-1] i[L[r],r1]都有 A i ≤ B r A_i \le B_r AiBr

可以使用 S T ST ST表+二分。那么对于 r r r可能的l就在 [ L [ r ] , r − 1 ] [L[r],r-1] [L[r],r1]中。

线段树维护,对于此时的最大右端点限制,区间里的可行起点个数。

考虑令 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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值