zoukankan      html  css  js  c++  java
  • HGOI20190815 题解

    Problem A modsum

    求$sumlimits_{i=1}^{n} sumlimits_{j=1 , i eq j}^{m} (n mod i)(m mod j)$

    在模$ 19940417 $的意义下计算答案。

    对于$100\%$的数据,满足$1 leq n,m leq 10^9$

    Solution :

    由于$n,m$的答案和$m,n$的答案都是一样的,我们不妨令$nleq m$

    一眼想到了整除分块,我们显然可以通过$O(sqrt{n})$的复杂度求出

    $G(n,k) = sumlimits_{i=1}^{n} (k mod i)$的值。

    我们考虑化简原来的式子,原式$ = sumlimits_{i=1}^{n} ((sumlimits_{j=1}^{m} (n mod i)(m mod j))- (n mod i)(m mod i))$

    $ = sumlimits_{i=1}^nsumlimits_{j=1}^{m}(n mod i)(m mod j) - sumlimits_{i=1}^{n} (n mod i)(m mod i)$

    $ = sum_{i=1}^nsum_{j=1}^{m}(n mod i)(m mod j) -sum_{i=1}^{n}(n-i imes left lfloor frac{n}{i} ight floor)(m-i imes left lfloor frac{m}{i} ight floor)$

    $== sum_{i=1} ^n (n mod i)sum_{i=1}^{m} (m mod i)- n^2 m + msum_{i=1}^{n} i imes left lfloor frac{n}{i} ight floor + nsum_{i=1}^{n} i imes left lfloor frac{m}{i} ight floor - sum_{i=1}^{n} left lfloor frac{n}{i} ight floor  left lfloor frac{m}{i} ight floor i^2$

    对于$ sum_{i=1}^{n} left lfloor frac{n}{i} ight floor  left lfloor frac{m}{i} ight floor i^2$的求法可以整除分块,每一次跳最小值来实现。

    由于最多是有$2sqrt{n}$条线,所以复杂度是$O(sqrt{n})$

    注意到$19940417$ 不是质数,然而$sum_{i=1}^{n} i^2 = frac{n imes(n+1) imes(2n+1)}{6}$中的$6$和它是互质的,所以可以简单的for循环求逆元即可。

    # include <bits/stdc++.h>
    # define int long long
    using namespace std;
    const int mo=19940417;
    int inv6=3323403;
    int inv2=9970209;
    int mul(int x,int n)
    {
        int ans=0;
        while (n) {
            if (n&1) ans=(ans+x)%mo;
            n>>=1;
            x=(x+x)%mo; 
        }
        return ans%mo;
    }
    int fun(int l,int r){
        l--; 
        return (((mul(mul(r,r+1),2*r+1)*inv6)%mo-(mul(mul(l,l+1),2*l+1)*inv6)%mo)%mo+mo)%mo;
    }
    int calc1(int n,int m)
    {
        int ret=0;
        for (int l=1,r1,r2,r;l<=n;l=r+1) {
            if (n/l!=0) r1=min(n/(n/l),n); else r1=n;
            if (m/l!=0) r2=min(m/(m/l),n); else r2=m;
            r=min(r1,r2);
            ret=(ret + mul(mul((n/l),(m/l)),fun(l,r)))%mo;
        }
        return ret%mo;
    }
    int calc(int n,int k) {
        int ret=0;
        for (int l=1,r;l<=n;l=r+1) {
            if (k/l!=0) r=min(k/(k/l),n);
            else r=n;
            ret=(ret + mul(mul(mul(k/l,(r-l+1)),(l+r)),inv2))%mo;
        }
        return ret%mo;
    }
    int G(int n,int k) { return ((mul(n,k)-calc(n,k))%mo+mo)%mo;}
    signed main()
    { 
        int n,m; scanf("%lld%lld",&n,&m);
        if (n>m) swap(n,m);
        int ans=mul(G(n,n),G(m,m))-mul(mul(n,n),m)+mul(m,calc(n,n))+mul(n,calc(n,m))-calc1(n,m);
        printf("%lld
    ",(ans%mo+mo)%mo);
        return 0;
    }
    A.cpp

     Problem B queue

    给出一棵多叉树,求关于所有节点的排列数,要求:

      1. 每棵子树的根节点先于这棵子树的其他节点出现。

      2. 和一个节点直接相连的节点必须依次出现但可不连续。

    在$mod 10007$ 意义下询问排列数。
    对于$100\%$的数据$1 leq nleq 10^3$

    Solution :

     可以将多叉树转化为二叉树,答案显然不变。

     一种显然的转化方法就是将每个点的儿子按照一条链连接下去,这样一定保证是二叉树。

     设$f[u]$表示将$u$的子树排列的可行数目,显然从$f[l_{son}]$和$f[r_{son}]$转移过来。

     显然,$f[u] = f[l_{son}] imes f[r_{son}] imes C_{size[l_{son}]+size[r_{son}]}^{size[l_{son}]}$

     其中合并的贡献就是在$size[l_{son}]+size[r_{son}]$个位置上放$size[l_{son}]$个数字的方案数(顺序没有关系)。

     复杂度是 预处理组合数的复杂度是$O(n^2)$  

    # include <bits/stdc++.h>
    # define int long long
    using namespace std;
    const int N=2005;
    const int mo=10007;
    vector<int>E[N];
    int c[N][N],n,size[N],f[N];
    int C(int n,int m){return c[n][m];}
    void dfs(int u)
    {
        if (E[u].size()==0) {
            size[u]=1; f[u]=1;
            return;
        }
        if (E[u].size()==1) {
            dfs(E[u][0]); size[u]=size[E[u][0]]+1;
            f[u]=f[E[u][0]];
        }
        if (E[u].size()==2) {
            dfs(E[u][0]); dfs(E[u][1]);
            size[u]=size[E[u][0]]+size[E[u][1]]+1;
            f[u]=f[E[u][0]]*f[E[u][1]]%mo*C(size[E[u][0]]+size[E[u][1]],size[E[u][0]])%mo;
        }
    }
    signed main()
    {
        c[0][0]=1;
        for (int i=1;i<=2000;i++) {
            c[i][0]=c[i][i]=1;
            for (int j=1;j<i;j++)
             c[i][j]=(c[i-1][j-1]+c[i-1][j])%mo;
        }
        int T; scanf("%lld",&T); 
        while (T--) {
            scanf("%lld",&n);
            for (int i=1;i<=n;i++) f[i]=0;
            for (int i=1;i<=n;i++) E[i].clear();
            for (int i=1;i<=n;i++) {
                int k; scanf("%lld",&k);
                int nowf=i;
                for (int j=1;j<=k;j++) {
                    int x; scanf("%lld",&x);
                    E[nowf].push_back(x);
                    nowf=x;
                }
            }
            dfs(1);
            printf("%lld
    ",f[1]);
        }
        return 0;
    }
    B.cpp

     Problem C city

     每个点的点权是$w[i]$,经过一条边的代价是$|w[u] - w[v]|$.

     定义一条路径$val_S$的代价是路径经过边的最大代价,即$val_S = max{w[Edge] (Edge in S)}$

     设需要的能量为$D$,设两点存在“正常的关系”为:至少有两条不重叠的路径$S,T$使得$D leq val_S , D leq val_T$

     询问$u,v$是否是可能是正常的关系,如果是,输出所需最少能量$D$,否则输出"infinitely"

     对于$100\%$的数据$n,m,q leq 5 imes 10^5 $

    Solution : 

      建出最小生成树,这样树边都不是可行边,从小到大拿非树边取更新答案。

      由于非树边$(u,v)$添加上后会形成环,就存在边双了,这个环上的所有点的答案就是这条边的权值。

      我们只需要将这些点的答案用这条边的权值更新掉即可。

      由于边权排序了,所以后面的边来更新一定不是最优的,所以我们只需要用一个并查集将树上路径折叠掉即可。

      询问的时候直接求一遍倍增lca即可。

      复杂度是$O(n log_2 n)$

    # include<bits/stdc++.h>
    using namespace std;
    const int N=1e5+10,M=5e5+10;
    struct rec{ int pre,to,w;}a[N<<1];
    struct A{ int u,v,w;}Edge[M];
    bool cmp(A a,A b){return a.w<b.w;}
    int tot,n,m,q,val[N],g[N][22],d[N][22],f[N],head[N],dep[N];
    bool inTree[M]; 
    void adde(int u,int v)
    {
        a[++tot].pre=head[u];
        a[tot].to=v;
        head[u]=tot;
    }
    int father(int x)
    {
        if (f[x]==x) return x;
        return f[x]=father(f[x]);
    }
    void kruskal()
    {
        for (int i=1;i<=n;i++) f[i]=i;
        sort(Edge+1,Edge+1+m,cmp);
        for (int i=1;i<=m;i++) {
            int fx=father(Edge[i].u),fy=father(Edge[i].v);
            if (fx == fy) continue;
            f[fy] = fx; 
            adde(Edge[i].u,Edge[i].v);
            adde(Edge[i].v,Edge[i].u);
            inTree[i]=1; 
        } 
    }
    void dfs(int u,int fa)
    {
        g[u][0]=fa; dep[u]=dep[fa]+1;
        for (int i=head[u];i;i=a[i].pre) {
            int v=a[i].to; if (v==fa) continue;
            dfs(v,u);
        }
    }
    void init()
    {
        for (int i=1;i<=21;i++) 
         for (int j=1;j<=n;j++)
          g[j][i]=g[g[j][i-1]][i-1],
          d[j][i]=max(d[j][i-1],d[g[j][i-1]][i-1]);
    }
    int query(int u,int v){
        int ret=0;
        if (dep[u]<dep[v]) swap(u,v);
        for (int i=21;i>=0;i--)
         if (dep[g[u][i]]>=dep[v]) ret=max(ret,d[u][i]),u=g[u][i];
        if (u==v) return ret;
        for (int i=21;i>=0;i--) if (g[u][i]!=g[v][i]) {
            ret=max(max(ret,d[u][i]),d[v][i]);
            u=g[u][i]; v=g[v][i];
        }  
        return max(ret,max(d[u][0],d[v][0]));
    }
    int main()
    {
    //  freopen("city.in","r",stdin);
    //  freopen("city.out","w",stdout);
        scanf("%d%d%d",&n,&m,&q);
        for (int i=1;i<=n;i++) scanf("%d",&val[i]);
        for (int i=1;i<=m;i++) 
         scanf("%d%d",&Edge[i].u,&Edge[i].v),Edge[i].w=abs(val[Edge[i].v]-val[Edge[i].u]);
        kruskal();  dfs(1,0);
        for (int i=1;i<=n;i++) f[i]=i;
        for (int i=1;i<=m;i++) if (!inTree[i]) {
            int fx=father(Edge[i].u),fy=father(Edge[i].v);
            while (fx!=fy) {
                if (dep[fx]<dep[fy]) swap(fx,fy);
                d[fx][0]=Edge[i].w; f[fx]=g[fx][0]; fx=father(fx); 
            }
        }
        init();
        while (q--) {
            int u,v; scanf("%d%d",&u,&v);
            if (father(u)!=father(v)) puts("infinitely");
            else printf("%d
    ",query(u,v));
        }
        return 0;
    }
    C.cpp

     

  • 相关阅读:
    elasticsearch 心得
    elasticsearch window下配置安装
    centos 配置sentry+钉钉+邮件通知
    git 多账户链接不同gitlab仓库
    git 配置远程仓库(同一个邮箱注册多个gitlab仓库)
    配置git远程连接gitlab
    上传模型方法-断点续传方法
    three.js group遍历方法
    sql 行转列超快方法
    赴日本IT的相关注意事项和坑!!!!
  • 原文地址:https://www.cnblogs.com/ljc20020730/p/11357335.html
Copyright © 2011-2022 走看看