数据结构专项测试1
T1 Surprise me!
数论题,不会写
首先忽略方案数,最后统一除去。
对欧拉函数 \(\varphi(x)\) ,有 $$\varphi(x\times y)=\frac{gcd(x,y)\times\varphi(x)\times\varphi(y)}{\varphi(gcd(x,y))}$$
可以从 \(\varphi(x)=x\times\prod_{p|x}\frac{p-1}{p}\) 的角度理解。两个欧拉函数相乘后多计算的部分即 \(gcd\) 的质因子。
于是推式子。令 \(a_{loc_i}=i\) ,即编号为 \(loc_i\) 的节点数值为 \(i\) ,有
莫比乌斯反演,有 \([gcd(i,j)=1]=\sum_{d|gcd(i,j)}\mu(d)\) ,于是有
设 \(t=gd\) ,那么
到这里就可以了。 \(\sum_{g|t}\frac{g\mu(\frac{t}{g})}{\varphi(g)}\) 是关于 \(t\) 的函数,可以线性筛出 \(\mu\) 与 \(\varphi\) 后枚举倍数 \(O(n\ln n)\) 得出。考虑后面的东西怎么求。
与上面相似的思想,同样枚举 \(t\) ,每次考虑 \(t\) 的倍数的数值对答案的贡献。这样复杂度同样是 \(O(n\ln n)\) 。
计算贡献时,因为每次考虑的节点不多,可以建出虚树后 \(DP\) 。令每个点的权值 \(w_i=\varphi(a_i)[t|i]\) ,即关键点的点权为它的数值的欧拉函数,其他点权为 \(0\) ,那么我们要求的就是 \(\sum_i\sum_jw_iw_j\operatorname{dist}(i,j)\) 。
继续拆式子,设 \(v\) 是 \(s\) 的儿子,现将 \(v\) 合并至 \(s\) ,设 \(g_s\) 为 \(s\) 子树中的答案,那么 \(g_s=g_s+g_v+delta\) ,
这样就把 \(x,s\) 和 \(y,v\) 放到了一起。设
- \(f_s=\sum_{x\in s}w_x\operatorname{dist}(x,s)\)
- \(d_s=\sum_{x\in s}w_x\)
进行辅助,就能完成转移。
\(code:\)
T1
#include<bits/stdc++.h>
using namespace std;
namespace IO{
typedef long long LL;
#define int LL
int read(){
int x=0,f=0; char ch=getchar();
while(ch<'0'||ch>'9'){ f|=(ch=='-'); ch=getchar(); }
while(ch>='0'&&ch<='9'){ x=(x<<1)+(x<<3)+(ch^48); ch=getchar(); }
return f?-x:x;
} char output[50];
void write(LL x,char sp){
int len=0;
if(x<0) x=-x, putchar('-');
do{ output[len++]=x%10+'0'; x/=10; }while(x);
for(int i=len-1;~i;i--) putchar(output[i]); putchar(sp);
}
void ckmin(int &x,int y){ x=x<y?x:y; }
void ckmax(int &x,int y){ x=x>y?x:y; }
} using namespace IO;
const int NN=200010,mod=1e9+7;
int n,ans,p[NN],loc[NN];
vector<int>e[NN];
void add(int a,int b){
e[a].push_back(b);
e[b].push_back(a);
}
namespace Mathematics{
int cnt,mu[NN],pri[NN],phi[NN],bas[NN];
bool vis[NN];
int qpow(int a,int b,int res=1){
for(;b;b>>=1,a=a*a%mod)
if(b&1) res=res*a%mod;
return res;
}
void get_functions(){
mu[1]=phi[1]=1;
for(int i=2;i<=n;i++){
if(!vis[i]) mu[i]=-1, phi[i]=i-1, pri[++cnt]=i;
for(int j=1;j<=cnt&&pri[j]*i<=n;j++){
int now=pri[j]*i;
vis[now]=1;
if(i%pri[j]==0){
mu[now]=0; phi[now]=phi[i]*pri[j];
break;
}
mu[now]=-mu[i]; phi[now]=phi[i]*phi[pri[j]];
}
}
for(int i=1;i<=n;i++) for(int j=i;j<=n;j+=i)
(bas[j]+=mod+mu[j/i]*i*qpow(phi[i],mod-2)%mod)%=mod;
}
} using namespace Mathematics;
namespace RMQforLCA{
int idx,fa[NN],lg[NN<<1],dep[NN],dfn[NN],seq[NN<<1][21];
int LCA(int x,int y){
x=dfn[x]; y=dfn[y];
if(x>y) swap(x,y);
int t=lg[y-x+1],u=seq[x][t],v=seq[y-(1<<t)+1][t];
return dep[u]<dep[v]?u:v;
}
void RMQ_dfs(int s,int f){
dfn[s]=++idx; fa[s]=f; seq[idx][0]=s; dep[s]=dep[f]+1;
for(int v:e[s]) if(v!=f)
RMQ_dfs(v,s), seq[++idx][0]=s;
}
void build_RMQ(){
for(int i=2;i<=idx;i++) lg[i]=lg[i>>1]+1;
for(int i=1;i<=20;i++)
for(int u,v,j=1;j<=idx-(1<<i)+1;j++){
u=seq[j][i-1]; v=seq[j+(1<<i-1)][i-1];
seq[j][i]=dep[u]<dep[v]?u:v;
}
}
} using namespace RMQforLCA;
namespace Virtual_Tree{
int w[NN],s[NN],f[NN],g[NN];
int top,stk[NN];
vector<int>ve[NN],key;
bool cmp(int a,int b){ return dfn[a]<dfn[b]; }
void vadd(int a,int b){ ve[a].push_back(b); }
void insert(int x){
if(top==1) return stk[++top]=x,void();
int lca=LCA(x,stk[top]);
if(lca==stk[top]) return stk[++top]=x,void();
while(top>1&&dfn[stk[top-1]]>=dfn[lca]) vadd(stk[top-1],stk[top]), --top;
if(lca!=stk[top]) vadd(lca,stk[top]), stk[top]=lca;
stk[++top]=x;
}
void build(int t){
key.clear(); stk[top=1]=1;
for(int i=t;i<=n;i+=t){
w[loc[i]]=phi[i];
if(loc[i]^1) key.push_back(loc[i]);
}
sort(key.begin(),key.end(),cmp);
for(int x:key) insert(x);
while(top>1) vadd(stk[top-1],stk[top]), --top;
}
void dfs(int u){
s[u]=w[u]; f[u]=g[u]=0;
for(int v:ve[u]) if(v!=fa[u]){
int len=dep[v]-dep[u];
dfs(v);
(g[u]+=g[v]+f[u]*s[v]+s[u]*s[v]%mod*len+f[v]*s[u])%=mod;
(f[u]+=s[v]*len+f[v])%=mod;
(s[u]+=s[v])%=mod;
}
ve[u].clear(); w[u]=0;
}
} using namespace Virtual_Tree;
signed main(){
n=read(); get_functions();
for(int i=1;i<=n;i++) p[i]=read(), loc[p[i]]=i;
for(int i=1;i<n;i++) add(read(),read());
RMQ_dfs(1,0); build_RMQ();
for(int i=1;i<=n;i++){
build(i); dfs(1);
(ans+=bas[i]*g[1]*2%mod)%=mod;
}
ans=ans*qpow(n,mod-2)%mod*qpow(n-1,mod-2)%mod;
write(ans,'\n');
return 0;
}
T2 神牛养成计划
又是类似二维偏序的题目,想办法用两种方法逐一满足限制。
对后缀,进行后缀排序,询问时二分找到满足后缀条件的区间,然后建出可持久化 \(Trie\) ,查询前缀在区间内的个数即可。
卡空间,要用 std::map
存 \(Trie\) , std::unordered_map
会 \(MLE\) 。
然后强制在线解码解错炸飞了
也有两棵 \(Trie\) 加 std::bitset
分块强行离线的神仙乱搞,也能用主席树加 \(Trie\) 写。
我这种写法算常数挺大的了。
\(code:\)
T2
#include<bits/stdc++.h>
using namespace std;
namespace IO{
typedef long long LL;
int read(){
int x=0,f=0; char ch=getchar();
while(ch<'0'||ch>'9'){ f|=(ch=='-'); ch=getchar(); }
while(ch>='0'&&ch<='9'){ x=(x<<1)+(x<<3)+(ch^48); ch=getchar(); }
return f?-x:x;
} char output[50];
void write(LL x,char sp){
int len=0;
if(x<0) x=-x, putchar('-');
do{ output[len++]=x%10+'0'; x/=10; }while(x);
for(int i=len-1;~i;i--) putchar(output[i]); putchar(sp);
}
void ckmin(int &x,int y){ x=x<y?x:y; }
void ckmax(int &x,int y){ x=x>y?x:y; }
} using namespace IO;
const int NN=2005,MM=2000005;
int n,len,ans;
char s1[MM],s2[MM];
namespace Suffix_Array{
const int NM=MM+NN;
char ch[NM];
int l,m,x[NM],y[NM],c[NM],sa[NM],st[NN],ed[NN],id[NN];
void Sort(){
for(int i=1;i<=m;i++) c[i]=0;
for(int i=1;i<=l;i++) ++c[x[i]];
for(int i=1;i<=m;i++) c[i]+=c[i-1];
for(int i=l;i>=1;i--) sa[c[x[y[i]]]--]=y[i];
}
void get_S(){
m=125;
for(int i=1;i<=l;i++) x[i]=ch[i],y[i]=i;
Sort();
for(int num,k=1;k<=l;k<<=1){
num=0;
for(int i=l-k+1;i<=l;i++) y[++num]=i;
for(int i=1;i<=l;i++) if(sa[i]>k) y[++num]=sa[i]-k;
Sort(); swap(x,y); x[sa[1]]=m=1;
for(int i=2;i<=l;i++){
if(y[sa[i]]!=y[sa[i-1]]||y[sa[i]+k]!=y[sa[i-1]+k]) ++m;
x[sa[i]]=m;
}
if(l==m) break;
}
for(int i=1;i<=l;i++) x[sa[i]]=i;
}
} using namespace Suffix_Array;
namespace Pers_Trie{
const int TN=2005000;
int tot,root[NN];
map<char,int>to[TN];
void insert(int l,int r,int k){
int u=(root[k]=++tot),v=root[k-1];
for(int i=r;i>=l;i--){
to[u]=to[v]; to[u][ch[i]]=++tot;
v=to[v].count(ch[i])?to[v][ch[i]]:0;
u=to[u][ch[i]];
sa[u]=sa[v]+1;
}
}
int query(int l,int r){
if(l>r) return 0;
int u=root[r],v=root[l-1];
for(int i=1;i<=len;i++){
u=to[u].count(s1[i])?to[u][s1[i]]:0;
v=to[v].count(s1[i])?to[v][s1[i]]:0;
}
return sa[u]-sa[v];
}
} using namespace Pers_Trie;
int cmp(int l,int ln){
for(int i=l,j=1;j<=ln;++i,++j){
if(ch[i]<s2[j]) return 0;
if(ch[i]>s2[j]) return 1;
}
return 2;
}
void getpos(int& lp,int& rp){
int l=1,r=n;
while(l<=r){
int mid=l+r>>1,now=cmp(st[id[mid]],len);
if(now==2) r=mid-1, lp=mid;
else if(now==1) r=mid-1;
else l=mid+1;
}
l=1; r=n;
while(l<=r){
int mid=l+r>>1,now=cmp(st[id[mid]],len);
if(now==2) l=mid+1, rp=mid;
else if(now==1) r=mid-1;
else l=mid+1;
}
}
signed main(){
n=read();
for(int i=1;i<=n;i++){
scanf("%s",s1+1); len=strlen(s1+1);
reverse(s1+1,s1+len+1); st[i]=l+1;
for(int i=1;i<=len;i++) ch[++l]=s1[i];
ed[i]=l; ch[++l]='#'; id[i]=i;
}
get_S(); memset(sa,0,sizeof(sa));
sort(id+1,id+n+1,[](int a,int b){ return x[st[a]]<x[st[b]]; });
for(int i=1;i<=n;i++) insert(st[id[i]],ed[id[i]],i);
m=read();
while(m--){
int lp=1,rp=0;
scanf("%s",s1+1); scanf("%s",s2+1);
len=strlen(s2+1);
reverse(s2+1,s2+len+1);
for(int i=1;i<=len;i++)
s2[i]=(s2[i]-'a'+ans)%26+'a';
getpos(lp,rp);
len=strlen(s1+1);
for(int i=1;i<=len;i++)
s1[i]=(s1[i]-'a'+ans)%26+'a';
write(ans=query(lp,rp),'\n');
}
return 0;
}
T3 串
把以每个点为左端点得到的最大区间和放进堆里,每次取出最大值并删去,用同一左端点的次大值代替,重复 \(k-1\) 次就能得到答案。
求最大值可以用主席树维护区间修改全局最大得到。不大会标记永久化,强行 \(pushdown\) 了一波。
\(code:\)
T3
#include<bits/stdc++.h>
using namespace std;
namespace IO{
typedef long long LL;
#define int LL
int read(){
int x=0,f=0; char ch=getchar();
while(ch<'0'||ch>'9'){ f|=(ch=='-'); ch=getchar(); }
while(ch>='0'&&ch<='9'){ x=(x<<1)+(x<<3)+(ch^48); ch=getchar(); }
return f?-x:x;
} char output[50];
void write(LL x,char sp){
int len=0;
if(x<0) x=-x, putchar('-');
do{ output[len++]=x%10+'0'; x/=10; }while(x);
for(int i=len-1;~i;i--) putchar(output[i]); putchar(sp);
}
void ckmin(int &x,int y){ x=x<y?x:y; }
void ckmax(int &x,int y){ x=x>y?x:y; }
} using namespace IO;
const int NN=100010;
int n,k,a[NN];
map<int,int>pre;
priority_queue<pair<int,int>>q;
namespace Pers_SegmentTree{
const int TN=NN*180;
int tot,root[NN],lc[TN],rc[TN],tag[TN],mxv[TN],mxp[TN];
void down(int rt,int vl){ tag[rt]+=vl; mxv[rt]+=vl; }
int clone(int v){
int u=++tot;
lc[u]=lc[v]; rc[u]=rc[v]; tag[u]=tag[v]; mxv[u]=mxv[v]; mxp[u]=mxp[v];
return u;
}
void pushup(int rt){
mxv[rt]=max(mxv[lc[rt]],mxv[rc[rt]]);
mxp[rt]=(mxv[rt]==mxv[lc[rt]]?mxp[lc[rt]]:mxp[rc[rt]]);
}
void pushdown(int rt){
if(!tag[rt]) return;
lc[rt]=clone(lc[rt]); down(lc[rt],tag[rt]);
rc[rt]=clone(rc[rt]); down(rc[rt],tag[rt]);
tag[rt]=0;
}
void build(int& rt,int l=1,int r=n){
rt=++tot; mxv[rt]=-1e16;
if(l==r){ mxp[rt]=l; return; }
int mid=l+r>>1;
build(lc[rt],l,mid);
build(rc[rt],mid+1,r);
pushup(rt);
}
void update(int& rt,int opl,int opr,int val,int l=1,int r=n){
if(opl>opr) return;
rt=clone(rt);
if(l>=opl&&r<=opr) return down(rt,val);
pushdown(rt);
int mid=l+r>>1;
if(opl<=mid) update(lc[rt],opl,opr,val,l,mid);
if(opr>mid) update(rc[rt],opl,opr,val,mid+1,r);
pushup(rt);
}
} using namespace Pers_SegmentTree;
signed main(){
n=read(); k=read();
build(root[0]);
for(int i=1;i<=n;i++){
a[i]=read();
root[i]=root[i-1];
update(root[i],i,i,1e16);
update(root[i],pre[a[i]]+1,i,a[i]);
q.push({mxv[root[i]],i});
pre[a[i]]=i;
}
while(--k){
int x=q.top().second; q.pop();
update(root[x],mxp[root[x]],mxp[root[x]],-1e16);
q.push({mxv[root[x]],x});
}
write(q.top().first,'\n');
return 0;
}