20260113 省选模拟赛
20260113 省选模拟赛
https://htoj.com.cn/cpp/oj/contest/detail?cid=22497077965824
Problem B. 树的直径
枚举直径中点。 对直径长度分类讨论:
-
长度为奇数。那么中点一定是一条权为 \(1\) 的边。
-
长度为偶数。中点一定是一条全为 \(0\) 的链(可能退化为一个点)。枚举两端不好做,但使用 点边容斥,点权值为 \(1\),边权值为 \(-1\),和为 \(1\)。
所以 分别对每个点和每条边统计以它为中心的直径条数即可。但发现长度是奇数和偶数时,边的贡献抵消了,所以只用算点。
枚举每个点为根。设 \(f_{x,i}\) 为 \(x\) 子树内 \(mxd=i\),最深点个数之和,\(g_{x,i}\) 为 \(x\) 子树 \(mxd=i\) 的方案数。枚举边权,每次是个 max 卷积状物,暴力做即可,树上背包复杂度,\(O(n^2)\)。
在根统计答案,设 \(h_i\) 为两条最长链长为 \(i\) 的方案数,转移仍然是 max 卷积状物。总复杂度 \(O(n^3)\)。
先用前缀和把 max 卷积优化做到 \(O(a+b)\)。然后换根,每次合并周围节点时先按 \(mxd\) 从小到大排序再做。一条边只会在两端各合并一次,每次合并周围节点时 \(mxd\) 递增,和为 \(O(n)\),所以这部分总复杂度 \(O(n^2)\)。
在根统计答案时,每条边也只会在两端合并一次,这部分复杂度也是 \(O(n^2)\)。
int CC,n;
int fa[N];
vector<int> e[N];
void Add(int u,int v){
e[u].push_back(v);
e[v].push_back(u);
}
const ll mod=1e9+7;
inline ll Mod(ll x){return (x>=mod)?(x-mod):(x);}
inline void Add(ll &x,ll y){x=Mod(x+y);}
struct Node{
ll f,g;
friend Node operator + (Node x,Node y){
return Node{Mod(x.f+y.f),Mod(x.g+y.g)};
}
friend Node operator * (Node x,Node y){
return Node{(x.f*y.g+x.g*y.f)%mod,x.g*y.g%mod};
}
friend Node operator * (Node x,ll y){
return Node{x.f*y%mod,x.g*y%mod};
}
};
vector<Node> f[N],g[N],pre[N],suf[N];
ll h[N],H[N];
vector<Node> Merge(vector<Node> A,vector<Node> B){
vector<Node> C;
int ca=A.size(),cb=B.size();
C.resize(max(ca,cb+1));
for(int i=0;i<min(ca,cb+1);i++){
if(i<cb) C[i]=C[i]+A[i]*B[i];
if(i) C[i]=C[i]+A[i]*B[i-1];
}
ll sum=0;
for(int i=1;i<ca;i++){
if(i-1<cb) Add(sum,B[i-1].g);
if(i>=2&&i-2<cb) Add(sum,B[i-2].g);
C[i]=C[i]+A[i]*sum;
}sum=0;
for(int i=1;i<cb;i++){
if(i-1<ca) Add(sum,A[i-1].g);
C[i]=C[i]+B[i]*sum;
}sum=0;
for(int i=1;i<=cb;i++){
if(i-1<ca) Add(sum,A[i-1].g);
C[i]=C[i]+B[i-1]*sum;
}
return C;
}
void dfs1(int x,int pr){
fa[x]=pr;
f[x].push_back(Node{1,1});
for(int y:e[x]){
if(y==pr) continue;
dfs1(y,x);
f[x]=Merge(f[x],f[y]);
}
}
vector<Node>& Get(int x,int y){
return fa[y]==x?f[y]:g[x];
}
void dfs2(int x,int pr){
sort(e[x].begin(),e[x].end(),[&](int p,int q)
{return Get(x,p).size()<Get(x,q).size();});
for(int y:e[x]){
if(y==pr) continue;
g[y].push_back(Node{1,1});
for(int z:e[x]){
if(z==y) continue;
g[y]=Merge(g[y],Get(x,z));
}
dfs2(y,x);
}
}
signed main(){
read(CC),read(n);
for(int i=1;i<n;i++){
int u,v;
read(u),read(v);
Add(u,v);
}
dfs1(1,0);
dfs2(1,0);
ll ans=0;
for(int x=1;x<=n;x++){
vector<Node> X;
X.push_back(Node{1,1});
memset(h,0,sizeof(h)); h[0]=1;
for(int y:e[x]){
memset(H,0,sizeof(H));
vector<Node> W=Get(x,y);
ll sum=0; int cw=W.size(),cx=X.size();
for(int j=0;j<=n;j++){
if(j<cw) Add(sum,W[j].g);
if(j&&j-1<cw) Add(sum,W[j-1].g);
(H[j]+=h[j]*sum)%=mod;
if(j<cw&&j<cx) (H[j]+=X[j].f*W[j].f)%=mod;
if(j&&j-1<cw&&j<cx) (H[j]+=X[j].f*W[j-1].f)%=mod;
}
memcpy(h,H,sizeof(h));
X=Merge(X,W);
}
ll res=0;
for(int i=0;i<=n;i++) Add(res,h[i]);
Add(ans,res);
}
printf("%lld\n",(ans-n+mod)%mod);
return 0;
}
Problem C. 广为人知
离线扫描线,转化为区间加 \(1\),区间历史异或和的异或和。
观察 1: \(x\) 加一等价于异或 \(x\oplus (x+1)\)。
观察 2: \(x\oplus(x+1)=2^{h(x)}-1\),\(h(x)\) 为 \(x\) 最低位 \(0\) 所在的位。
观察 3: 在 \(i\) 时刻异或 \(v\),只会对 \(i,i+2,i+4,i+6,\cdots\) 时刻的历史异或和贡献 \(v\)。
这样就只需要维护每个点奇偶时刻的异或和。
看上去不太好 polylog,就考虑分块。块长为 \(B\),需要维护整块的 \(ans_{i,0/1}\),每个点的 \(his_{i,0/1}\)。
整块修改时,枚举 \(h(c_i)=k\),那么需要知道 \(c_i\equiv 2^{k}-1 \pmod{2^{k+1}}\) 的个数。根号分治,设一个阈值 \(2^K\), \(<2^K\) 的位用桶维护,\(\ge 2^K\) 的每个数只会出现 \(\frac n {2^K}\) 次,可以暴力更新。
散块修改时,先下放标记,然后暴力更改,重构块。
下放标记,\(c_i\) 更新是简单的,还需要根据 \(tag\) 来更新 \(his_i\)。我们把 \(tag_j\) 存下来,对每个 \(i\) 枚举 \(k\),若 \(c_i+tag_j\equiv 2^k-1\pmod{2^{k+1}}\) 就要异或 \(2^{k+1}-1\)。和上面类似做。
现在总复杂度 \(O(nKB+\frac{n^2K}{B}+\frac{n}{2^K})\),平衡到 \(O(n\sqrt{n}\log n)\)。
考虑怎么去掉 \(\log\)。
整块修改时,若我们能求出 \(w_{x}\) 表示当前 \(tag\equiv x\pmod{2^K}\) 再加一时给 \(ans\) 造成的贡献,就能 \(O(1)\) 修改。
首先需要知道 \(c_j\equiv i \pmod{2^k}\) 的 \(j\) 个数,记为 \(f_{k,i}\)。而 \(f_{k,i}=f_{k+1,i}+f_{k+1,i+2^k}\),这是个 从低位到高位的满二叉 trie 树结构,可以 \(O(2^K)\) 求。接下来求 \(w_x\),将 \(2^{k+1}-1\) 拆为 \(2^0+2^1+\cdots+2^k\),那么 \(c_j+x\equiv 2^k-1\pmod{2^k}\) 时 \(j\) 就给所有这样的 \(x\) 造成 \(2^k\) 的贡献。而 \(w_x\) 就是 \(2^K-1-x\) 在 trie 树上的路径权值异或和。所以从下到上求 \(f\),从上到下再求 \(w\) 即可。
下放标记时,我们也把 \(tag_j\) 放到 trie 上,然后查询 \(2^K-1-(c_i\bmod 2^K)\) 路径。
实现中可以不建出 trie,可以先对底层做蝴蝶变换(翻转二进制位),然后类似线段树更新。
const int B=128,M=B+5,D=N/B+5,K=7;
int n,a[N],lst[N],Q,Ans[N];
int c[N],C,L[D],R[D],bl[N],ans[D][2],his[N][2];
int w[D][M],tag[D],head[D][M],nxt[D][M],ele[D][M],tot[D],tg[D][2][M];
int f[M<<1],g[M<<1],tran[M],lg[M];
vector<pii> qy[N];
void Rebuild(int p){
ans[p][0]=ans[p][1]=0;
for(int i=L[p];i<=R[p];i++){
ans[p][0]^=his[i][0];
ans[p][1]^=his[i][1];
}
for(int i=0;i<B;i++){
head[p][i]=0;
tg[p][0][i]=tg[p][1][i]=0;
}
tot[p]=0;
for(int i=L[p];i<=R[p];i++){
int x=c[i]&(B-1);
ele[p][++tot[p]]=i;
nxt[p][tot[p]]=head[p][x];
head[p][x]=tot[p];
}
memset(f,0,sizeof(f));
for(int i=L[p];i<=R[p];i++) f[tran[c[i]&(B-1)]|B]^=1;
for(int i=B-1;i;i--) f[i]=f[i<<1]^f[i<<1|1];
g[0]=0;
for(int i=1;i<(B<<1);i++){
g[i]=g[i>>1];
if(i<B) g[i]^=f[i]<<lg[i];
}
for(int i=0;i<B;i++) w[p][i]=g[B|tran[B-i-1]];
}
void Pushdown(int p){
if(!tag[p]) return;
for(int t=0;t<=1;t++){
memset(f,0,sizeof(f));
for(int i=0;i<B;i++) f[tran[i]|B]=tg[p][t][i];
for(int i=B-1;i;i--) f[i]=f[i<<1]^f[i<<1|1];
g[0]=0;
for(int i=1;i<(B<<1);i++){
g[i]=g[i>>1];
if(i<B) g[i]^=f[i]<<lg[i];
}
for(int i=L[p];i<=R[p];i++)
his[i][t]^=g[B|tran[B-1-(c[i]&(B-1))]];
}
for(int i=L[p];i<=R[p];i++) c[i]+=tag[p];
tag[p]=0;
}
void Modify1(int l,int r,int t){
for(int i=l;i<=r;i++){
his[i][t]^=c[i]^(c[i]+1);
++c[i];
}
}
void Modify2(int p,int t){
ans[p][t]^=w[p][tag[p]&(B-1)];
tg[p][t][tag[p]&(B-1)]^=1;
int x=B-1-(tag[p]&(B-1));
for(int i=head[p][x];i;i=nxt[p][i]){
int y=ele[p][i];
int v=(c[y]+tag[p])^(c[y]+tag[p]+1)^(B-1);
his[y][t]^=v,ans[p][t]^=v;
}
++tag[p];
}
void Update(int l,int r,int t){
int p=bl[l],q=bl[r];
if(p==q){
Pushdown(p);
Modify1(l,r,t);
Rebuild(p);
return;
}
Pushdown(p),Pushdown(q);
Modify1(l,R[p],t),Modify1(L[q],r,t);
for(int i=p+1;i<=q-1;i++) Modify2(i,t);
Rebuild(p),Rebuild(q);
}
int Ask(int l,int r,int t){
int p=bl[l],q=bl[r],res=0;
if(p==q){
Pushdown(p);
for(int i=l;i<=r;i++) res^=his[i][t];
Rebuild(p);
return res;
}
Pushdown(p),Pushdown(q);
for(int i=l;i<=R[p];i++) res^=his[i][t];
for(int i=L[q];i<=r;i++) res^=his[i][t];
for(int i=p+1;i<=q-1;i++)res^=ans[i][t];
Rebuild(p),Rebuild(q);
return res;
}
signed main(){
read(n),read(Q);
for(int i=1;i<=n;i++) read(a[i]);
for(int i=1;i<=n;i+=B){
++C; L[C]=i,R[C]=min(i+B-1,n);
for(int j=L[C];j<=R[C];j++) bl[j]=C;
}
for(int i=2;i<=B;i++) lg[i]=lg[i>>1]+1;
for(int i=1;i<B;i++) tran[i]=(tran[i>>1]>>1)|((i&1)<<(K-1));
for(int i=1;i<=Q;i++){
int l,r;
read(l),read(r);
qy[r].push_back({l,i});
}
for(int i=1;i<=n;i++){
Update(lst[a[i]]+1,i,i&1);
lst[a[i]]=i;
for(pii j:qy[i]) Ans[j.second]=Ask(j.first,i,i&1);
}
for(int i=1;i<=Q;i++) printf("%d\n",Ans[i]);
return 0;
}

浙公网安备 33010602011771号