CSP-S 模拟赛34

CSP-S 模拟赛34

T1

考虑对原序列将 k 的左右分成两个序列。simple 的想法是分别从 1 开始跑前缀和,每一次总跑到下一个小于它的点,然后依次类推。发现这样做碰到序列最小值之后难以继续。

然而我们发现这样跑点的过程从前往后和从后往前是等价的。这样考虑的原因是发现这样的选数问题不具有方向性。于是时间复杂度 O(n)

代码:

#include <bits/stdc++.h>
#define N 100005
#define int long long
using namespace std;
int T;
int n, k;
int a[N];
int a1[N], a2[N];
int ct1, ct2;
int sm1[N], sm2[N];
int nx1[N], nx2[N];
void sve() {
	memset(sm1, 0, sizeof sm1);
	memset(sm2, 0, sizeof sm2);
	memset(nx1, 0, sizeof nx1);
	memset(nx2, 0, sizeof nx2);
	cin >> n >> k;
	for (int i = 1; i <= n; i++)
		scanf("%lld", &a[i]);
	ct1 = ct2 = 1;
	a1[1] = a2[1] = 0;
	for (int i = k; i > 1; i--)
		a1[++ct1] = a[i];
	for (int i = k + 1; i <= n; i++)
		a2[++ct2] = a[i];
	for (int i = 1; i <= ct1; i++)
		sm1[i] = sm1[i - 1] + a1[i];
	for (int i = 1; i <= ct2; i++)
		sm2[i] = sm2[i - 1] + a2[i];
	int mn = sm1[1], nw = 1;
	for (int i = 2; i <= ct1; i++)
		if (sm1[i] <= mn) {
			nx1[nw] = i;
			nw = i;
			mn = sm1[i];
		}
	mn = sm1[ct1], nw = ct1;
	for (int i = ct1 - 1; i >= 1; i--)
		if (sm1[i] < mn) {
			nx1[nw] = i;
			nw = i;
			mn = sm1[i];
		}
	mn = sm2[1], nw = 1;
	for (int i = 2; i <= ct2; i++)
		if (sm2[i] <= mn) {
			nx2[nw] = i;
			nw = i;
			mn = sm2[i];
		}
	mn = sm2[ct2], nw = ct2;
	for (int i = ct2 - 1; i >= 1; i--)
		if (sm2[i] < mn) {
			nx2[nw] = i;
			nw = i;
			mn = sm2[i];
		}
	if (sm1[ct1] + sm2[ct2] > 0)
		return puts("No"), void();
	int p1 = 1, p2 = 1;
	while (nx1[p1] || nx2[p2]) {
		if (!nx1[p1]) {
			for (int j = p2 + 1; j <= nx2[p2]; j++)
				if (sm1[p1] + sm2[j] > 0) {
					puts("No");
					return;
				}
			p2 = nx2[p2];
			continue;
		}
		if (!nx2[p2]) {
			for (int j = p1 + 1; j <= nx1[p1]; j++)
				if (sm1[j] + sm2[p2] > 0) {
					puts("No");
					return;
				}
			p1 = nx2[p1];
			continue;
		}
		int fg = 0;
		for (int i = p1 + 1; i <= nx1[p1]; i++)
			if (sm1[i] + sm2[p2] > 0) {
				fg = 1;
				break;
			}
		if (!fg) {
			p1 = nx1[p1];
			continue;
		}
		for (int j = p2 + 1; j <= nx2[p2]; j++)
			if (sm1[p1] + sm2[j] > 0) {
				puts("No");
			return;
		}
		p2 = nx2[p2];
	}
	p1 = ct1, p2 = ct2;
	while (nx1[p1] || nx2[p2]) {
		if (!nx1[p1]) {
			for (int j = p2 - 1; j >= nx2[p2]; j--)
				if (sm1[p1] + sm2[j] > 0) {
					puts("No");
					return;
				}
			p2 = nx2[p2];
			continue;			
		}
		if (!nx2[p2]) {
			for (int j = p1 - 1; j >= nx1[p1]; j--)
				if (sm1[j] + sm2[p2] > 0) {
					puts("No");
					return;
				}
			p1 = nx2[p1];
			continue;			
		}
		int fg = 0;
		for (int i = p1 - 1; i >= nx1[p1]; i--)
			if (sm1[i] + sm2[p2] > 0) {
				fg = 1;
				break;
			}
		if (!fg) {
			p1 = nx1[p1];
			continue;
		}
		for (int j = p2 - 1; j >= nx2[p2]; j--)
			if (sm1[p1] + sm2[j] > 0) {
				puts("No");
			return;
		}
		p2 = nx2[p2];
	}
	puts("Yes");
}
signed main() {
	freopen("game.in", "r", stdin);
	freopen("game.out", "w", stdout);
	cin >> T;
	for (int i = 1; i <= T; i++)
		sve();
	return 0;
}

T2

显然考虑 O(n2) 的 dp。

朴素的 dp 定义是 dpi,j 表示长度为 i 的序列,j 次消除的方案数。然而发现这样转移的复杂度难以接受,需要分别枚举左右区间的消除次数。

考虑某一个位置 x 的消除次数由什么决定。对于只有某一边有 >ax 的,则这个位置的消除次数一定是 j1。对于两边都有 >ax 的,两边的消除次数有一个是 j1。那么就考虑前缀和优化这个 dp,则定义 dpi,j,0/1 表示长度为 i 的序列,至多 j 次消除,有一边 / 两边 >ax 的方案数,那么 j 便由 j,j1 转移而来。

时间复杂度大抵是 O(n2log2n)

这样考虑的原因是注意到每个最大值都会覆盖一个 "区间",因此可以由左右两端转移而来。

代码:

#include <bits/stdc++.h>
#define N 1005
#define int long long
using namespace std;
int n, k, p;
int C[N][N];
int dp[N][N][2];
void add(int &x, int y) {
	x = (x + y + p) % p;
}
signed main() {
	freopen("per.in", "r", stdin);
	freopen("per.out", "w", stdout);
	cin >> n >> k >> p;
	C[0][0] = 1;
	for (int i = 1; i <= n; i++)
		for (int j = 0; j <= i; j++)
			C[i][j] = (C[i - 1][j] + (j > 0 ? C[i - 1][j - 1] : 0)) % p;
	for (int i = 0; i <= k; i++)
		dp[0][i][0] = dp[0][i][1] = 1;
	for (int i = 1; i <= n; i++)
		for (int j = 1; j <= k; j++) 
			for (int t = 1; t <= i; t++) {
				add(dp[i][j][0], C[i - 1][t - 1] * dp[t - 1][j - 1][1] % p * dp[i - t][j][0] % p);
				add(dp[i][j][1], C[i - 1][t - 1] * ((dp[t - 1][j][1] * dp[i - t][j - 1][1] % p + dp[t - 1][j - 1][1] * dp[i - t][j][1] % p - dp[t - 1][j - 1][1] * dp[i - t][j - 1][1] % p) % p) % p);
			}
	int ans = 0;
	for (int i = 1; i <= n; i++)
		add(ans, C[n - 1][i - 1] * ((dp[i - 1][k][0] * dp[n - i][k][0] % p - dp[i - 1][k - 1][0] * dp[n - i][k - 1][0] % p)) % p % p);
	cout << ans << "\n";
	return 0;
}

T3

考虑问题的本质是从 1n 再从 n1,显然贪心地维护不具有正确性,于是我们可以维护一个点对 (x,y) 表示 1x,y1 的最小值,所求就是 (n,n) 的答案。实现的时候可以建反图,用类似 Dijkstra 的方法动态维护哪些点走过。

对于正确性,考虑不用买门票的情形一定是去对回做贡献或是回对去做贡献,那这样的枚举是保证了某一方向先走到一个点再更新另一个方向,于是必然可以更新到正确答案。

代码:

#include <bits/stdc++.h>
#define N 255
using namespace std;
int n, m;
struct MP {
	struct Node {
		int to, nxt;
	} e[N * N];
	int head[N], cnt;
	void add(int u, int v) {
		e[++cnt].to = v;
		e[cnt].nxt = head[u];
		head[u] = cnt;
	}
} A, B; 
int vl[N];
int dis[N][N];
bitset<N>can[N][N];
struct node {
	int x, y;
	int dis;
	bool operator < (const node &x) const {
		return dis > x.dis;
	}
};
priority_queue<node>q;
int vis[N][N];
void Dij() {
	dis[1][1] = vl[1];
	can[1][1].set(1);
	q.push((node) {
		1, 1, dis[1][1]
	});
	while (!q.empty()) {
		node tmp = q.top();
		q.pop();
		int x = tmp.x, y = tmp.y;
		if (vis[x][y])
			continue;
		vis[x][y] = 1;
		for (int i = A.head[x]; i; i = A.e[i].nxt) {
			int p = A.e[i].to;
			int ds = tmp.dis;
			if (!can[x][y][p])
				ds += vl[p];
			if (ds < dis[p][y]) {
				dis[p][y] = ds;
				can[p][y] = can[x][y];
				can[p][y].set(p);
				q.push((node) {
					p, y, dis[p][y]
				});
			}
		}
		for (int i = B.head[y]; i; i = B.e[i].nxt) {
			int p = B.e[i].to;
			int ds = tmp.dis;
			if (!can[x][y][p])
				ds += vl[p];
			if (ds < dis[x][p]) {
				dis[x][p] = ds;
				can[x][p] = can[x][y];
				can[x][p].set(p);
				q.push((node) {
					x, p, dis[x][p]
				});
			}
		}
	}
}

int main() {
	freopen("tour.in", "r", stdin);
	freopen("tour.out", "w", stdout);
	cin >> n >> m;
	for (int i = 1; i <= n; i++)
		scanf("%d", &vl[i]);
	for (int i = 1; i <= m; i++) {
		int x, y;
		scanf("%d%d", &x, &y);
		A.add(x, y);
		B.add(y, x);
	}
	memset(dis, 0x3f, sizeof dis);
	Dij();
	if (dis[n][n] < 0x3f3f3f3f)
		cout << dis[n][n] << "\n";
	else
		puts("-1");
	return 0;
}

T4

其实是简单题。矩形问题套路地考虑扫描线。将矩形按照 y1 排序,线段树维护 x 维度。考虑用 y1 和下面的矩形建立联系,用 y2 更新区间的上边。建立联系考虑并查集维护。对于一个区间,显然只有原先只被一个矩形覆盖的时候可以直接更新,否则要继续分治直到只被单个矩形覆盖为止。

对于时间复杂度,更新上边的操作单次显然是 O(logn) 的,对于合并,"拆分" 一个区间的代价是对新的区间合并,均摊下来是 O(nlogn) 的,于是 o(nlognα(n))

代码:

#include <bits/stdc++.h>
#define N 100005
#define M 100000
using namespace std;
int n;
int fa[N];
int fnd(int x) {
	return x == fa[x] ? x : (fa[x] = fnd(fa[x]));
}
void mge(int x, int y) {
	x = fnd(x), y = fnd(y);
	if (x != y)
		fa[x] = y;
}
struct Node {
	int l, r;
	int mx;
	int fg;
	int tp;
	int id; 
} e[N << 2];
#define lc (p << 1)
#define rc (lc | 1)
#define l(i) e[i].l
#define r(i) e[i].r
#define mx(i) e[i].mx
#define fg(i) e[i].fg
#define tp(i) e[i].tp
#define id(i) e[i].id
void push_up(int p) {
	if (!tp(lc) || !tp(rc))
		return tp(p) = 0, void();
	if (id(lc) == id(rc)) {
		tp(p) = 1;
		mx(p) = mx(lc);
		id(p) = id(lc);
	}
	else
		tp(p) = 0;
}
void build(int p, int l, int r) {
	l(p) = l, r(p) = r;
	tp(p) = 1;
	if (l == r)
		return;
	int mid = (l + r) >> 1;
	build(lc, l, mid);
	build(rc, mid + 1, r);
}
void push_down(int p) {
	if (fg(p) == 0)
		return;
	fg(lc) = fg(rc) = 1;
	mx(lc) = mx(rc) = mx(p);
	id(lc) = id(rc) = id(p);
	tp(lc) = tp(rc) = 1;
	fg(p) = 0;
}
void update(int p, int l, int r, int mx, int id) {
	if (l > r || l > r(p) || l(p) > r)
		return;
	if (l <= l(p) && r(p) <= r && tp(p)) {
		if (mx(p) > mx)
			return;
		fg(p) = 1;
		mx(p) = mx;
		id(p) = id;
		return;
	}
	push_down(p);
	update(lc, l, r, mx, id);
	update(rc, l, r, mx, id);
	push_up(p);
}
void query(int p, int l, int r, int mx, int id) {
	if (l > r || l > r(p) || l(p) > r)
		return;
	if (l <= l(p) && r(p) <= r && tp(p)) {
		if (mx(p) >= mx)
			mge(id(p), id);
		return;
	}
	push_down(p);
	query(lc, l, r, mx, id);
	query(rc, l, r, mx, id);
}
struct node {
	int x1, x2, y1, y2;
	bool operator < (const node &x) const {
		return y1 < x.y1;
	}
} t[N];
int main() {
	freopen("jux.in", "r", stdin);
	freopen("jux.out", "w", stdout);
	cin >> n;
	for (int i = 1; i <= n; i++) 
		scanf("%d%d%d%d", &t[i].x1, &t[i].y1, &t[i].x2, &t[i].y2), fa[i] = i;
	sort(t + 1, t + 1 + n);
	build(1, 1, M);
	for (int i = 1; i <= n; i++) {
		query(1, t[i].x1, t[i].x2, t[i].y1, i);
		update(1, t[i].x1, t[i].x2, t[i].y2, i);
	}	
	unordered_map<int, int>mp;
	for (int i = 1; i <= n; i++)
		mp[fnd(i)] = 1;
	cout << mp.size() << "\n";
	return 0;
}
posted @   长安19路  阅读(14)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
点击右上角即可分享
微信分享提示