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

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

好累😩

比赛链接:ZYZ28-NOIP模拟赛-Round7 - ZYZOJ

A.pmst

$( ‵▽′)ψ$$lz$竟然把原来的$T2$塞进了$T1$!是想坑谁?还好我盒到了原。

原题链接:Darnassus

提交链接:07-A - ZYZOJ

分析

思路还是比较简单的,最开始思考方向也是正确的,及由于是排列,差值的绝对值取在$1$到$n-1$,连接$(i,i+1)$的边,其边权一定小于$n$。从极限的角度考虑,$|p_i-p_j|\le \sqrt{n}$,$|i-j|\le \sqrt{n}$,那么,每个点$O(n)$建图,全局$O(n^2)$建图就可以降到$O(\sqrt{n})$,然后跑$Kruskal$复杂度就是$O(n\sqrt{n}logn)$。边权范围$[1,n]$,开桶记录,不必排序,最终复杂度$O(n\sqrt{n}\alpha(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$和小$Z$在卡牌游戏中对战。小$Y$有$N$张卡牌而小$Z$有$M$张卡牌。每张卡牌均有一个由正整数表示的力量值。

每一回合,小$Y$和小$Z$各自展示一张卡牌,如果一名玩家的卡牌力量值大于对手的卡牌力量值,则该玩家被视为胜出此回合。如果展示的两张卡牌具有相同的力量值,则此回合被视为和局。 小$Y$的第$i$张卡牌的力量值为$A_i$。小Z的第$j$张卡牌的力量值为$B_j$。 然后,他们将进行$N \times M$轮的对战。他们都循环地展示卡牌。

小$Y$依照以下顺序展示卡牌(共$M$轮):$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$

小Z依照以下顺序展示卡牌(共$N$轮):$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$

请你求出在这个过程中小$Y$获胜、小$Z$获胜及和局的回合数。

分析

显然思路又对了……为啥没过呢?心态浮躁?代码能力弱?思维还是不行?单纯运气不好?我不知道……但是,前段时间学习方法是绝对有问题的,一直在抄题解,没有自主思考,不热爱思考,必须纠正。

我们很容易想到,两者并不必须走$N\times M$轮,只需走$lcm(N, M)$轮即可,而且,相对应的位置,它们下标一定在模$gcd(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$个城市构成,编号为$1,2,\dots,N$。 有$M$条双向道路连接这些城市,第$i$条连通城市$U_i$和城市$ V_i$,通过这条道路需要花费$W_i$的时间。 此外,比特国的人们还可以使用“比特跳跃”来通行于任意两个城市之间。比特跳跃所需的时间取决于两个常数$S$和$K$,两者均为整数。对于从城市$x$跳跃到城市$y$:

· $S = 1$:传送需要$K \times (x & y)$单位时间($&$表示按位与(bitwise AND))。

· $S = 2$:传送需要$K \times (x \oplus y)$单位时间($\oplus$表示按位异或(bitwise XOR))。

· $S = 3$:传送需要$K \times (x | y) $单位时间($| $表示按位或(bitwise OR))。

请你计算从$1$号城市移动到每个城市所需的最短时间。

分析

$O(n^2)$建图显然是很不错的那份手段,但不妨多想一想。

对于$N$是$2$的整数次幂,从$1$跳到$N$,代价为$0$,从$N$跳到任意点,代价均为$0$,输出$N-1$个$0$即可。

S=1

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

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

S=2

对于异或,如果要从$a$到达$b$,若两者之间某一位二进制位不同,则可以通过$a\oplus 2^?$解决。同理,所以,变成了$O(nlogn)$建边,再跑最短路即可,非常划算。

S=3

或运算是三种位运算中唯一单调递增的,所以,一次跳跃不劣于多次跳跃。除非从其子集而来。那么,从$1$使用$O(n)$建图,先跑一遍$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$的数列$A_1, A_2, \dots, A_N$和一个长度为$N-1$的数列$B_2, B_3, \dots, B_N$。 有$Q$个询问,每次询问给出一个区间$[L_i, R_i]$。请你求出有多少二元组$(l, r)$满足:

· $L_i \leq l < r \leq R_i$

· $\forall i \in {l+1, l+2, \dots, r-1}, A_l > A_i$ (如果$l+1==r$则忽略这一条件,认为符合)

· $ \forall i \in {l, l+1, \dots, r-1}, B_r > A_i $

分析

$N\le 400,Q \le 400$

预处理区间最大值,暴力枚举答案,$O(1)$判断。总复杂度$O(N^2Q)$。

$N \le 3000,Q \le 300$

枚举左端点$l$,找到第一个不小于$A_l$的位置$A_x$,那么$r$最大取到$x$。

查询$r\in [l+1,x]$内$B_r>A_l$的点的个数即可。

可以预处理从每个$l$开始的答案,查询$O(1)$。

总复杂度$O(QNlogN)$或$O(QN)$(预处理每个点不小于自己最近的位置——二分或单调栈)。

$A_i,B_i\le 3$

$l+1=r$的情况预处理,前缀和+差分直接$O(1)$统计。

否则,长度不小于3,唯一的情况就是$A_l=2,B_r = 3$,区间里所有其他的$A_i=1$。

因此对于每个$r$,可能的$l$最多只有1个。

把所有答案放在平面上,扫描线+二维数点。

复杂度$O((N+Q)logn)$。

$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$

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

对于每个$r$,找到最小的$L[r]$使得$\forall i\in [L[r],r-1]$都有$A_i \le B_r$。

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

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

考虑令$r$为答案数对中的右端点,那么对于每个“当前向右看依然没找到跟自己一样高或者更高”的$A_l$,都可以产生一个贡献。单调栈维护当前这类$A_l$,在线段树维护区间内允许的起点数,每次相当于区间让对应的这些位置答案$+1$。

总复杂度$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;
}

posted on 2025-11-17 19:24  hetao1733837  阅读(0)  评论(0)    收藏  举报

导航