数据结构专项测试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\) ,有

\[\begin{align*} ans &=\sum_i^n\sum_j^n\varphi(i\times j)\operatorname{dist}(loc_i,loc_j) \\ &=\sum_i^n\sum_j^n\frac{\varphi(i)\varphi(j)gcd(i,j)}{\varphi(gcd(i,j))}\operatorname{dist}(loc_i,loc_j) \\ &=\sum_g^n\frac{g}{\varphi(g)}\sum_i^{\left\lfloor\frac{n}{g}\right\rfloor}\sum_j^{\left\lfloor\frac{n}{g}\right\rfloor}\varphi(ig)\varphi(jg)\operatorname{dist}(loc_{ig},loc_{jg})[gcd(i,j)=1] \end{align*} \]

莫比乌斯反演,有 \([gcd(i,j)=1]=\sum_{d|gcd(i,j)}\mu(d)\) ,于是有

\[\begin{align*} ans &=\sum_g^n\frac{g}{\varphi(g)}\sum_i^{\left\lfloor\frac{n}{g}\right\rfloor}\sum_j^{\left\lfloor\frac{n}{g}\right\rfloor}\varphi(ig)\varphi(jg)\operatorname{dist}(loc_{ig},loc_{jg})[gcd(i,j)=1] \\ &=\sum_g^n\frac{g}{\varphi(g)}\sum_i^{\left\lfloor\frac{n}{g}\right\rfloor}\sum_j^{\left\lfloor\frac{n}{g}\right\rfloor}\sum_{d|gcd(i,j)}\mu(d)\varphi(ig)\varphi(jg)\operatorname{dist}(loc_{ig},loc_{jg}) \\ &=\sum_g^n\frac{g}{\varphi(g)}\sum_d^{\left\lfloor\frac{n}{g}\right\rfloor}\mu(d)\sum_i^{\left\lfloor\frac{n}{gd}\right\rfloor}\sum_j^{\left\lfloor\frac{n}{gd}\right\rfloor}\varphi(igd)\varphi(jgd)\operatorname{dist}(loc_{igd},loc_{jgd}) \end{align*} \]

\(t=gd\) ,那么

\[ans=\sum_t^n\sum_{g|t}\frac{g\mu(\frac{t}{g})}{\varphi(g)}\sum_i^{\left\lfloor\frac{n}{t}\right\rfloor}\sum_j^{\left\lfloor\frac{n}{t}\right\rfloor}\varphi(it)\varphi(jt)\operatorname{dist}(loc_{it},loc_{jt}) \]

到这里就可以了。 \(\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\)

\[\begin{align*} delta&=\sum_{x\in s}\sum_{y\in v}w_xw_y\operatorname{dist}(x,y) \\ &=\sum_{x\in s}w_x\sum_{y\in v}w_y(\operatorname{dist}(x,s)+\operatorname{dist}(s,v)+\operatorname{dist}(y,v)) \\ &=(\sum_{x\in s}w_x\sum_{y\in}w_y\operatorname{dist}(x,s))+(\sum_{x\in s}w_x\sum_{y\in v}w_y\operatorname{dist}(s,v))+(\sum_{x\in s}w_x\sum_{y\in}w_y\operatorname{dist}(y,v)) \end{align*} \]

这样就把 \(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;
}
posted @ 2021-12-18 20:31  keen_z  阅读(32)  评论(0编辑  收藏  举报