2024.8.8
\(0+90+0+10\)
挂分:\(\text{T1}\) 数组开小了,\(\text{T2}\) 最后一个点不小心 \(\text{RE}\) 了,\(\text{T3}\) 炸了,\(\text{T4}\) 大样例没骗到
然后这四个题一眼题名以为出题人语文功底多好,却是四个 \(\text{galgame}\)。
考场一眼 \(\text{T2}\) 莫名熟悉,还以为出原题 原来是前不久才刷过、、
T1 九次九日九重色
给定两个长度为 \(n\) 的 \(1\sim n\) 的排列 \(P\) 和 \(Q\),现在需要在 \(P\) 和 \(Q\) 中分别取出长度为 \(k\) 两个子序列 \(A\) 和 \(B\),满足 \(∀i∈[1,k]\),\(A_i \mid B_i\)。最大化 \(k\)。
考场思路
想想好像不咋好搞,要在 \(B\) 里找 \(A\) 的倍数,还得保证最大化,肯定得 \(\text{DP}\),但是好像只会 \(O(n^2)\) 的,先写吧。然后就设 \(f_{i,j}\) 表示 \(B\) 中选到 \(i\) 且匹配了 \(A\) 里的前 \(j\) 个且 \(B_i\) 与 \(A_j\) 匹配的最大匹配数。想转移,肯定至少要从 \(i-1\) 转移过来,好像不止,因为 \(i\) 可以不匹配。那么就得从 \(1\sim i-1\) 转移。\(j\) 也得从 \(1 \sim j-1\) 转移过来,这样才能对,那我天然就得到了一个 \(O(n^4)\) 的 \(\text{DP}\)。
这一坨东西能有分都是神迹。
预处理贡献优化。我们再设一个 \(g_{i,j}\) 表示 \(\displaystyle\max_{k=1}^{i-1}\max_{l=1}^{j-1}f_{k,l}\)。既然这个东西是由 \(f_{i,j}\) 转出来了,就可以从 \(f_{i,j}\) 向后递贡献。先给离它最近的 \(f_{i+1,j+1}\) 刷上贡献: \(g_{i+1,j+1} = f_{i,j}\)。再考虑给下一阶段的 \(i\) 刷上递推过来的贡献,\(g_{i+1,j} = \max{g_{i+1,j},g_{i,j}}\)。\(j\) 也是同理:\(g_{i+1,j} = \max{g_{i+1,j},g_{i+1,j-1}}\)。
接下来你就把它优化到了 \(O(n^2)\)。
但是空间只有 \(256 \text{MB}\),开俩 \(5000 \times 5000\) ,死了。又考虑到 \(5000 \times 5000\) 的范围的答案连 \(65535\) 都不到,于是就可以改 \(\text{short}\),于是就拿到了 \(30\)。
Sol1
\(\text{std}\) 的思路。考虑到每一个数的倍数用倍增是 \(\log\) 级别的,所以可以预处理每个数的倍数,即整除关系配对。把配好对的二元组按位置关系排序。注意排序的时候要将 \(b\) 的元素位置逆序排,更优秀。在里面跑一个 \(\text{LIS}\),然后用 \(O(n\log n)\) 乱搞处理 \(\text{LIS}\),总复杂度就是 \(O(n \log^2 n)\)。
顺便复习一下 \(O(n \log n)\) 求 \(\text{LIS}\)。还是顺推贡献,\(f_i\) 会对所有 \(a_j > a_i,j>i\) 的点产生贡献,先离散化,把值域缩到 \(n\) 以内,再用线段树在值域 \([a_i+1,n]\) 刷上贡献就行了,线段树维护区间修改 \(\max\),单点查询 \(\max\)。
const int N = 1e5+5;
int a[N];int c[N],b[N];int f[N];
struct TREE
{
#define lson(x) x<<1
#define rson(x) x<<1|1
struct NODE
{
int l,r,val,lz;
}tree[N<<2];
void pushup(int x){tree[x].val = max(tree[lson(x)].val ,tree[rson(x)].val);}
void build(int l,int r,int x)
{
tree[x].l = l,tree[x].r = r;
if(l == r){tree[x].val = 0;return;}
int mid = (l +r)>>1;
build(l,mid,lson(x)),build(mid+1,r,rson(x));
pushup(x);
}
void pushdown(int x)
{
if(tree[x].lz)
{
tree[lson(x)].lz = max(tree[x].lz,tree[lson(x)].lz);tree[rson(x)].lz =max(tree[x].lz,tree[rson(x)].lz);
int mid =(tree[x].l + tree[x].r)>>1;
tree[lson(x)].val = max(tree[lson(x)].val,tree[x].lz * (mid-tree[x].l+1));
tree[rson(x)].val = max(tree[rson(x)].val,tree[x].lz * (tree[x].r-mid));
tree[x].lz = 0;
}
}
void update(int l,int r,int k,int x)
{
if(l<=tree[x].l&&tree[x].r<=r) {tree[x].lz = max(tree[x].lz,k);tree[x].val =max(tree[x].val,k*(tree[x].r-tree[x].l+1));return;}
pushdown(x);
int mid = (tree[x].l + tree[x].r)>>1;
if(l<=mid) update(l,r,k,lson(x));
if(r>mid) update(l,r,k,rson(x));
pushup(x);
}
int query(int l,int r,int x)
{
if(l<=tree[x].l && tree[x].r <= r) return tree[x].val;
pushdown(x);
int mid = (tree[x].l + tree[x].r)>>1,res{-LONG_LONG_MAX};
if(l<=mid) res =max(res, query(l,r,lson(x)));
if(r>mid) res = max(query(l,r,rson(x)),res);
return res;
}
}tree;
signed main()
{
#ifdef LOCAL
freopen("in.in","r",stdin);
#endif
int n = read();
for(int i{1};i<=n;i++) c[i] = read();
memcpy(b,c,sizeof(int)*(n+1));
sort(b+1,b+1+n);
int l = unique(b+1,b+1+n)-b-1;
for(int i{1};i<=n;i++) a[i] = lower_bound(b+1,b+1+l,c[i])-b;
f[1] = 1;
tree.build(1,n,1);
for(int i{1};i<=n;i++)
{
f[i] = tree.query(a[i],a[i],1)+1;
tree.update(a[i]+1,n,f[i],1);
}
int ans{};
for(int i{1};i<=n;i++) ans = max(ans,f[i]);
writeln(ans);
}
Sol2
\(\text{XiaoLeMC}\) 的思路。其实大体是相同的,但是实现更简便。倒序跑 \(A\),在 \(B\) 内找到所有 \(A\) 的倍数,在线段树上维护序列 \(c\),修改 \(c_i\) 为 \(c_{i+1} \sim c_n\) 的最大值 \(+1\),跑完输出 \(c\) 中最大值就行了, \(O(n \log^2 n)\)。
T2 天色天歌天籁音
这题没啥说的,莫队维护区间众数板子。呃呃但是由于删除操作中的一些细节,导致有可能下标变 \(-1\),然后调了半天发现了,把完全死透改成了半死不活,就成 了 \(90\) ,以下是考场代码,把第三行和第二行换一下直接过。
while(rr<q[i].r) add(++rr);
while(rr>q[i].r) sub(rr--);
while(ll>q[i].l) add(--ll);
while(ll<q[i].l) sub(ll++);
T3 春色春恋春熙风
以 \(1\) 节点为根,每条边上都有一个
a
到v
中的字符。一条简单路径被称为 \(\lceil\) 熙 \(\rfloor\),当且仅当路径上的字符重新排序后能成为回文串。求每个点子树中最长 \(\lceil\) 熙 \(\rfloor\) 的长度。
本来题就看错了,题上说的是这个串里所有的字符都得在回文串里。那我写的链的情况就完全错掉了。链的部分分应该是二分长度,然后 \(O(n)\) 跑 \(\text{check}\),做前缀和,\(O(n\log n)\) 出。呃呃,没有链的大样例,\(10\) 分都不太好拿。
首先比较显然的是,一个串能够排成回文串当且仅当,它的所有字符数量中奇数的数量不超过 \(1\)。对于字符只有 \(22\) 种(你猜为什么是 \(22\) 种,不就是给状压用的…),所以可以状压,用 \(0\) 表示偶,\(1\) 表示奇。
对于路径 \((u,v)\) ,状态可以差分求出。设 \(val_u\) 表示 \(u\) 到 \(1\) 的路径上的字符状态,于是有 :\((u,v) = (val_u \oplus val_{lca}) \oplus (val_v \oplus val_{lca})\)(这个真的好聪明)。考虑到异或的特殊性,这就是 \(val_u \oplus val_v\)。于是一条路径合法当且仅当:\(\text{count}_{val_u \oplus val_v} \le 1\),就容易了。
-
我们可以枚枚举点对,以 \(O(23)\) 的复杂度 \(\text{check}\) \(val_u \oplus val_v\),一个点对 \((u,v)\) 对答案的贡献是 \(d_u + d_v - 2 d_{lca}\),从 \(\text{lca}\) 向根贡献答案复杂度 \(O(23n^2)\),期望得分 \(30\)(实测,如果你用线段树做到 \(O(n^2 \log n)\) 可以拿到 \(20\) 分)。
-
优化复杂度,我们对 \(2^{22}\) 种状态开桶,从一个点 \(x\) 开始搜索,遍历子树,对于一个状态 \(val_u\) 进行 \(\oplus\),于是检查桶内有没有合法解。
int head[N];
struct EDGE
{
int nxt,to;
char c;
}e[N<<1];
int ecnt{};
void add(int u,int v,char c)
{
e[++ecnt].nxt = head[u];e[ecnt].to = v;
e[ecnt].c= c;head[u] = ecnt;
}
int f[N],d[N],du[N],siz[N],son[N];
char cfa[N];
int val[N];
int dfn[N];
int ori[N];
int tot{};
void dfs(int u,int fa)
{
f[u] = fa;d[u] = d[fa]+1;
dfn[u]= ++tot;
ori[dfn[u]] = u;
siz[u] = 1;
for(int i{head[u]};i;i=e[i].nxt)
{
int v = e[i].to;
if(v == fa) {cfa[u] = e[i].c;continue;}
val[v]= val[u] ^ (1<<(e[i].c-'a'));
dfs(v,u);
siz[u] += siz[v];
if(siz[v] > siz[son[u]]) son[u] = v;
}
}
int ans[N];
int maxdep[N*10];
void dfs4(int u,int kp)
{
for(int i{head[u]};i;i=e[i].nxt)
{
int v = e[i].to;
if(v == son[u] || v == f[u]) continue;
dfs4(v,0);
ans[u] = max(ans[u],ans[v]);
}
if(son[u]) dfs4(son[u],1),ans[u] = max(ans[u],ans[son[u]]);
if (maxdep[val[u]]) ans[u] = max(ans[u], maxdep[val[u]] - d[u]);
for (int i{}; i <= 21; i++) {
if (maxdep[val[u] ^ (1 << i)]) ans[u] = max(ans[u], maxdep[val[u] ^ (1 << i)] - d[u]);
}
maxdep[val[u]] = max(d[u], maxdep[val[u]]);
for (int i = head[u]; i; i = e[i].nxt) {
int v = e[i].to;
if (v == son[u]) continue;
for (int j = dfn[v]; j <= dfn[v]+siz[v]-1; j++) {
int x = ori[j];
if (maxdep[val[x]]) ans[u] = max(ans[u], maxdep[val[x]] + d[x] - 2 * d[u]);
for (int k = 0; k <= 21; k++) {
if (maxdep[val[x] ^ (1 << k)]) ans[u] = max(ans[u], maxdep[val[x] ^ (1 << k)] + d[x] - 2 * d[u]);
}
}
for (int j = dfn[v]; j <= dfn[v]+siz[v]-1; j++) {
maxdep[val[ori[j]]] = max(maxdep[val[ori[j]]], d[ori[j]]);
}
}
if (!kp)
for (int i =dfn[u]; i <= dfn[u]+siz[u]-1; i++) maxdep[val[ori[i]]] = 0;
}
signed main()
{
#ifdef LOCAL
freopen("in.in","r",stdin);
#endif
cin.tie(0);
char c;
int n = read();
for(int i{2};i<=n;i++)
{
int u =read();char c;
cin>>c;
add(u,i,c);
}
dfs(1,0);
dfs4(1,1);
for(int i{1};i<=n;i++) writek(ans[i]);
return 0;
}