2025-11-12~13 hetao1733837的刷题记录

刷题背景

这是一个悲伤的故事……

aa13ef772281029f628646f274192b0f

11-12

P3206 [HNOI2010] 城市建设

原题链接:[P3206 [HNOI2010] 城市建设]([P3206 HNOI2010] 城市建设 - 洛谷)

分析

一眼最小生成树,但是,掺进了修改……不同于[P1340](P1340 兽径管理 - 洛谷)的是,这题不是加边而是改边权,然后就评紫了……其实,在最开始学$CDQ$分治(虽然没学会吧……)的时候,我就意识到了其代码和线段树的相似性。所以说,$CDQ$分治的递归树就是一棵线段树。普通$CDQ$分治是在考虑线段树左右儿子的关系,而本题则需要考虑儿子父亲之间的关系。也就是说,我们在调用$CDQ(l,r)$时,需要使图与区间产生一个关联(我是这么理解的······)

具体的实现如下:

若我们正在构造$(l,r)$这段区间的最小生成树边集,并且我们已知它父亲最小生成树的边集。我们将在$(l,r)$这段区间中发生变化的边赋值为$+\infty$和$-\infty$,各跑一次$Kruskal$,求最小生成树里的边。

对于一条边,

1.如果被赋值为$+\infty$,而它未出现在树中,则证明它不可能出现在$(l,r)$这些询问的最小生成树中。所以,我们仅仅在$(l,r)$的边集中加入最小生成树的边。

2.如果最小生成树里所有被修改的边权都被赋成了$-\infty$,而它出现在树中,则证明它一定会出现$(l,r)$这段的区间的最小生成树当中。这样的话,我们就可以使用并查集将这些边对应的点缩起来,并且将答案加上这些边的边权。

于是,我们得到$(l,r)$这段区间的边集构造出来了。用这些边求出最小生成树和直接求原图的最小生成树是等价的。

易于证明:复杂度是$O(nlong^2n)$的。

正解


我为何$MLE$,$WA$掉了???

P14426 [JOISC 2014] 稻草人 / Scarecrows

原题链接:[P14426 [JOISC 2014] 稻草人 / Scarecrows]([P14426 JOISC 2014] 稻草人 / Scarecrows - 洛谷)

我吃饱了/ll

分析

好吧,我真的吃饱了。容易想到的是存在$(i,j)$使得$x_i< x_j,y_i< y_j$,但是,我们还要控制“烟堆的内部(不包括边界)不能有神社”,这也成为了本题的难点……我去看题解了,能学会吗?

考虑到我是天然的二维偏序,维护额外信息再加一维即可,即需要满足$\nexists k$,使得$x_i < x_k < x_j$且$y_i < y_k < y_j$。考虑分治,我们把$x$轴分为左右两部分,会发现在左半部分,一个点可以统计答案当且仅当它不为西南角,所以,左半区间的y随x的增加而减少($k<0$),同理右半子区间y随x的增加而增加($k>0$),那么,可以开单调栈维护,每次查询我们需要找到比前一个$y$大,比当前$y$小,这就是二分(此处默认通过对$x$的排序消掉了一维)。好吧,我需要继续理解……

正解

#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N = 200005;
int n, stk1[N], stk2[N];
int tp1, tp2;
struct node{
    int x, y, id;
    bool operator<(const node &k) const{
        return x < k.x;
    }
}a[N], b[N];
int ans;
void CDQ(int l, int r){
	if (l == r)
		return ;
	int mid = (l + r) >> 1;
	CDQ(l, mid);
	CDQ(mid + 1, r);
	int i = l, j = mid + 1, cnt = 0;
	while (i <= mid && j <= r){
		if (a[i].y < a[j].y){
			b[++cnt] = {a[i].x, a[i].y, 0};
			i++;
		}
		else{
			b[++cnt] = {a[j].x, a[j].y, 1};
			j++;
		}
	}
	while (i <= mid){
		b[++cnt] = {a[i].x, a[i].y, 0};
		i++;
	}
	while (j <= r){
		b[++cnt] = {a[j].x, a[j].y, 1};
		j++;
	}
	tp1 = 0;
	tp2 = 0;
	for (int i = 1; i <= cnt; i++){
		if (!b[i].id){
			while (tp1 && b[stk1[tp1]].x < b[i].x){
				tp1--;
			}
			stk1[++tp1] = i;
		}
		else{
			while (tp2 && b[stk2[tp2]].x > b[i].x){
				tp2--;
			}
			if (!tp2){
				ans += tp1;
			}
			else{
				int s = 1, t = tp1, pos = tp1 + 1;
				while (s <= t){
					int mid = (s + t) >> 1;
					if (b[stk1[mid]].y > b[stk2[tp2]].y){
						t = mid - 1;
						pos = mid;
					}
					else{
						s = mid + 1;
					}
				}
				ans += tp1 - pos + 1;
			}
			stk2[++tp2] = i;
		}
	}
	for (int i = l; i <= r; i++){
		a[i] = b[i - l + 1];
	}
}
signed main(){
	cin >> n;
	for (int i = 1; i <= n; i++){
		cin >> a[i].x >> a[i].y;
	}
	sort(a + 1, a + n + 1);
	CDQ(1, n);
	cout << ans;
}

记得开$long$ $ long$哦!

11-13

不知为何,机房都在打$OIer$教练模拟器……连$lz$都在打……
刷题背景

退竞了······$tboj$号你们想登就登吧,对$OI$的新鲜感过了,账号也抽了很多卡了,我也不想学了,送给大家了······这个事情我也想了很久,非常舍不得。最终,还是决定不玩了。学了这么久还是没有什么进步,我对自己太失望了/ll
账号:$KfcCrazyThursday$
密码:$Vwo50rmb$

P11362 [NOIP2024] 遗失的赋值

原题链接:[P11362 [NOIP2024] 遗失的赋值]([P11362 NOIP2024] 遗失的赋值 - 洛谷)

分析

比较傻了,就是乘法原理套加法原理,但是,感觉最近睡眠有点少,脑子比较糊(我在找借口),所以没有直接切掉……糖丸了,$qpow()$初始化写res=0也是有了……

正解

#include <bits/stdc++.h>
#define int long long
#define mod 1000000007
using namespace std;
int qpow(int x, int y){
	int ans = 1;
	while (y){
		if (y & 1)
			ans = ans * x % mod;
		x = x * x % mod;
		y >>= 1;
	}
	return ans;
}
int n, m, v, ans, T;
map<int, int> mp;
signed main(){
	ios::sync_with_stdio(0);
	cin.tie(0);
	cout.tie(0);
	cin >> T;
	while (T--){
		cin >> n >> m >> v;
		mp.clear();
		ans = 1;
		for (int i = 1; i <= m; i++){
			int c, d;
			cin >> c >> d;
			if (mp.find(c) != mp.end() && mp[c] != d)
				ans = 0;
			else
				mp[c] = d;
		}
		if (!ans){
			cout << 0 << '\n';
			continue;
		}
		auto pre = mp.begin(), nxt = mp.begin();
		ans = qpow(v, ((*nxt).first - 1) * 2);
		++nxt;
		for ( ; nxt != mp.end(); ++pre, ++nxt){
			int p = (*pre).first, q = (*nxt).first;
			ans = ans * (qpow(v, (q - p) * 2) - qpow(v, q - p - 1) * (v - 1) % mod + mod) % mod;
		}
		ans = ans * qpow(v, (n - (*pre).first) * 2) % mod;
		cout << ans << '\n';
	}
}

好像并未理解,脑子还是糊……

P7961 [NOIP2021] 数列

原题链接:[P7961 [NOIP2021] 数列]([P7961 NOIP2021] 数列 - 洛谷)

分析

想到了找一些规律,就比如说$(10)_2+(10)_2=(100)_2$这样在同一位上的$1$的个数$cnt$,会转化为$\left\lfloor\dfrac{cnt}{2}\right\rfloor$个更高位的$1$,和$cnt\mod 2$个当前位置上的$1$。呃,然后比一下$k$,然后加法原理和乘法原理往上套……思路大概长这样。对吗?容我看一眼题解……

woc,咋是$DP$?好吧,重新稍考一下……对于和$k$比的部分,显然是__builtin_popcount()吧……$O(1)$显然很有性价比。那么,就是状态了……由于牵扯进位,所以不妨从低位往高位$DP$,设$dp_{i,j,k,p}$表示讨论了$S$从低到高的前$i$位,已经确定了$j$个序列$a$中的元素,$S$从低到高前$i$位中有$k$个$1$,要向当前讨论位的下一位进位$p$。

由之前瞎想的规律得出:$dp_{i,j,k,p}$可以更新$dp_{i+1,j+t,k+(t+p)\mod2,\left\lfloor\dfrac{cnt}{2}\right\rfloor}$,由于乘积之和的形式满足乘法分配律,所以不难想到$dp_{i,j,k,p}$对新状态的贡献应该是$dp_{i,j,k,p}\times v_{i}^{v}\times \dbinom{n-j}{t}$,对于初始化$dp_{0,0,0,0}=1$。

答案统计,前三维按照本身统计,最后一维随意。

好吧,我承认这题确实难……

正解

#include <bits/stdc++.h>
#define mod 998244353
using namespace std;
const int N = 35, M = 105;
int n, m, K;
long long ans, v[M], dp[M][N][N][N], C[N][N], P[M][N];
int main(){
//	freopen("sequence.in", "r", stdin);
//	freopen("sequence.out", "w", stdout);
	for (int i = 0; i < N; i++)
		C[i][0] = 1;
	for (int i = 1; i < N; i++)
		for (int j = 1; j <= i; j++)
			C[i][j] = (C[i - 1][j] + C[i - 1][j - 1]) % mod;
	cin >> n >> m >> K;
	for (int i = 0; i <= m; i++){
		cin >> v[i];
		P[i][0] = 1;
		for (int j = 1; j <= n; j++)
			P[i][j] = P[i][j - 1] * v[i] % mod;
	}
	dp[0][0][0][0] = 1;
	for (int i = 0; i <= m; i++){
		for (int j = 0; j <= n; j++){
			for (int k = 0; k <= K; k++){
				for (int p = 0; p <= n >> 1; p++){
					for (int t = 0; t <= n - j; t++){
						dp[i + 1][j + t][k + (t + p & 1)][t + p >> 1] = 
						(dp[i + 1][j + t][k + (t + p & 1)][t + p >> 1] + 
                         dp[i][j][k][p] * P[i][t] % mod * C[n - j][t] % mod) % mod;
					}
				}
			}
		}
	}
	int ans = 0;
	for (int k = 0; k <= K; k++)
		for (int p = 0; p <= n >> 1; p++)
			if (k + __builtin_popcount(p) <= K)
				ans = (ans + dp[m + 1][n][k][p]) % mod;
	cout << ans;
}

不中了,不中了,思维也成一坨了,为了$NOIP$,现在开始吃……

题单链接:题单

[JOISC 2014] 巴士走读 / Bus

原题链接1:[P14418 [JOISC 2014] 巴士走读 / Bus - 洛谷]

原题链接2:#2872. 「JOISC 2014 Day1」巴士走读

分析

我有个不是思路的思路……就是说,我为何不反着想?我改为从$N$出发,这样需要额外记录每个节点的到达最大时间,然后……再稍考一下……呃,似乎并非,需要进行一次二分来确定可以走哪些路径,难道就是这样?但是$1e5$好像并非能过……

咦,好像是对的,好吧,我承认我代码能力很弱……我也承认我很喜欢用省略号……

那么实现起来其实就是做个离线,反向建时间图,依次提高到达终点的时间,跑$Dijkstra$,大概就是我的理解了。燃尽了/ll

正解

#include <bits/stdc++.h>
using namespace std;
const int N = 100005, M = 300005;
struct edge{
	int s, t, u;
	bool operator < (const edge &tmp) const{
		if (s == tmp.s){
			if (t == tmp.t){
				return u < tmp.u;
			}
			return t < tmp.t;
		} 
		return s < tmp.s;
	}
};
vector<edge> e[N];
int cur_vis[M], cur_mn[M];
int n, m, Q;
struct node{
	int x, v;
	bool operator < (const node &tmp) const{
		return v < tmp.v;
	}
};
priority_queue<node> q;
int d[N], sta[N], top;
bool vis[N];
void dijkstra(int tim){
	for (int i = 1; i <= top; i++)
		vis[sta[i]] = 0;
	top = 0;
	while (!q.empty()){
		q.pop();
	}
	d[n] = tim;
	q.push({n, tim});
	edge cur;
	while (!q.empty()){
		auto tmp = q.top();
		int now = tmp.x;
		q.pop();
		if (now == 1)
			return ;
		if (vis[now])
			continue;
		sta[++top] = now;
		vis[now] = 1;
		while (cur_vis[now] < cur_mn[now]){
			cur = e[now][cur_vis[now]];
			if (cur.s <= d[now]){
				if (cur.t > d[cur.u]){
					d[cur.u] = cur.t;
					q.push({cur.u, cur.t});
				}
				++cur_vis[now];
			}
			else{
				break;
			}
		}
	}
}
struct node2{
	int tim, id;
	bool operator < (const node2 &tmp) const{
		return tim < tmp.tim;
	}
}qry[N];
int ans[N];
int main(){
	ios::sync_with_stdio(0);
	cin.tie(0);
	cout.tie(0);
	cin >> n >> m;
	for (int i = 1; i <= m; i++){
		int a, b, x, y;
		cin >> a >> b >> x >> y;
		e[b].push_back({y, x, a});
	}
	for (int i = 1; i <= n; i++)
		sort(e[i].begin(), e[i].end());
	for (int i = 1; i <= n; i++)
		cur_mn[i] = (int)e[i].size();
	cin >> Q;
	for (int i = 1; i <= Q; i++){
		cin >> qry[i].tim;
		qry[i].id = i;
	}
	sort(qry + 1, qry + Q + 1);
	for (int i = 1; i <= n; i++)
        d[i] = -1;
	for (int i = 1; i <= Q; i++){
		dijkstra(qry[i].tim);
		ans[qry[i].id] = d[1];
	}
	for (int i = 1; i <= Q; i++)
		cout << ans[i] << '\n';
}

[JOISC 2014] 有趣的家庭菜园 / Growing Vegetables is Fun

原题链接1:[JOISC 2014] 有趣的家庭菜园 / Growing Vegetables is Fun

原题链接2:#2873. 「JOISC 2014 Day1」有趣的家庭菜园

分析

显然,按照之前我们求单峰的做法——求区间最值,然后进行神秘操作是行不通的(因为这题不带修?我也不知道,好像是因为你求出最大值,次大值……之后不同交换代价也不同,硬上DP也是不好做的)。

考虑怎么做,初始情况下,我们给每一株$IOI$草赋一个初始值$pos_i=i$,那么,在最终排好序的序列中,操作数就是$pos$的逆序对。然后,我们开始往最终序列里加值,每次加值,考虑加到西边还是东边,使用树状数组求逆序对,贪心地选择少的那一边(这里产生了对$h_i$降序排序的冲动,是对的),最后就做完了……其实我并不会写带码。

正解

#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N = 300005;
int n;
struct node{
	int h, pos;
}a[N];
int c[N];
void add(int x, int v){
	for (int i = x; i <= n; i += i & (-i))
		c[i] += v;
}
int query(int x){
	int ans = 0;
	for (int i = x; i; i -= i & (-i))
		ans += c[i];
	return ans;
} 
bool cmp(node x, node y){
	return x.h > y.h;
}
signed main(){
	ios::sync_with_stdio(0);
	cin.tie(0);
	cout.tie(0);
	cin >> n;
	for (int i = 1; i <= n; i++){
		cin >> a[i].h;
		a[i].pos = i;
	}
	sort(a + 1, a + n + 1, cmp);
	int ans = 0;
	for (int i = 1; i <= n; ){
		int j;
		for (j = i; j <= n && a[j].h == a[i].h; j++){
			int k = query(a[j].pos);
			ans += min(k, i - 1 - k);
		}
		for (j = i; j <= n && a[j].h == a[i].h; j++){
			add(a[j].pos, 1);
		}
		i = j;
	}
	cout << ans;
}

好吧,蒟蒻要重新理解树状数组求逆序对了……

[JOISC 2014] 历史的研究 / Historical Research

原题链接1:[JOISC 2014] 历史的研究 / Historical Research

原题链接2:#2874. 「JOISC 2014 Day1」历史研究

分析

$cnt$数组前缀和?$1e9$你闹呢?线段树能写吗?unknow……看一眼tag,莫队,下一题!!!

我问问有没有其他做法……死心了,全是在说回滚莫队板子qwq,改天和整体二分一起学。

[JOISC 2014] 拉面比较 / Ramen

原题链接1:[JOISC 2014] 拉面比较 / Ramen

原题链接2:#2875. 「JOISC 2014 Day1」拉面比较

${\color{Yellow}黄题}$哭了/(ㄒoㄒ)/~~发现是交互……

分析

首先考虑$2N$次交互,相当于两次遍历,可以过$N\le 300$。但是原题$N\le400$,没法这样写,考虑优化……那就是经典的分治做法了,对于序列我们先两两分组比较,选取较大值和较小值,需要$\frac{1}{2}N$次,然后,每组再比$\frac{1}{2}N$次,卡到了$\frac{3}{2}N$次,可以通过了。好的,让我们学写一下交互代码,以后可以打CF的了o(** ̄▽ ̄)ブ

正解(LOJ版)

#include <bits/stdc++.h>
#include "ramen.h"
using namespace std;
int Compare(int X, int Y);
void Answer(int X, int Y);
void Ramen(int N){
	if (N == 1)
		return Answer(0, 0), void();
	vector<size_t> la, li;
	for (size_t i = 0; i + 1 < N; i += 2){
		if (Compare(i, i + 1) > 0){
			la.push_back(i);
			li.push_back(i + 1);
		}
		else{
			la.push_back(i + 1);
			li.push_back(i);
		}
	}
	if (N % 2 == 1){
		size_t last = N - 1;
		if (Compare(last, la[0]) > 0){
			li.push_back(la[0]);
			la[0] = last;
		}
		else if (Compare(last, li[0]) < 0){
			la.push_back(li[0]); 
			li[0] = last;
		}
		else{
			la.push_back(last);
		}
	}
	size_t maxn = la[0];
	for (size_t i = 1; i < la.size(); i++){
		if (Compare(la[i], maxn) > 0)
			maxn = la[i];
	}
	size_t minn = li[0];
	for (size_t i = 1; i < li.size(); i++){
		if (Compare(li[i], minn) < 0)
			minn = li[i];
	}
	Answer(minn, maxn);
}

正解(LG版)

#include <bits/stdc++.h>
using namespace std;
int Compare(int X, int Y);
void Answer(int X, int Y);
void Ramen(int N){
	if (N == 1)
		return Answer(0, 0), void();
	vector<size_t> la, li;
	for (size_t i = 0; i + 1 < N; i += 2){
		if (Compare(i, i + 1) > 0){
			la.push_back(i);
			li.push_back(i + 1);
		}
		else{
			la.push_back(i + 1);
			li.push_back(i);
		}
	}
	if (N % 2 == 1){
		size_t last = N - 1;
		if (Compare(last, la[0]) > 0){
			li.push_back(la[0]);
			la[0] = last;
		}
		else if (Compare(last, li[0]) < 0){
			la.push_back(li[0]); 
			li[0] = last;
		}
		else{
			la.push_back(last);
		}
	}
	size_t maxn = la[0];
	for (size_t i = 1; i < la.size(); i++){
		if (Compare(la[i], maxn) > 0)
			maxn = la[i];
	}
	size_t minn = li[0];
	for (size_t i = 1; i < li.size(); i++){
		if (Compare(li[i], minn) < 0)
			minn = li[i];
	}
	Answer(minn, maxn);
}

上课了,讲的AC自动机,晚上估计要写别的题单,先发一篇。

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

导航