zoukankan      html  css  js  c++  java
  • HDU多校训练第一场 1012 Sequence

    题目链接:acm.hdu.edu.cn/showproblem.php?pid=6589

    题意:给出一个长度为n的数组,有m次操作,操作有3种1,2,3,问操作m次后的数组,输出i*a[i]的异或和

    操作k的实质是进行一次O(n)的计算,a[i]+=a[i-k] (i-k>0)

    k=1时,我们可以发现这是一次求前缀和的操作

    k=2时,我们可以发现这是对于1,3,5,7... 2,4,6,8...两个子数组分别进行求前缀和的操作

    k=3时,我们可以发现这是对于1,4,7,11...2,5,8,12...3,6,9,12...三个子数组分别求前缀和的操作

    暴力的复杂度是O(mn),我们可以模拟出暴力的过程,其实这并不是一个浪费时间的过程,因为在比赛时,我们通过这个暴力的程序验算样例,发现了一个性质,那就是操作顺序的改变,并不会影响结果!

    这个性质是解题的关键,如果没有发现这个性质,那么是想不到正解的,那么,问题的本质就变成了如何快速求出m次前缀和,粗略一想很显然这还是个o(nm)的操作,其实不然

    观察求前缀和的过程

    0次(不求):a[1],a[2],a[3],a[4],a[5]...

    1次:         a[1],a[2]+a[1],a[3]+a[2]+a[1],a[4]+a[3]+a[2]+a[1],a[5]+a[4]+a[3]+a[2]+a[1]...

    2次:         a[1],a[2]+2a[1],a[3]+2a[2]+3a[1],a[4]+2a[3]+3a[2]+4a[1],a[5]+2a[4]+3a[3]+4a[2]+5a[1]...

    3次:         a[1],a[2]+3a[1],a[3]+3a[2]+6a[1],a[4]+3a[3]+6a[2]+10a[1],a[5]+3a[4]+6a[3]+10a[2]+15a[1]...

    ...

    这里,规律就很明显了,我们可以发现进行多次前缀和后的数组,它的结果是和组合数有关的

    第m次,组合数数组应该是c[i]=C(m+i-2,i-1),那么,上述结果用数组表示就是

    m次:      c[1]*a[1],c[1]*a[2]+c[2]*a[1],c[1]*a[3]+c[2]*a[2]+c[3]*a[1],c[1]*a[4]+c[2]*a[3]+c[3]*a[2]+c[4]*a[1],c[1]*a[5]+c[2]*a[4]+c[3]*a[3]+c[4]*a[2]+c[5]*a[1]...

    这个东西已经很明显了,就是数组a[1],a[2],a[3],a[4],a[5]... 与b[1],b[2],b[3],b[4],b[5]...求卷积的结果,组合数的求法,O(m)预处理,O(1)求解即可,这是个很经典的方法,这里就不再赘述,百度上很多

    求卷积有NTT(快速数论变换)与FFT(快速傅立叶变换)两种方法,也许你并不会这两个方法,这没有关系,套模板就行了,对于k=2,k=3的情况,我们只需要将数组拆分成子数组,就可以变成k=1的形式了,问题也就解决了

    值得一提的是,由于FFT是复数操作,存在浮点误差,而且取模是一个魔法操作(不会),所以这里还是用NTT比较合适,注意一个细节,由于要多次使用板子,所以每次用完一定要把板子里面应该重置的数据要初始化,

    特别是那两个用来求卷积的数组!!!

    做一次卷积,我们就可以得到n次前缀和后的数组,整体时间复杂度O(m+nlogn)

    上代码:

    #include <bits/stdc++.h>
    using namespace std;
    #define maxn 300005//注意用来求卷积的数组的大小
    #define MOD 998244353
    #define mod MOD
    #define G 3
    typedef long long ll;
    namespace NTT {//模板内容
        int rev[maxn], n, m;
        long long A[maxn], B[maxn], C[maxn];
    
        inline ll Pow(ll a, ll k) {
            ll base = 1;
            while (k) {
                if (k & 1) base = (base * a) % MOD;
                a = (a * a) % MOD;
                k >>= 1;
            }
            return base % MOD;
        }
    
        void NTT(long long *a, int len, int opt) {
            for (int i = 0; i < len; i++) {
                if (i < rev[i]) {
                    swap(a[i], a[rev[i]]);
                }
            }
            for (int i = 1; i < len; i <<= 1) {
                long long wn = Pow(G, (opt * ((MOD - 1) / (i << 1)) + MOD - 1) % (MOD - 1));
                int step = i << 1;
                for (int j = 0; j < len; j += step) {
                    long long w = 1;
                    for (int k = 0; k < i; k++, w = (1ll * w * wn) % MOD) {
                        long long x = a[j + k];
                        long long y = 1ll * w * a[j + k + i] % MOD;
                        a[j + k] = (x + y) % MOD;
                        a[j + k + i] = (x - y + MOD) % MOD;
                    }
                }
            }
            if (opt == -1) {
                long long r = Pow(len, MOD - 2);
                for (int i = 0; i < len; i++)
                    a[i] = 1ll * a[i] * r % MOD;
            }
        }
    
        void solve(int n, int m) {
            int x, l = 0 ,len = 1;
            while (len <= n + m) len <<= 1, ++l;
            for (int i = 0; i < len; ++i)
                rev[i] = (rev[i >> 1] >> 1) | ((i & 1) << (l - 1));
            NTT(A, len, 1), NTT(B, len, 1);
            for (int i = 0; i < len; ++i){
                C[i] = (ll) (A[i] * B[i]) % MOD;
                A[i]=B[i]=0;
            }
            NTT(C, len, -1);
        }
    }
    template <class T>
    void read(T &x) {
        static char ch;static bool neg;
        for(ch=neg=0;ch<'0' || '9'<ch;neg|=ch=='-',ch=getchar());
        for(x=0;'0'<=ch && ch<='9';(x*=10)+=ch-'0',ch=getchar());
        x=neg?-x:x;
    }
    int n,cnt[4];
    ll a[100005],c[100005];
    ll fac[1000005],inv[1000005];
    ll pow_mod(ll m,ll n)
    {
        ll res=1;
        while (n)
        {
            if(n&1)res=res*m%mod;
            m=m*m%mod;
            n>>=1;
        }
        return res;
    }
    void init()
    {
        inv[0]=fac[0]=1;
        for(int i=1;i<=1000000;i++)fac[i]=fac[i-1]*i%mod;
        inv[1000000]=pow_mod(fac[1000000],mod-2);
        for(int i=999999;i>=1;i--)inv[i]=inv[i+1]*(i+1)%mod;
    }
    ll C(int n,int m)
    {
        if(m==0)return 1;//这个地方是为了特殊处理调cnt=0的情况,这个时候的c数组应该是1,0,0,0,0...
        if(n-m<0)return 0;
        return fac[n]*inv[n-m]%mod*inv[m]%mod;
    }
    void calc(int m)//求m次前缀和的组合数数组
    {
        for(int i=1;i<=n;i++){
            c[i]=C(m-2+i,i-1);
        }
    }
    int main()
    {
        init();//组合数预处理
        int T;
        cin>>T;
        while (T--)
        {
            memset(cnt,0, sizeof(cnt));
            int m,op;
            read(n);read(m);
            for(int i=1;i<=n;i++)
            {
                read(a[i]);
            }
            for(int i=1;i<=m;i++)
            {
                read(op);
                ++cnt[op];
            }
            calc(cnt[1]);
            for(int i=0;i<n;i++)NTT::A[i]=a[i+1];
            for(int i=0;i<n;i++)NTT::B[i]=c[i+1];
            NTT::solve(n,n);
            for(int i=0;i<n;i++)a[i+1]=NTT::C[i];
            calc(cnt[2]);
            vector<int>d1,d2,d3;
            for(int i=1;i<=n;i++)
            {
                i%2?d1.emplace_back(a[i]):d2.emplace_back(a[i]);
            }
            for(int i=0;i<d1.size();i++)NTT::A[i]=d1[i];
            for(int i=0;i<d1.size();i++)NTT::B[i]=c[i+1];
            NTT::solve(d1.size(),d1.size());
            for(int i=1;i<=n;i+=2)a[i]=NTT::C[i/2];
            for(int i=0;i<d2.size();i++)NTT::A[i]=d2[i];
            for(int i=0;i<d2.size();i++)NTT::B[i]=c[i+1];
            NTT::solve(d2.size(),d2.size());
            for(int i=2;i<=n;i+=2)a[i]=NTT::C[i/2-1];
            d1.clear();
            d2.clear();
            calc(cnt[3]);
            for(int i=1;i<=n;i++)
            {
                if(i%3==1)d1.emplace_back(a[i]);
                else if(i%3==2)d2.emplace_back(a[i]);
                else d3.emplace_back(a[i]);
            }
            for(int i=0;i<d1.size();i++)NTT::A[i]=d1[i];
            for(int i=0;i<d1.size();i++)NTT::B[i]=c[i+1];
            NTT::solve(d1.size(),d1.size());
            for(int i=1;i<=n;i+=3)a[i]=NTT::C[i/3];
            for(int i=0;i<d2.size();i++)NTT::A[i]=d2[i];
            for(int i=0;i<d2.size();i++)NTT::B[i]=c[i+1];
            NTT::solve(d2.size(),d2.size());
            for(int i=2;i<=n;i+=3)a[i]=NTT::C[i/3];
            for(int i=0;i<d3.size();i++)NTT::A[i]=d3[i];
            for(int i=0;i<d3.size();i++)NTT::B[i]=c[i+1];
            NTT::solve(d3.size(),d3.size());
            for(int i=3;i<=n;i+=3)a[i]=NTT::C[i/3-1];
            ll ans=0;
            for(int i=1;i<=n;i++)ans=ans^(1ll*i*a[i]);
            cout<<ans<<endl;
        }
        return 0;
    }
    

     标程给了一个更好的思路,k=2时,我们把k=1的那种c数组变成c[1],0,c[2],0,c[3],0....

    k=3时,变成c[1],0,0,c[2],0,0,c[3],0,0...这种,然后直接对两个数组求卷积就可以了

    这是按照标程思路写的代码,精简了很多,常数也小了一些

    #include <bits/stdc++.h>
    using namespace std;
    #define maxn 300005
    #define MOD 998244353
    #define mod MOD
    #define G 3
    typedef long long ll;
    int rev[maxn];
    long long C[maxn];
    
    inline ll Pow(ll a, ll k) {
        ll base = 1;
        while (k) {
            if (k & 1) base = (base * a) % MOD;
            a = (a * a) % MOD;
            k >>= 1;
        }
        return base % MOD;
    }
    
    void NTT(long long *a, int len, int opt) {
        for (int i = 0; i < len; ++i) {
            if (i < rev[i]) {
                swap(a[i], a[rev[i]]);
            }
        }
        for (int i = 1; i < len; i <<= 1) {
            long long wn = Pow(G, (opt * ((MOD - 1) / (i << 1)) + MOD - 1) % (MOD - 1));
            int step = i << 1;
            for (int j = 0; j < len; j += step) {
                long long w = 1;
                for (int k = 0; k < i; ++k, w = (1ll * w * wn) % MOD) {
                    long long x = a[j + k];
                    long long y = 1ll * w * a[j + k + i] % MOD;
                    a[j + k] = (x + y) % MOD;
                    a[j + k + i] = (x - y + MOD) % MOD;
                }
            }
        }
        if (opt == -1) {
            long long r = Pow(len, MOD - 2);
            for (int i = 0; i < len; i++)
                a[i] = 1ll * a[i] * r % MOD;
        }
    }
    
    void solve(ll A[],ll B[],int n, int m) {
        int x, l = 0 ,len = 1;
        while (len <= n + m) len <<= 1, ++l;
        for (int i = 0; i < len; ++i)
            rev[i] = (rev[i >> 1] >> 1) | ((i & 1) << (l - 1));
        NTT(A, len, 1), NTT(B, len, 1);
        for (int i = 0; i < len; ++i) {
            C[i] = (ll) (A[i] * B[i]) % MOD;
            A[i] = B[i] = 0;
        }
        NTT(C, len, -1);
    }
    void read(ll &x) {
        static char ch;static bool neg;
        for(ch=neg=0;ch<'0' || '9'<ch;neg|=ch=='-',ch=getchar());
        for(x=0;'0'<=ch && ch<='9';(x*=10)+=ch-'0',ch=getchar());
        x=neg?-x:x;
    }
    int n,cnt[4];
    ll a[maxn],c[maxn];
    ll fac[1000005],inv[1000005];
    ll pow_mod(ll m,ll n)
    {
        ll res=1;
        while (n)
        {
            if(n&1)res=res*m%mod;
            m=m*m%mod;
            n>>=1;
        }
        return res;
    }
    void init()
    {
        inv[0]=fac[0]=1;
        for(int i=1;i<=1000000;i++)fac[i]=fac[i-1]*i%mod;
        inv[1000000]=pow_mod(fac[1000000],mod-2);
        for(int i=999999;i>=1;i--)inv[i]=inv[i+1]*(i+1)%mod;
    }
    ll Comb(int n,int m)
    {
        return n<m?0:fac[n]*inv[n-m]%mod*inv[m]%mod;
    }
    int main()
    {
        init();//组合数预处理
        int T;
        cin>>T;
        while (T--)
        {
            memset(cnt,0, sizeof(cnt));
            ll m,op;
            cin>>n>>m;
            for(int i=1;i<=n;i++)
            {
                read(a[i]);
            }
            for(int i=1;i<=m;i++)
            {
                read(op);
                ++cnt[op];
            }
            for(int i=1;i<=3;i++)
            {
                memset(c,0, sizeof(c));
                for(int j=0;j*i<n;j++)
                {
                    c[j*i]=Comb(cnt[i]-1+j,j);
                }
                if(cnt[i]==0)c[0]=1;//特殊处理
                solve(a+1,c,n,n);
                for(int i=0;i<n;i++)a[i+1]=C[i];
            }
            ll ans=0;
            for(int i=1;i<=n;i++)ans=ans^(1ll*i*a[i]);
            cout<<ans<<endl;
        }
        return 0;
    }

    总结:这个题总体来说还是不难的,虽然过程繁琐,比赛的时候用了FFT也没写出来,不过总的收获还是很大的,以前对于这种比赛时过的很少的题束手无策,现在也能自己分析个七七八八的,算是一种进步了吧

    多想想,不要轻易放弃,也许下一刻就能收获AC!

  • 相关阅读:
    .linearDrag on rigidbody / rigidbody2D in code?
    Unity5权威讲解+项目源码+MP4
    C#的扩展方法解说
    use crunch compression
    IIS服务命令
    使用批处理打开控制面板中的功能
    一次性在一个文件夹里建立多个文件夹
    bat 批处理切换到当前脚本所在文件夹
    %date~0,4%和 %time~0,2%等用法详解(转)
    DOS批处理高级教程(还不错)(转)
  • 原文地址:https://www.cnblogs.com/xusirui/p/11229450.html
Copyright © 2011-2022 走看看