[赛记] 暑假集训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的什么输入法
天色天歌天籁音 50pts
赛时分块 + 莫队 + 线段树没卡过去,赛后证明确实不用线段树(为啥一遇到分块赛时就死活过不去啊。。。);
首先转化题意:找区间众数的出现次数;
用一下回滚莫队,因为发现删除操作比较不好做 (其实挺好做,只不过我不会。。。),所以用只加不减的莫队即可;
对于回滚莫队,之前学的时候没时间练,导致约等于没学,所以今天赛时的时候忘了,用的普通莫队也没对,赛后用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步统计了经过根的路径;
至于第一步的原因,一方面是因为重儿子合并到轻儿子上不划算,另一方面是因为如果不消除影响的话,第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;
}