[Contest on 2022.6.14] 彻底寄了
\(\cal T_1\) 区间第 \(k\) 小
Description
给定一个长度为 \(n\) 的序列和一个常数 \(w\),所给序列中的任意一个数 \(x\) 满足 \(0 \leqslant x < n\)。现在每次询问 \((l, r, k)\),表示对所给序列询问区间 \([l, r]\) 中第 \(k\) 小的数。需要注意的是,如果某一个数 \(x\) 在这段区间中的出现次数超过了 \(w\),则在求第 \(k\) 小的数时应将区间中所有的数字 \(x\) 视为数字 \(n\).
\(n,w,q\leqslant 10^5\),强制在线。
Solution
崩溃,\(\mathcal O(n\log^2n)\) 的正解 \(\mathtt{T}\) 成了赛上写的部分分 ??,我真的好难受!
先讲一下会 \(\mathtt{T}\) 的做法吧:首先,当 \(w=10^5\) 时就是一个裸的树套树,外层做权值线段树,内层做区间线段树,查询时在权值线段树上二分即可。考虑出现次数超过 \(w\) 这个限制不好在询问时处理,所以我们直接预处理 —— 具体就是将权值线段树可持久化,\(rt_r\) 维护询问右端点为 \(r\) 的权值线段树,这样假设 \(a_r=v\),那么先将权值 \(v\) 的区间线段树的下标 \(r\) 加上 \(1\),再找到往左数第 \(w+1\) 个为 \(v\) 的位置 \(p\),将权值 \(v\) 的区间线段树的下标 \(p\) 加上 \(-w-1\),再往 \(p\) 往左数第 \(2\) 个为 \(v\) 的位置 \(p'\),将权值 \(v\) 的区间线段树的下标 \(p'\) 加上 \(w\),就可以成功维护一系列信息。
我猜想这个做法 \(\mathtt{T}\) 成 \(\rm sb\) 的原因是加入一个数就修改了三次外层线段树,还有就是两层递归常数很大?反正就是 \(\mathtt{T}\) 得我心碎,\(\mathtt{T}\) 得我流泪。
事实上可以仿照树状数组套权值线段树的写法,预处理出根的数组,再在权值线段树上二分询问。所以考虑将权值线段树放里面,同时为了减少修改外层线段树的次数,可以把单点修改改成区间修改(?),使得属于 \(rt_r\) 区间线段树的 \(l\) 点表示 \([l,r]\) 而不是单点即可,具体就看代码吧。
Code
# include
# include
# define print(x,y) write(x), putchar(y)
template
inline T read(const T sample) {
T x=0; char s; bool f=0;
while(!isdigit(s=getchar())) f |= (s=='-');
for(; isdigit(s); s=getchar()) x=(x<<1)+(x<<3)+(s^48);
return f? -x: x;
}
template
inline void write(T x) {
static int writ[50], w_tp=0;
if(x<0) putchar('-'), x=-x;
do writ[++w_tp]=x-x/10*10, x/=10; while(x);
while(putchar(writ[w_tp--]^48), w_tp);
}
# include
using namespace std;
const int maxn = 1e5+5;
vector vec[maxn];
int n, W, Q, ty, a[maxn], rk[maxn], rt[maxn], idx, Idx;
int ls[maxn*200], rs[maxn*200], s[maxn*200];
int Ls[maxn*600], Rs[maxn*600], ss[maxn*600], Rt[maxn], all;
void ins(int& o,int pre,int l,int r,int p,int k) {
Ls[o=++Idx]=Ls[pre], Rs[o]=Rs[pre], ss[o] = ss[pre]+k;
if(l==r) return; int mid = l+r>>1;
if(p<=mid) ins(Ls[o],Ls[pre],l,mid,p,k);
else ins(Rs[o],Rs[pre],mid+1,r,p,k);
}
void insert(int& o,int pre,int l,int r,int L,int R,int k,int p) {
ls[o=++idx]=ls[pre], rs[o]=rs[pre], s[o]=s[pre];
if(l>=L && r<=R) return ins(s[o],s[pre],0,n,p,k), void();
int mid = l+r>>1;
if(L<=mid) insert(ls[o],ls[pre],l,mid,L,R,k,p);
if(R>mid) insert(rs[o],rs[pre],mid+1,r,L,R,k,p);
}
void transL() {
for(int i=1;i<=all;++i) Rt[i]=Ls[Rt[i]];
}
void transR() {
for(int i=1;i<=all;++i) Rt[i]=Rs[Rt[i]];
}
int query(int l,int r,int k) {
if(l==r) return l; int mid = l+r>>1, S=0;
for(int i=1;i<=all;++i) S += ss[Ls[Rt[i]]];
if(k<=S) return transL(), query(l,mid,k);
return transR(), query(mid+1,r,k-S);
}
void addRoot(int o,int l,int r,int p) {
if(!o) return; Rt[++all]=s[o];
if(l==r) return; int mid = l+r>>1;
if(p<=mid) addRoot(ls[o],l,mid,p);
else addRoot(rs[o],mid+1,r,p);
}
int main() {
freopen("kth.in","r",stdin);
freopen("kth.out","w",stdout);
n=read(9), W=read(9), Q=read(9), ty=read(9);
for(int i=1;i<=n;++i)
vec[a[i]=read(9)].emplace_back(i),
rk[i] = vec[a[i]].size()-1;
for(int i=1;i<=n;++i) {
int las = rk[i]>=W? vec[a[i]][rk[i]-W]: 0;
insert(rt[i],rt[i-1],1,n,las+1,i,1,a[i]);
if(rk[i]>=W) {
int lst = (rk[i]==W? 0: vec[a[i]][rk[i]-W-1]);
insert(rt[i],rt[i],1,n,lst+1,las,-W,a[i]);
}
} int lastans=0;
while(Q --) {
int l=read(9), r=read(9);
l ^= (lastans*ty), r ^= (lastans*ty);
int k = read(9)^(lastans*ty); all=0;
addRoot(rt[r],1,n,l);
print(lastans=query(0,n,k),'\n');
}
return 0;
}
\(\cal T_2\) 求和
Description
众所周知,当 \(x\) 的质因数分解为 \(∏_i p_i^{a_i}\) 时,有
\[\mu(x) = \prod_i (?1)^{a_i} [a_i \leqslant 1] \]类似地,定义
\[f_k(x) = \prod_i (?1)^{a_i} [a_i \leqslant k] \]现在给定 \(n, k\),求
\[\sum_{i=1}^{n}\sum_{j=1}^n\sum_{d=1}^k f_d(\gcd(i,j))\pmod {2^{30}} \]\(0
Solution
半年未写反演题!老年选手激情推柿一分未得黯然离场。
根据做反演题的经验,上来就是一个枚举 \(\gcd\)
\[\sum_{r=1}^n\left(\sum_{i=1}^n\sum_{j=1}^n [\gcd(i,j)=r] \right)\cdot \sum_{t=1}^k f_t(r) \]中间那一坨是经典莫反,于是答案就变成
\[\sum_{r=1}^n\left(\sum_{r\mid d} \left\lfloor \frac{n}{d} \right\rfloor^2 \cdot \mu(d/r)\right)\cdot \sum_{t=1}^k f_t(r) \]考虑 \(\sum_{r\mid d} \left\lfloor \dfrac{n}{d} \right\rfloor^2 \cdot \mu(d/r)\),枚举 \(d/r\),那么原式等于 \(\sum_{i=1}^{\lfloor n/r \rfloor} \mu(i)\cdot\left\lfloor \dfrac{n}{ir} \right\rfloor^2\),又因为 \(\left\lfloor \dfrac{n}{ir} \right\rfloor^2=\left\lfloor \dfrac{\left\lfloor \frac{n}{r} \right\rfloor}{i} \right\rfloor^2\),证明请戳 。为了方便观察,记 \(h(x)=\sum_{i=1}^{ x} \mu(i)\cdot\left\lfloor \dfrac{x}{i} \right\rfloor^2\),那么答案就变成
\[\sum_{r=1}^n h\left(\lfloor n/r \rfloor\right)\cdot \sum_{t=1}^k f_t(r) \]于是可以对 \(h\) 进行整除分块,对于每个 \(\lfloor n/r \rfloor\),还需要再用整除分块算一次函数值,然后用 \(\text{min-25}\) 筛算出对应 \(r\) 区间的 \(f_t\) 前缀和。
这是过不了的,我们计算 \(h\) 的复杂度实在是太大了。可以将 \(\sum_{i=1}^n\sum_{j=1}^n [\gcd(i,j)=r]\) 表示成一个更为熟知的积性函数[1] —— \(2\cdot \left(\sum_{i=1}^{\lfloor n/r \rfloor} \varphi(i)\right)-1\),考虑枚举 \(\gcd(i,j)=r\) 实际上就是枚举小于等于 \(\lfloor n/r \rfloor\) 的 \(x,y\),满足 \(\gcd(x,y)=1\)。不妨设 \(x>y\),那么枚举 \(x\),对应 \(y\) 的数量为 \(\varphi(x)\),\(y\) 同理,最后需要减去 \((1,1)\).
令 \(h(r)=2\cdot \left(\sum_{i=1}^{r} \varphi(i)\right)-1\),于是可以用 \(\text{min-25}\) 筛筛出 \(\varphi(i)\) 的前缀和,容易发现需要的点值都是 \(\lfloor n/r \rfloor\) 的形式,这些在计算 \(h(n)\) 时也会一并被算出,特意记录一下函数值即可。复杂度 \(\mathcal O\left(\frac{kn^{3/4}}{\log n}\right)\).
Code
先咕掉。
\(\cal T_3\) 树
Description
给定一棵 \(n\) 个点的树,若按照 \(u\rightarrow v\) 的方向经过边 \((u,v)\) 且 \(u
现在给定每个点最终的点权,尝试构造 \(m\) 条路径 \((x_1,y_1),(x_2,y_2),...,(x_m,y_m)\),使得经过这些路径之后能得到给定的点权。在最小化 \(m\) 的条件下,要求 \(x_1,y_1,x_2,y_2,...,x_m,y_m\) 的字典序最小。
\(n\leqslant 10^6\),数据保证答案的 \(m\leq n\).
Solution
一些闲话:这题不知道怎么搞的,当时竟然觉得网络流 ??,结果 \(n=50\) 都不会做(。
事实上本题的关键在于 这是树形结构。所以先考虑叶子节点,且不考虑路径 只考虑一条边 进行影响,那么它的 \(a\) 可以消除父亲的 \(a\),最后消除到根节点。此时可以发现,每个点的 \(a\) 的绝对值都代表和父亲有多少条匹配边,具体方向由父子编号大小关系决定。
现在考虑将匹配边合并成路径。显然如果存在 \(u\rightarrow v,v\rightarrow w\),我们可以将其贪心地合并成 \(u\rightarrow w\),而且这一定是最优的。合并之后我们就得到了每个点作为起/终点的次数(容易发现每个点只能作为一种点),事实上,直接将起/终点塞进 \(\rm vector\),各自排序就能得到最优解 —— 这是因为如果有 \(A\rightarrow B,C\rightarrow D\) 的路径,这也相当于 \(A\rightarrow D,B\rightarrow C\) 的路径,因为这是一棵树,两个点之间的简单路径只有一条。于是这相当于起点终点可以任意交换,所以上述做法是正确的。
Code
# include
# include
# define print(x,y) write(x), putchar(y)
template
inline T read(const T sample) {
T x=0; char s; bool f=0;
while(!isdigit(s=getchar())) f |= (s=='-');
for(; isdigit(s); s=getchar()) x=(x<<1)+(x<<3)+(s^48);
return f? -x: x;
}
template
inline void write(T x) {
static int writ[50], w_tp=0;
if(x<0) putchar('-'), x=-x;
do writ[++w_tp]=x-x/10*10, x/=10; while(x);
while(putchar(writ[w_tp--]^48), w_tp);
}
# include
# include
using namespace std;
const int maxn = 1e6+5;
vector g[maxn], x, y;
int n, a[maxn], deg[maxn];
int Abs(int x) { return x>0?x:-x; }
void dfs(int u,int fa) {
for(const auto& v:g[u]) if(v^fa) {
dfs(v,u), a[u] -= a[v];
int tmp = (u0, tmp=Abs(deg[i]);
while(tmp) {
if(f) x.emplace_back(i);
else y.emplace_back(i); --tmp;
}
}
sort(x.begin(),x.end());
sort(y.begin(),y.end());
print(x.size(),'\n');
for(int i=0; i
注意 \(h(x)=\sum_{i=1}^{ x} \mu(i)\cdot\left\lfloor \dfrac{x}{i} \right\rfloor^2\) 并不是一个卷积,不要被它的形式诈骗了! ??