2019.11.5 newcoder

\(A-\)复读数组

题意:

https://ac.nowcoder.com/acm/contest/1103/A

有一个长为\(n \times k\)的数组,它是由长为\(n\)的数组\(A_1,A_2,…,A_n\)重复\(k\)次得到的。

定义这个数组的一个区间的权值为它里面不同的数的个数,现在,你需要求出对于这个数组的每个非空区间的权值之和。

答案对\(10^9+7\)取模。

数据范围:

对于所有数据,\(1≤n≤10^5,1≤k≤10^9,1≤A_i≤10^9\)

分析:

对于一个数\(x\),只要一个区间中出现了\(x\),而不论它的个数是多少,它对这个区间权值的贡献都只是1。

那么我们考虑枚举每一个\(x\)来求出含有它的区间的个数。

我们假设得到了\(x\)在这个\(n\times k\)的数组中出现的位置分别为\(p_1,p_2,\cdots,p_m\),因为我们发现如果要直接求\(x\)出现的区间会很麻烦,因为需要考虑有几个\(x\)以及各种问题,所以我们运用补集转化的思想,求出不含\(x\)的区间个数,用总区间个数减去这一部分的值,即为\(x\)的贡献。

因为对于一个长度为\(x\)的序列,它总共有\(\frac{x(x+1)}{2}\)的子区间(一个数也算做一个区间),所以我们设\(f(x)=\frac{x(x+1)}{2}\),那么可以得到不含x的区间总共有\(f(p_1-1)+f(n\times k-p_m)+\sum_{i=1}^{m-1}f(p_{i+1}-p_{i}-1)\)

首先\(f(p_1-1)\)\(f(n\times k-p_m)\)都是可以直接\(O(1)\)计算出来的,唯一比较麻烦的就是\(\sum_{i=1}^{m-1}f(p_{i+1}-p_i-1)\)这一部分。为了方便描述,我们就把这个\(n\times k\)的长度的数组描述为k个长度为n的数组。

我们通过观察是可以发现这个\(p_{i+1}-p_i\)只可能有两种情况,第一种是\(p_{i+1}\)\(p_i\)在同一个长度为n的数组中,这种情况总共会被计算\(k\)次(在每一个数组中都会被计算一次),另一种情况就是\(p_i\)是一个长度为\(n\)的数组中\(x\)这个数最后出现的位置,而\(p_{i+1}\)则是\(x\)在下一个长度为n的数组中第一次出现的位置,而这种情况则会被计算\(k-1\)次。

\(Code:\)

    #include<iostream>
    #include<cstdio>
    #include<algorithm>
    #include<cstring>
    #include<cmath>
    #include<map>
    #define ll long long
    using namespace std;
    
    const int maxn=1e5+10;
    const int mod=1e9+7;
    const int inv=5e8+4;
    
    int a[maxn];
    
    map<int,int> p,vis;
    
    int n,k;
    
    template<class T>void read(T &x)
    {
        bool f=0;char ch=getchar();x=0;
        for(;ch<'0'||ch>'9';ch=getchar()) if(ch=='-') f=1;
        for(;ch>='0'&&ch<='9';ch=getchar()) x=x*10+ch-'0';
        if(f) x=-x;
    }
    
    ll ans;
    
    ll f(ll x)
    {
        return x*(x+1)/2%mod;
    }
    
    int main()
    {
        read(n);read(k);
        for(int i=1;i<=n;++i)
        {
            read(a[i]);
            if(!p.count(a[i])) ans=(ans+f(1LL*n*k%mod))%mod,ans=(ans+mod-f(i-1))%mod;
            else ans=(ans+mod-f(i-p[a[i]]-1)*k%mod)%mod;
            p[a[i]]=i;
        }
        for(int i=1;i<=n;++i)
        {
            if(!vis.count(a[i]))
            {
                ans=(ans+mod-f(n-p[a[i]])-f(i-1+n-p[a[i]])*(k-1)%mod)%mod;
                vis[a[i]]=1;
            }
        }
        printf("%d\n",ans);
        return 0;
    }

\(B-\)路径计数机

题意:

https://ac.nowcoder.com/acm/contest/1103/B

有一棵n个点的树和两个整数\(p,q\),求满足以下条件的四元组\((a, b, c, d)\)的个数:

\(1. 1 \leqslant a, b, c, d \leqslant n\)
\(2.\)\(a\)到点\(b\)的经过的边数为\(p\)
\(3.\)\(c\)到点\(d\)的经过的边数为\(q\)
\(4.\)不存在一个点,它既在点\(a\)到点\(b\)的路径上,又在点\(c\)到点\(d\)的路径上。

数据范围:

对于所有数据\(1 \le n, p, q \le 3000,1 \le u, v \le n\),保证给出的是一棵合法的树。

分析:

首先我们可以先预处理出任意两点之间的距离(可以\(O(nlogn)\)先预处理,\(O(1)\)查询\(LCA\),或者直接用\(tarjan\)做到\(O(n)\)预处理\(O(1)\)查询\(LCA\),不过我打不来\(tarjan\),还可以直接\(dp\)一遍求出,下面的代码中就是这一种\(dp\)的写法)。

我们通过路径长度来枚举\(a\)\(b\),而满足条件的\(c\)\(d\)一共有两种情况:

\(1.lca(c,d)\)\(lca(a,b)\)的子树中

\(2.lca(c,d)\)不在\(lca(a,b)\)的子树中

对于第一种情况,我们发现只需要\(lca(c,d)\)不在\(a\)\(b\)的路径上即可,那么我们可以预处理出一个\(f(i)\)数组来表示\(lca\)\(i\)的且路径长度为\(q\)的路径条数即可。

而对于第二种情况,我们发现需要\(c\)\(d\)的路经不经过\(lca(a,b)\)\(fa(lca(a,b))\)之间的路径即可。这个时候我们类似之前的方法预处理出经过每一条边(上一种是每一个点)的路径长度为\(q\)的路径条数即可。

\(Code:\)

    #include<iostream>
    #include<cstdio>
    #include<algorithm>
    #include<cstring>
    #include<cmath>
    #include<vector>
    #define ll long long
    using namespace std;
    
    const int maxn=3010;
    
    vector<int> G[maxn];
    
    ll fp[maxn],fq[maxn],gp[maxn],gq[maxn],f[maxn][maxn],g[maxn][maxn];//其中fp/fq代表lca为
    //i且路径长度为p/q的路径条数,gp/gq代表经过了i与它父亲之间的边的路径长度为p/q的路径条数,f代表i的子
    //树中到i的距离为j的点的个数,g代表i子树以外的点中到点i的距离为j的点的个数
    
    int n,p,q;
    
    template<class T>void read(T &x)
    {
        bool f=0;char ch=getchar();x=0;
        for(;ch<'0'||ch>'9';ch-getchar()) if(ch=='-') f=1;
        for(;ch>='0'&&ch<='9';ch=getchar()) x=x*10+ch-'0';
        if(f) x=-x;
    }
    
    void dfs(int u,int fa)
    {
        f[u][0]=1;
        for(int i=0;i<G[u].size();++i)
        {
            int v=G[u][i];
            if(v==fa) continue;
            dfs(v,u);
            for(int j=1;j<=p;++j) fp[u]+=f[v][p-j]*f[u][j-1];
            for(int j=1;j<=q;++j) fq[u]+=f[v][q-j]*f[u][j-1];
            for(int j=1;j<=max(p,q);++j) f[u][j]+=f[v][j-1];
        }
    }
    
    void rdfs(int u,int fa)
    {
        for(int i=1;i<=p;++i) gp[u]+=f[u][p-i]*g[u][i];//肯定是经过了u的父亲节点的
        for(int i=1;i<=q;++i) gq[u]+=f[u][q-i]*g[u][i];
        for(int i=0;i<G[u].size();++i)
        {
            int v=G[u][i];
            if(v==fa) continue;
            g[v][1]=1;
            for(int j=2;j<=max(p,q);++j) g[v][j]+=g[u][j-1]+f[u][j-1]-f[v][j-2];
            rdfs(v,u);
        }
    }
    
    int main()
    {
        read(n);read(p);read(q);
        for(int i=1;i<=n-1;++i)
        {
            int u,v;
            read(u);read(v);
            G[u].push_back(v),G[v].push_back(u);
        }
        dfs(1,0),rdfs(1,0);
        ll sum1=0,sum2=0;
        for(int i=1;i<=n;++i) sum1+=fp[i],sum2+=fq[i];
        ll ans=sum1*sum2;
        for(int i=1;i<=n;++i) ans-=(fp[i]*fq[i]+fp[i]*gq[i]+fq[i]*gp[i]);
        printf("%lld\n",ans*4);
        return 0;
    }

\(C-\)排列计数机

题意:

https://ac.nowcoder.com/acm/contest/1103/C

定义一个长为\(k\)的序列\(A_1, A_2, \dots, A_k\)的权值为:对于所有\(1 \le i \le k,max(A_1, A_2, \dots, A_i)\)有多少种不同的取值。

给出一个\(1\)\(n\)的排列\(B_1, B_2, \dots, B_n\),求\(B\)的所有非空子序列的权值的\(m\)次方之和。

答案对\(10^9 + 7\)取模。

数据范围:

对于所有数据,\(1 \le n \le 10^5,1 \le m \le 20\),保证\(B\)\(1\)\(n\)的排列。

分析:

首先我们通过题解可以想出:

\(f(i,j,k)\)表示选到了第\(i\)个数(第\(i\)个数必须被选),选出的子序列的最大值为\(j\),而当前的权值为\(k\)的方案数,然后我们枚举下一个数选不选即可。

时间复杂度为\(O(n^4)\)

\(20pts\)

    for(int i=1;i<=n;++i)
    {
        dp[i][A[i]][1]=1;
        for(int j=1;j<=A[i]-1;++j)
        {
            for(int k=1;k<=i-1;++k)
            {
                for(int t=2;t<=i;++t)
                {
                    dp[i][A[i]][t]=(dp[i][A[i]][t]+dp[k][j][t-1])%mod;
                }
            }
        }
        for(int j=A[i]+1;j<=n;++j)
        {
            for(int k=1;k<=i-1;++k)
            {
                for(int t=1;t<=i;++t)
                {
                    dp[i][j][t]=(dp[i][j][t]+dp[k][j][t])%mod;
                }
            }
        }
    }
    for(int i=1;i<=n;++i)
    {
        for(int j=1;j<=n;++j)
        {
            for(int k=1;k<=i;++k) ans=(ans+d[i][j][k]*fpow(k,m)%mod)%mod;
        }
    }
    printf("%d\n",ans);

先优化一下,我们发现第一维并没有什么作用,所以我们就去掉第一维。

我们设\(dp(i,j)\)表示最大值为\(i\),而序列的权值为\(j\)的方案数。

\(40pts\)

    for(int i=1;i<=n;++i)
    {
        dp[A[i]][1]=1;
        for(int j=1;j<=A[i]-1;++j)
        {
            for(int k=1;k<=i;++k)
            {
                dp[A[i]][k]=(dp[A[i]][k]+dp[j][k-1])%mod;
            }
        }
        for(int j=A[i]+1;j<=n;++j)
        {
            for(int k=1;k<=i;++k)
            {
                dp[j][k]=(dp[j][k]+dp[j][k])%mod;
            }
        }
    }
    for(int i=1;i<=n;++i)
    {
        for(int j=1;j<=n;++j)
        {
            ans=(ans+dp[i][j]*fpow(j,m))%mod;
        }
    }
    printf("%d\n",ans);

对于\(m=1\)的情况,我们就相当于是求所有子序列的权值之和(这一点的思想和\(A\)题有点像)。

我们考虑一个数\(A[i]\)对我们最终的答案是有贡献的,那么在\([1,i-1]\)这一段中的被选择的数一定是要小于\(A[i]\)的, 而在\([i+1,n]\)这一段剩下的区间当中,它们的选法对\(A[i]\)已经没有影响了,因为\(A[i]\)已经提供了贡献。

所以我们设\([1,i-1]\)这一段中比\(A[i]\)小的数总共有\(x\)个,那么\(A[i]\)对答案的贡献最终就是\(2^{x+(n-i)}\)

\(x\)的计算我们使用树状数组来实现即可。

\(60pts\)

    int lowbit(int x)
    {
        return x&(-x);
    }
    void add(int x,int k)
    {
        for(int i=x;i<=n;i+=lowbit(i)) c[i]+=k;
    }
    int query(int x)
    {
        int res=0;
        for(int i=x;i;i-=lowbit(i)) res+=c[i];
    }
    void work()
    {
        for(int i=1;i<=n;++i)
        {
            int x=query(a[i]);
            ans=(ans+two[x+n-i])%mod;
            add(a[i],1);
        }
        printf("%d\n",ans);
    }

猜你喜欢

转载自www.cnblogs.com/iwillenter-top1/p/11808492.html