[赛记] 暑假集训CSP提高模拟16

$ Peppa \ Pig $ 都有时间写赛记了,看来现在这题是真不好改了

今天又是一题没切

九次九日九重色 0pts

原题:现找的

赛时理解错了子序列,给理解成了字串($ HDK $ 给我说的,要不我可能还不知道),导致大样例咋手模都出不来,干了45min,整了个不像暴力的暴力然后走了;

赛后证明,我的乱胡搞到了 $ 20pts $ ,但手欠后来把T2代码交到了T1里,导致 $ 0pts $;

这题和Luogu P2782 友好城市 挺像的;

依据调和级数(我的理解是:$ 1 + \frac12 + \frac13 + \ ... \ + \frac1n $ 是 $ \log n $ 级别的),可得 $ 1 $ 到 $ n $ 的倍数不超过 $ n \log n $,所以可以暴力预处理;

具体来讲,我们对 $ a_i $ 的每个在 $ B $ 中出现的倍数(不妨设为 $ b_j $ ),连一条从 $ i $ 到 $ j $ 的边(这里不用真的连,只需要用一个二元组存一下就行);

这样连完边后,不难发现我们要找的就是最多的边,使其两两不相交,那么排完序后对 $ j $ 做一个最长上升子序列即可;

对于排序,我们可以将每一个二元组按以 $ i $ 升序为第一关键字,$ j $ 降序为第二关键字排序,前者很显然,因为我们要从左往右选嘛,后者是为了避免 $ i $ 被重复选择,因为这样的话 $ i $ 在由以前的状态转移而来时就不会被以前的 $ i $ 转移而来,反之则有可能;

最后要用 $ \Theta(n \log n) $ 的最长上升子序列求,总时间复杂度为 $ \Theta(n \log n \log(n \log n)) $,空间复杂度 $ \Theta(n \log n) $;

点击查看代码
#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
int n;
int a[200005], b[200005];
int pos[200005];
int ans, cnt;
struct sss{
	int id, mat;
	bool operator <(const sss &A) const {
		if (id == A.id) return mat > A.mat;
		else return id < A.id;
	}
}e[5000005];
int c[5000005];
int d[5000005], len;
int main() {
	cin >> n;
	for (int i = 1; i <= n; i++) {
		cin >> a[i];
	}
	for (int i = 1; i <= n; i++) {
		cin >> b[i];
		pos[b[i]] = i;
	}
	for (int i = 1; i <= n; i++) {
		for (int j = 1; j * a[i] <= n; j++) {
			e[++cnt] = {i, pos[j * a[i]]};
		}
	}
	sort(e + 1, e + 1 + cnt);
	len = 1;
	for (int i = 1; i <= cnt; i++) {
		c[i] = e[i].mat;
	}
	d[1] = c[1];
	for (int i = 1; i <= cnt; i++) {
		if (c[i] > d[len]) {
			d[++len] = c[i];
		} else {
			d[lower_bound(d + 1, d + 1 + len, c[i]) - d] = c[i];
		}
	}
	cout << len;
	return 0;
}

另记:TM的什么输入法

image

image

天色天歌天籁音 50pts

原题:Luogu P3709 大爷的字符串题

赛时分块 + 莫队 + 线段树没卡过去,赛后证明确实不用线段树(为啥一遇到分块赛时就死活过不去啊。。。)

首先转化题意:找区间众数的出现次数;

用一下回滚莫队,因为发现删除操作比较不好做 (其实挺好做,只不过我不会。。。),所以用只加不减的莫队即可;

对于回滚莫队,之前学的时候没时间练,导致约等于没学,所以今天赛时的时候忘了,用的普通莫队也没对,赛后用1h+手造出来一个回滚莫队,貌似是对了(反正题库和洛谷过了),而且跑的还挺快。。。

代码仅供参考;

点击查看代码
#include <iostream>
#include <cstdio>
#include <map>
#include <cmath>
#include <algorithm>
#include <cstring>
using namespace std;
int n, m;
int a[500005];
int b[500005];
int cnt;
int sq;
int st[500005], ed[500005];
int belog[500005];
int ans[500005];
map<int, int> mp;
struct sss{
	int l, r, id;
	bool operator <(const sss &A) const {
		if (belog[l] == belog[A.l]) {
			return r < A.r;
		} else {
			return l < A.l;
		}
	}
}e[500005];
int ma, sum[500005];
int t[500005];
bool vis[500005];
inline void add(int x) {
	sum[x]++;
	ma = max(ma, sum[x]);
}
inline void del(int x) {
	sum[x]--;
}
int main() {
	ios::sync_with_stdio(0);
	cin.tie(0);
	cout.tie(0);
	cin >> n >> m;
	for (register int i = 1; i <= n; i++) {
		cin >> a[i];
		if (!mp[a[i]]) {
			mp[a[i]] = ++cnt;
		}
		b[i] = mp[a[i]];
	}
	sq = sqrt(n);
	for (register int i = 1; i <= sq; i++) {
		st[i] = (i - 1) * sq + 1;
		ed[i] = i * sq;
	}
	ed[sq] = n;
	for (register int i = 1; i <= sq; i++) {
		for (register int j = st[i]; j <= ed[i]; j++) {
			belog[j] = i;
		}
	}
	for (register int i = 1; i <= m; i++) {
		cin >> e[i].l >> e[i].r;
		e[i].id = i;
	}
	sort(e + 1, e + 1 + m);
	int l = 1;
	int r = 0;
	int ls = 0;
	for (register int i = 1; i <= m; i++) {
		ma = 0;
		if (belog[e[i].l] == belog[e[i].r]) {
			for (int j = e[i].l; j <= e[i].r; j++) {
				t[b[j]]++;
				ma = max(ma, t[b[j]]);
			}
			ans[e[i].id] = -ma;
			for (int j = e[i].l; j <= e[i].r; j++) {
				t[b[j]] = 0;
			}
			vis[i] = true;
			continue;
		}
		if (i == 1 || belog[e[i].l] != belog[e[i - 1].l] || vis[i - 1]) {
			ls = 0;
			while(l < ed[belog[e[i].l]]) del(b[l++]);
			while(r < ed[belog[e[i].l]] - 1) add(b[++r]);
			while(r > ed[belog[e[i].l]] - 1) del(b[r--]);
		}
		while(l < ed[belog[e[i].l]]) del(b[l++]);
		while(r < e[i].r) add(b[++r]);
		ls = max(ls, ma);
		while(l > e[i].l) add(b[--l]);
		ans[e[i].id] = -max(ls, ma);
	}
	for (register int i = 1; i <= m; i++) {
		cout << ans[i] << '\n';
	}
	return 0;
}

春色春恋春熙风 0pts

原题:Luogu CF Arpa’s letter-marked tree and Mehrdad’s Dokhtar-kosh paths

一个新的东西:$ dsu \ on \ tree $;

其实老早就想学,只不过没啥时间(其实是懒)

树上启发式合并,和大多数启发式合并相似,都是“一拍脑门”就想出来的,其主体思想是将轻儿子的信息合并到重儿子上(因为重儿子 $ size $ 大嘛),然后就可以做到时间复杂度 $ \Theta(n^2) \rightarrow \Theta(n \log n) $ 的转换;

具体的实现过程神似点分治,还是把路径分成经过根和不经过根两部分分别求解;

不同的是,它有三步操作:

  1. 统计轻儿子的答案,并更新当前根,但要消除其影响(就是点分治中的桶数组)

  2. 统计重儿子的答案,并更新当前根,不清除影响

  3. 遍历轻儿子,将它们的信息(即前文所述的影响)合并到重儿子上;

显然,第1,2步统计了不经过根的路径,第3步统计了经过根的路径;

至于第一步的原因,一方面是因为重儿子合并到轻儿子上不划算,另一方面是因为如果不消除影响的话,第3步时就会出现重复统计不经过根的路径的情况(因为此时的桶数组记录的信息中有当前子树的信息),导致错误;

至于为啥不把1,2步交换位置,因为此时桶数组只能清空轻儿子的影响,交换位置后可能会清空重儿子的影响;

1,3步不能合并的原因同上;

综上,为本题的树上启发式合并的主要内容;

实现真的很像点分治。。。

点击查看代码
#include <iostream>
#include <cstdio>
#include <string>
using namespace std;
int n;
struct sss{
	int t, ne, w;
}e[2000005];
int h[2000005], cnt;
void add(int u, int v, int ww) {
	e[++cnt].t = v;
	e[cnt].ne = h[u];
	h[u] = cnt;
	e[cnt].w = ww;
}
string s;
int dep[2000005], hson[2000005], siz[2000005];
int ans[2000005], dis[2000005];
int fa[2000005];
int L[2000005], R[2000005];
int nfd[2000005], dcnt;
int judge[(1 << 22) + 5], rem[(1 << 22) + 5];
void init_dfs(int x, int f) {
	hson[x] = -1;
	siz[x] = 1;
	dep[x] = dep[f] + 1;
	L[x] = ++dcnt; //欧拉序;
	nfd[dcnt] = x;
	fa[x] = f;
	for (int i = h[x]; i; i = e[i].ne) {
		int u = e[i].t;
		if (u == f) continue;
		dis[u] = dis[x] ^ e[i].w;
		init_dfs(u, x);
		siz[x] += siz[u];
		if (hson[x] == -1 || siz[hson[x]] < siz[u]) hson[x] = u;
	}
	R[x] = dcnt;
}
void solve(int x, int k) {
	for (int i = h[x]; i; i = e[i].ne) {
		int u = e[i].t;
		if (u == hson[x] || u == fa[x]) continue;
		solve(u, 0);
		ans[x] = max(ans[x], ans[u]);
	}
	if (hson[x] > 0) {
		solve(hson[x], 1);
		ans[x] = max(ans[x], ans[hson[x]]);
	}
	if (judge[dis[x]]) ans[x] = max(ans[x], judge[dis[x]] - dep[x]);
	for (int i = 0; i <= 21; i++) {
		if (judge[dis[x] ^ (1 << i)]) ans[x] = max(ans[x], judge[dis[x] ^ (1 << i)] - dep[x]);
	}
	judge[dis[x]] = max(judge[dis[x]], dep[x]);
	for (int i = h[x]; i; i = e[i].ne) {
		int uu = e[i].t;
		if (uu == fa[x] || uu == hson[x]) continue;
		for (int j = L[uu]; j <= R[uu]; j++) { //手动模拟DFS;
			int u = nfd[j];
			if (judge[dis[u]]) ans[x] = max(ans[x], judge[dis[u]] + dep[u] - 2 * dep[x]);
			for (int k = 0; k <= 21; k++) {
				if (judge[dis[u] ^ (1 << k)]) ans[x] = max(ans[x], judge[dis[u] ^ (1 << k)] + dep[u] - 2 * dep[x]);
			}
		}
		for (int j = L[uu]; j <= R[uu]; j++) {
			int u = nfd[j];
			judge[dis[u]] = max(judge[dis[u]], dep[u]);
		}
	}
	if (!k) {
		for (int i = L[x]; i <= R[x]; i++) { //手动清空;
			judge[dis[nfd[i]]] = 0;
		}
	}
}
int main() {
	cin >> n;
	int x;
	for (int i = 2; i <= n; i++) {
		cin >> x;
		cin >> s;
		add(x, i, (1 << (s[0] - 'a')));
		add(i, x, (1 << (s[0] - 'a')));
	}
	init_dfs(1, 0);
	solve(1, 1);
	for (int i = 1; i <= n; i++) {
		cout << ans[i] << ' ';
	}
	return 0;
}
posted @ 2024-08-08 20:21  Peppa_Even_Pig  阅读(13)  评论(0编辑  收藏  举报