【题目记录】——2021牛客暑期多校训练营1


题目集地址 2021牛客暑期多校训练营1

A 博弈论,SG函数优化

A题目地址

题目大意

Alice和Bob博弈,每次一个人从一堆中拿 k(k>0) 个,同时从另一堆拿 k * s(s >= 0) 个,Alice先手,问谁胜。

输入内容
第一行测试用例的个数t[1,10000],每个测试用例包含两个整数n,m[1,5000]表示两堆石头的数量。
5
2 3
3 5
5 7
7 5
7 7

Bob
Alice
Bob
Bob
Alice

思路

可以暴力sg:
设 sg[i][j] 表示第一堆是 i 且第二堆是 j 时的 sg函数,直接模拟所有取石子转移。注意有一维枚举的是倍数,所以总复杂度是 𝑂(𝑁^3 log𝑁),底下跑一段时间可以跑完 N=5000。

有一个结论:如果某堆石子数量是 i,另一堆石子最多只有一种数量满足后手胜。

可以找规律或者证明得到。

反证法:假设 (i, p) 和 (i, q) 都是后手必胜,且 q > p。那么在状态 (i, q) 时,先手可以在第二堆选 q-p 个,第一堆选 0 个,转移到后手胜的 (i, p),说明 (i, q) 是先手胜,矛盾。

知道这个结论后,我们可以直接记录所有后手胜的 pair,每次对于一个 i,根据之前的 pair 去推导是否有一个满足后手胜的搭配 j。复杂度是 𝑂(𝑁^2 log 𝑁),实际速度近似 𝑂(𝑁)。

当然也可以利用这个结论去很方便地打表。

我们知道当面临两堆石头数量为(0,0)时为失败,那么如果能一次操作能取光石头此时为必胜态,我们用一个二维数组f[i][j]来表示第一堆石头数量为i第二堆石头数量为j的情况,f[i][j]=1表示状态面临两堆石头数量为i,j时必定胜利,f[i][j]=0表示状态面临两堆石头数量为i,j时无法一次取光石头。初始f[0][0]=0,根据题目f[k][sk]或f[sk][k]一定能够一次性取完,我们可以从(i=0,j=0)开始自小变大枚举s与k找出所有先手必胜的状态,由于是自小变大枚举所以发现有f[i][j]=0就发现了一个必败的局面再对它枚举s与k,即f[i+k][j+k*s]=1,时间复杂度为O(n^4),但是这里大家需要知道一个结论:对于一个的i只存在至多一种j后手能够获胜。

#include<bits/stdc++.h>

using namespace std;
const int maxnum=5001;
bool sg[maxnum][maxnum];
int main()
{
    for(int i=0; i<maxnum; i++)
    {
        for(int j=0; j<maxnum; j++)
        {
            if(!sg[i][j])
            {
                for(int n=1; i+n<maxnum; n++)
                {
                    for(int m=0; j+n*m<maxnum; m++)
                    {
                        sg[i+n][j+n*m]=true;
                    }
                }
                for(int n=1; j+n<maxnum; n++)
                {
                    for(int m=0; i+n*m<maxnum; m++)
                    {
                        sg[i+m*n][j+n]=true;
                    }
                }
            }
        }
    }
    int t,n,m;
    cin>>t;
    while(t--)
    {
        cin>>n>>m;
        if(sg[n][m])
        {
            printf("Alice\n");
        }
        else
        {
            printf("Bob\n");
        }
    }
    return 0;
}

F 数位DP,暴力

F题目地址

题目大意

定义一个自然数是 3-friendly 的,如果它存在一个子串(允许前导0)是 3 的倍数。多组数据,求 L~R 中 3-friendly 的数的个数。

思路

限制比较少,数位DP的思路还是挺明显的。比如记录到某个位置 i 时,sum[j,i]%3 =0/1/2 的情况是否能满足,其中 j 是 i 之前的某个位置。

n>=100 时必然合法
从上述 DP 式子里稍作推理就会发现,根据鸽笼原理,只要位数不少于 3 位,必然出现一组前缀和 %3 相同的位置,所以他们这段区间必然 %3 = 0。
不允许前导 0 也能得出一样的结论。因为只要出现 0,单个的 0 就直接合法了。
这样我们只要对 <100 的 n 暴力即可。

代码

#include <iostream>
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;

int main()
{
    ll a[101]={0};
    ll b[101]={0};
    a[0]=1;
    a[3]=1;
    a[6]=1;
    a[9]=1;
    for(int i=10;i<=100;i++)
    {
        ll g=i%10;
        ll s=i/10;
        if(g%3==0||s%3==0||i%3==0)
            a[i]=1;
    }
    b[0]=1;
    for(int i=1;i<=100;i++)
    {
        b[i]=b[i-1]+a[i];
    }
    int T;
    cin>>T;
    ll l,r;
    while(T--)
    {
        cin>>l>>r;
        ll result=0;
        if(r<=100)
        {
            result=b[r]-b[l-1];
        }
        else if(l<=100)
        {
            result=r-100+b[100]-b[l-1];
        }
        else
        {
            result=r-l+1;
        }
        cout<<result<<endl;
    }
    return 0;
}

G 思维,模拟

G题目地址

题意

题目大意对于数组大小为n的两个数组a,b在a上操作k次交换使得 ∑ 1 n ∣ a i − b i ∣ \sum_{1}^{n}{|ai-bi|} 1naibi最大。

思路

如果我们交换了 a i 和 a j a_i和a_j aiaj的值,得到的 ∣ a i − b i ∣ + ∣ a j − b j ∣ |a_i-b_i|+|a_j-b_j| aibi+ajbj比之前的 ∣ a i − b i ∣ + ∣ a j − b j ∣ |a_i-b_i|+|a_j-b_j| aibi+ajbj更大那么这就是有效交换,每次交换带来的 ∣ a i − b i ∣ + ∣ a j − b j ∣ |a_i-b_i|+|a_j-b_j| aibi+ajbj的增长值,假设称之为收益,我们要在k次交换后使得 ∑ 1 n ∣ a i − b i ∣ \sum_{1}^{n}{|ai-bi|} 1naibi最大,就要保证每次交换带来的收益最大。
下面对交换的收益进行讨论:对于(a1,b1)(a2,b2)由于绝对值的存在无论a1,b1大小其值都等与大数减小数,理解到了这一点其实对a操作也可以视为对b操作,我们一定可以满足(a1>b1&&a2>b2)(不满足将其对应位置的ai,bi互换结果保持不变)
接下来只需要讨论b1与a2的大小即可确定四个数字的大小关系
b1>a2:
对于|a1-b1|+|a2-b2|=a1+a2-b1-b2
交换后:|a1-b2|+|a2-b1|=a1-b2+b1-a2=a1+b1-a2-b2
交换收益为:2 * b1 - 2 * a2=2*min(a1,b1)-2 * max(a2,b2)
b1<=a2:
对于|a1-b1|+|a2-b2|=a1+a2-b1-b2
交换后:|a1-b2|+|a2-b1|=a1-b2+a2-b1=a1+a2-b1-b2
结果之差为0(无意义交换)
由于题目要求是必须k次交换,假设s次交换我们已经满足求和最大(s<=k),剩下的次数做无意义的交换即可,如果(s>k)我们需要取前k次交换收益最大的值。
不难看出,交换带来的收益最大的情况是a[i]>b[i]的情况下,对a和b排序后,交换a中最大值和最小值,依次进行交换。

AC代码

#include<bits/stdc++.h>
using namespace std;

int a[500005],b[500005];
int main()
{
    int n,k;
    cin >> n >> k;
    long long ans = 0;
    for(int i = 0;i < n;i++)
    {
        scanf("%d",&a[i]);
    }
    for(int i = 0;i < n;i++)
    {
        scanf("%d",&b[i]);
        if(a[i] > b[i])
        {
            swap(a[i],b[i]);
        }
        ans+=b[i]-a[i];
    }
    sort(a,a + n);
    sort(b,b + n);
    for(int i = 0;i < k;i++)
    {
        int t;
        t = 2*(a[n - i - 1] - b[i]);

        if(t > 0)
        {
            ans += t;
        }
        else
            break;
    }
    cout << ans << endl;
    return 0;
}

H 数论,卷积,快速傅里叶变换

H题目地址

题意

题目可以转化为:找到最小的mod,使得 a i a_i ai处于mod的不同同余系中。

根据同余的性质有 a ≡ b m o d    p a \equiv b \mod p abmodp则有 ( a − b ) m o d    p ≡ 0 (a-b) \mod p \equiv 0 (ab)modp0
并且对于p的某一因数pp有 a ≡ b m o d    p p a \equiv b \mod pp abmodpp
根据上面两条,我们要求的就是:最小的、且不为所给的数中任意两个数的差的因数的数。
但是这个多项式加速就比较难想。我们可以这样总结:若需要对于两个数组中的元素、两两之间进行操作,朴素算法需要 O ( n 2 ) O(n^2) O(n2),可以考虑多项式优化为 n log ⁡ n n\log{n} nlogn
我们可以构造两个多项式:
一个多项式系数为表示数i是否出现在数组中。
另一个多项式系数表示数-i是否出现在数组中。但是,多项式的项数不能为负数呀?那么,考虑给它+5e5,使得意义为5e5-i这个数是否在数组中出现。
两个多项式用FFT/NTT相乘后,卷积的结果即为:第i项表示是否有i+5e5-j这个数存在于这个数组中任选两个数相减后的结果,因此可知,下标小于5e5的部分没有意义。若某一项系数,则说明i-5e5这个数可以差出来。

(对于NTT/FTT 和卷积相关内容还没学到,只能偷来答案了先)

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=5e5+100;
const int mx=5e5;
const int mod=998244353;
namespace IO{
    template<typename T>void write(T x)
    {
        if(x<0)
        {
            putchar('-');
            x=-x;
        }
        if(x>9)
        {
            write(x/10);
        }
        putchar(x%10+'0');
    }

    template<typename T> void read(T &x)
    {
        x = 0;char ch = getchar();int f = 1;
        while(!isdigit(ch)){if(ch == '-')f*=-1;ch=getchar();}
        while(isdigit(ch)){x = x*10+(ch-'0');ch=getchar();}x*=f;
    }
};
namespace Math{
    ll w;
    const int p=::mod;
    struct complex{
        ll real,imag;
        complex(ll a=0,ll b=0){
            real=a;
            imag=b;
        }
        friend complex operator*(const complex&a,const complex&b){
            complex ans;
            ans.real=((a.real*b.real%p+a.imag*b.imag%p*w%p)%p+p)%p;
            ans.imag=((a.real*b.imag%p+a.imag*b.real%p)%p+p)%p;
            return ans;
        }
    };
    ll x1,x2;
    ll ksm(ll a,ll b,ll p){
        ll ans=1;
        while(b){
            if(b&1) ans=(ans*a)%p;
            a=(a*a)%p;
            b>>=1;
        }
        return ans;
    }

    ll ksm(complex a,ll b,ll p){
        complex ans(1,0);
        while(b){
            if(b&1) ans=ans*a;
            a=a*a;
            b>>=1;
        }
        return ans.real%p;
    }

    bool Cipolla(ll n,ll&x0,ll&x1){
        n%=p;
        if(ksm(n,(p-1)>>1,p)==p-1) return false;
        ll a;
        while(true){
            a=rand()%p;
            w=((a*a%p-n)%p+p)%p;
            if(ksm(w,(p-1)>>1,p)==p-1) break;
        }
        complex x(a,1);
        x0=(ksm(x,(p+1)>>1,p)+p)%p;
        x1=(p-x0+p)%p;
        return true;
    }
};

namespace NTT{
    #define mul(x,y) ((1ll*x*y>=mod?x*y%mod:1ll*x*y))
    #define dec(x,y) (1ll*x-y<0?1ll*x-y+mod:1ll*x-y)
    #define add(x,y) (1ll*x+y>=mod?1ll*x+y-mod:1ll*x+y)
    #define ck(x) (x>=mod?x-mod:x)
    typedef vector<int> Poly;
    int ksm(int a,int n,int mod=::mod){
        int res=1;
        while(n){
            if(n&1)res=1ll*res*a%mod;
            a=1ll*a*a%mod;
            n>>=1;
        }
        return res;
    }
    const int img=86583718;
    const int g=3,INV=ksm(g,mod-2);
    const int mx=21;
    int R[maxn<<2],deer[2][mx][maxn<<2],inv[maxn<<2];
    void init(const int t) {
        for(int p = 1; p <= t; ++ p) {
            int buf1 = ksm(g, (mod - 1) / (1 << p));
            int buf0 = ksm(INV, (mod - 1) / (1 << p));
            deer[0][p][0] = deer[1][p][0] = 1;
            for(int i = 1; i < (1 << p); ++ i) {
                deer[0][p][i] = 1ll * deer[0][p][i - 1] * buf0 % mod;
                deer[1][p][i] = 1ll * deer[1][p][i - 1] * buf1 % mod;
            }
        }
        inv[1] = 1;
        for(int i = 2; i <= (1 << t); ++ i)
            inv[i] = 1ll * inv[mod % i] * (mod - mod / i) % mod;
    }

    int NTT_init(int n) {
        int lim = 1, l = 0;
        while(lim < n) lim <<= 1, l ++ ;
        for(int i = 0; i < lim; ++ i)
            R[i] = (R[i >> 1] >> 1) | ((i & 1) << (l - 1));
        return lim;
    }
    void ntt(Poly &A, int type, int lim) {
        A.resize(lim);
        for(int i = 0; i < lim; ++ i)
            if(i < R[i])
                swap(A[i], A[R[i]]);
        for(int mid = 2, j = 1; mid <= lim; mid <<= 1, ++ j) {
            int len = mid >> 1;
            for(int pos = 0; pos < lim; pos += mid) {
                int *wn = deer[type][j];
                for(int i = pos; i < pos + len; ++ i, ++ wn) {
                    int tmp = 1ll * (*wn) * A[i + len] % mod;
                    A[i + len] = ck(A[i] - tmp + mod);
                    A[i] = ck(A[i] + tmp);
                }
            }
        }
        if(type == 0) {
            for(int i = 0; i < lim; ++ i)
                A[i] = 1ll * A[i] * inv[lim] % mod;
        }
    }
    Poly poly_mul(Poly A, Poly B) {
        int deg = A.size() + B.size() - 1;
        int limit = NTT_init(deg);
        Poly C(limit);
        ntt(A, 1, limit);
        ntt(B, 1, limit);
        for(int i = 0; i < limit; ++ i)
            C[i] = 1ll * A[i] * B[i] % mod;
        ntt(C, 0, limit);
        C.resize(deg);
        return C;
    }
    Poly poly_inv(Poly &f, int deg) {
        if(deg == 1)
            return Poly(1, ksm(f[0], mod - 2));

        Poly A(f.begin(), f.begin() + deg);
        Poly B = poly_inv(f, (deg + 1) >> 1);
        int limit = NTT_init(deg << 1);
        ntt(A, 1, limit), ntt(B, 1, limit);
        for(int i = 0; i < limit; ++ i)
            A[i] = B[i] * (2 - 1ll * A[i] * B[i] % mod + mod) % mod;
        ntt(A, 0, limit);
        A.resize(deg);
        return A;
    }
    Poly poly_idev(Poly f) {
        int n = f.size();
        for(int i = n - 1; i-1>=0 ; -- i) f[i] = 1ll * f[i - 1] * inv[i] % mod;
        f[0] = 0;
        return f;
    }

    Poly poly_dev(Poly f) {
        int n = f.size();
        for(int i = 1; i < n; ++ i) f[i - 1] = 1ll * f[i] * i % mod;
        f.resize(n - 1);
        return f;
    }

    Poly poly_ln(Poly f, int deg) {
        Poly A = poly_idev(poly_mul(poly_dev(f), poly_inv(f, deg)));
        return A.resize(deg), A;
    }

    Poly poly_exp(Poly &f, int deg) {
        //cerr<<deg<<endl;
        if(deg == 1)
            return Poly(1, 1);

        Poly B = poly_exp(f, (deg + 1) >> 1);
        B.resize(deg);
        Poly lnB = poly_ln(B, deg);
        for(int i = 0; i < deg; ++ i)
            lnB[i] = ck(f[i] - lnB[i] + mod);

        int limit = NTT_init(deg << 1);
        ntt(B, 1, limit), ntt(lnB, 1, limit);
        for(int i = 0; i < limit; ++ i)
            B[i] = 1ll * B[i] * (1 + lnB[i]) % mod;
        ntt(B, 0, limit);
        B.resize(deg);
        return B;
    }
    Poly poly_pow(Poly&f,int k){
        f=poly_ln(f,f.size());
        for(auto&x:f)x=1ll*x*k%mod;
        return poly_exp(f,f.size());
    }
    Poly power(Poly f,int k1,int k2,int deg){
        int s=0;
        while(f[s]==0&&s<f.size())++s;
        if(1ll*s*k1>=deg){
            return vector<int>(deg);
        }
        int Inv=ksm(f[s],mod-2,mod);
        int Mul=ksm(f[s],k2);
        deg-=s;
        for(int i=0;i<deg;++i)f[i]=f[i+s];
        f.resize(deg);
        for(int i=0;i<deg;++i)f[i]=1ll*f[i]*Inv%mod;
        auto res1=poly_ln(f,deg);
        for(int i=0;i<res1.size();++i)res1[i]=1ll*res1[i]*k1%mod;
        auto res2=poly_exp(res1,deg);
        for(int i=0;i<deg;++i)res2[i]=1ll*res2[i]*Mul%mod;
        deg+=s;
        int now=s*k1;
        Poly res;res.resize(deg);
        for(int i=deg-1;i>=now;--i)res[i]=res2[i-now];
        for(int i=now-1;i>=0;--i)res[i]=0;
        return res;
    }
    Poly Poly_Sqrt(Poly&f,int deg){
        if(deg==1)return Poly(1,1);
        Poly A(f.begin(),f.begin()+deg);
        Poly B=Poly_Sqrt(f,(deg+1)>>1);
        Poly IB=poly_inv(B,deg);
        int lim=NTT_init(deg<<1);
        ntt(A,1,lim),ntt(IB,1,lim);
        for(int i=0;i<lim;++i){
            A[i]=1ll*A[i]*IB[i]%mod;
        }
        ntt(A,0,lim);
        for(int i=0;i<deg;++i){
            A[i]=(1ll*A[i]+B[i])%mod*inv[2]%mod;
        }
        A.resize(deg);
        return A;
    }
    Poly Sqrt(Poly&f,int deg){
        const int Pow=ksm(2,mod-2);
        int k1=1;
        if(f[0]!=1){
            k1=ksm(f[0],mod-2);
            for(int i=1;i<f.size();++i){
                f[i]=1ll*k1*f[i]%mod;
            }
            ll x0,x1;
            assert(Math::Cipolla(f[0],x0,x1));
            k1=min(x1,x0);
            f[0]=1;
        }
        auto Ln=poly_ln(f,deg);
        for(int i=0;i<f.size();++i){
            Ln[i]=1ll*Ln[i]*Pow%mod;
        }
        auto Exp=poly_exp(Ln,deg);
        for(int i=0;i<Exp.size();++i)Exp[i]=1ll*Exp[i]*k1%mod;
        return Exp;
    }
    Poly poly_sin(Poly&f,int deg){
        Poly A(f.begin(),f.begin()+deg);
        Poly B(deg),C(deg);
        for(int i=0;i<deg;++i){
            A[i]=1ll*A[i]*img%mod;
        }
        B=poly_exp(A,deg);
        C=poly_inv(B,deg);
        const int inv2i=ksm(img<<1,mod-2);
        for(int i=0;i<deg;++i){
            A[i]=1ll*(1ll*B[i]-C[i]+mod)%mod*inv2i%mod;
        }
        return A;
    }
    Poly poly_cos(Poly&f,int deg){
        Poly A(f.begin(),f.begin()+deg);
        Poly B(deg),C(deg);
        for(int i=0;i<deg;++i){
            A[i]=1ll*A[i]*img%mod;
        }
        B=poly_exp(A,deg);
        C=poly_inv(B,deg);
        const int inv2=ksm(2,mod-2);
        for(int i=0;i<deg;++i){
            A[i]=(1ll*B[i]+C[i])%mod*inv2%mod;
        }
        return A;
    }

    Poly poly_arcsin(Poly f,int deg){
        Poly A(f.size()),B(f.size()),C(f.size());
        A=poly_dev(f);
        B=poly_mul(f,f);
        for(int i=0;i<deg;++i){
            B[i]=dec(mod,B[i]);
        }
        B[0]=add(B[0],1);
        C=Poly_Sqrt(B,deg);
        C=poly_inv(C,deg);
        C=poly_mul(A,C);
        C=poly_idev(C);
        return C;
    }

    Poly poly_arctan(Poly f, int deg) {
        Poly A(f.size()), B(f.size()), C(f.size());
        A = poly_dev(f);
        B = poly_mul(f, f);
        B[0] = add(B[0], 1);
        C = poly_inv(B, deg);
        C = poly_mul(A, C);
        C = poly_idev(C);
        C.resize(deg);
        return C;
    }
};
using NTT::Poly;
using namespace IO;
int n,t;
Poly a,b;
bool vis[maxn];

signed main(){
    //freopen("in.txt","r",stdin);
    //clock_t c1 = clock();
    NTT::init(NTT::mx-1);
    //for(auto x:tmp)cerr<<x<<" ";cerr<<endl;
    a.resize(maxn),b.resize(maxn);
    int n;read(n);
    if(n==1){
        cout<<1<<endl;
        return 0;
    }
    for(int i=0;i<n;++i){
        int x;read(x);
        a[x]=1;
        b[mx-x]=1;
    }
    auto res=NTT::poly_mul(a,b);
    for(int i=mx;i<=2*mx;++i){
        if(res[i]){
            vis[i-mx]=1;
            //cerr<<i<<endl;
        }
    }
    bool flag=0;
    for(int i=1;i<=mx+1;++i){
        int now=0;
        for(int j=i;j<=mx;j+=i){
            now|=(vis[j]!=0);
            if(now)break;
        }
        if(!now){
            cout<<i<<endl;
            flag=1;
            break;
        }
    }
    assert(flag);
    //std::cerr << "Time:" << clock() - c1 << "ms" << std::endl;
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值