2021牛客暑期多校训练营1

这篇博客介绍了几道算法竞赛题目,包括Alice and Bob的游戏,利用SG打表和递推优化求解;Ball Dropping的几何问题;Determinethe Photo Position的贪心策略;Find3-friendly Integers的数位DP解决方法;以及Journey among Railway Stations的线段树应用。此外,还提到了概率DP在Increasing Subsequence问题中的应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

2021牛客暑期多校训练营1

比赛链接

Alice and Bob


SG打表:

通过SG打表发现他大部分情况都是先手必胜态。

递推转移:

所以我们可以通过递推的方式,将先手必败态转移必胜,之后先手必胜被标记过后将不再转移。即可将时间复杂优化为 O ( n 2 ∗ l o g n ) O(n^2*logn) O(n2logn)

或者直接打表 (雾

代码:

#include <iostream>
#include <cstring>
#include <unordered_set>
using namespace std;
const int N=5e3+10;
int t,n,m;
int sg[N][N];
void SG(){
    for(int i=0;i<=5000;i++){
        for(int j=i;j<=5000;j++){
            if(!sg[i][j]){
                for(int s=1;i+s<=5000;s++){
                    for(int k=0;j+k*s<=5000;k++){
                        int a=i+s,b=j+k*s;
                        if(a>b) swap(a,b);
                        sg[a][b]=1;
                    }
                }
                for(int s=1;j+s<=5000;s++){
                    for(int k=0;i+k*s<=5000;k++){
                        int a=j+s,b=i+k*s;
                        if(a>b) swap(a,b);
                        sg[a][b]=1;
                    }
                }
            }
        }
    }
}
int main(){
    SG();
    scanf("%d",&t);
    while(t--){
        scanf("%d %d",&n,&m);
        if(n>m) swap(n,m);
        if(sg[n][m]) cout<<"Alice\n";
        else cout<<"Bob\n";
    }
}


Ball Dropping


几何题,队友写的,^^_


Determine the Photo Position


签到题,模拟即可。


Find 3-friendly Integers


数位DP

这题上来一看就感觉是数位DP,首先发现能被3整除的,其数位和也能被3整除,记录一个数位的前缀和,在维护前缀和膜3为0,1,2的个数,即可判断数字的子串能否被3整除。

#include <iostream>
#include <cstring>
using namespace std;
typedef long long ll;
int t,tot,a[20];
ll l,r,dp[20][3][3][3][3][2][2];
ll dfs(int p,int cnt0,int cnt1,int cnt2,int s,int pre,int lim){
    if(p==0){
        if(cnt0==2||cnt1==2||cnt2==2) return 1;
        return 0;
    }
    if(dp[p][cnt0][cnt1][cnt2][s][pre][lim]!=-1) return dp[p][cnt0][cnt1][cnt2][s][pre][lim];
    int up=lim? a[p]:9;
    ll ans=0;
    for(int i=0;i<=up;i++){
         if(pre&&i==0){
            ans+=dfs(p-1,cnt0,cnt1,cnt2,(s+i)%3,pre&&i==0,lim&&i==up);
         }else{
            if(cnt0<2&&(s+i)%3==0){
                ans+=dfs(p-1,cnt0+1,cnt1,cnt2,(s+i)%3,pre&&i==0,lim&&i==up);
            }else if(cnt1<2&&(s+i)%3==1){
                ans+=dfs(p-1,cnt0,cnt1+1,cnt2,(s+i)%3,pre&&i==0,lim&&i==up);
            }else if(cnt2<2&&(s+i)%3==2){
                ans+=dfs(p-1,cnt0,cnt1,cnt2+1,(s+i)%3,pre&&i==0,lim&&i==up);
            }else{
                ans+=dfs(p-1,cnt0,cnt1,cnt2,(s+i)%3,pre&&i==0,lim&&i==up);
            }
         }
    }
    return dp[p][cnt0][cnt1][cnt2][s][pre][lim]=ans;
}
ll solve(ll x){
    int tot=0;
    memset(dp,-1,sizeof dp);
    while(x){
        a[++tot]=x%10;
        x/=10;
    }
    return dfs(tot,1,0,0,0,1,1);
}
int main(){
    scanf("%d",&t);
    while(t--){
        scanf("%lld %lld",&l,&r);
        printf("%lld\n",solve(r)-solve(l-1));
    }
}


Determine the Photo Position

贪心

神仙贪心题,严格证明看官方题解。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DFfqo509-1629730995483)(C:\Users\5\AppData\Roaming\Typora\typora-user-images\image-20210721195857947.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0owNqeTD-1629730995486)(C:\Users\5\AppData\Roaming\Typora\typora-user-images\image-20210721195909797.png)]

代码:

#include <iostream>
#include <cmath>
#include <algorithm>
using namespace std;
typedef long long ll;
const int N=5e5+7;

int n,k;
ll a[N],b[N],mx[N],mn[N];
int main(){
    scanf("%d %d",&n,&k);
    for(int i=1;i<=n;i++){
        scanf("%lld",&a[i]);
    }
    for(int i=1;i<=n;i++){
        scanf("%lld",&b[i]);
    }
    if(n==2){
        k=k&1;
        if(k) swap(a[1],a[2]);
        printf("%lld\n",abs(a[1]-b[1])+abs(a[2]-b[2]));
        return 0;
    }
    ll ans=0;
    for(int i=1;i<=n;i++){
        ans+=abs(a[i]-b[i]);
        mx[i]=max(a[i],b[i]);
        mn[i]=min(a[i],b[i]);
    }
    sort(mx+1,mx+1+n);
    sort(mn+1,mn+1+n);reverse(mn+1,mn+1+n);
    //for(int i=1;i<=n;i++) cout<<mn[i]<<" "<<mx[i]<<endl;
    for(int i=1;i<=min(n,k);i++){
        if(mn[i]>mx[i])
         ans+=mn[i]-mx[i] <<1ll;
    }
    printf("%lld\n",ans);
}


Hash Function


比赛时被我们水过去了,正解貌似是FFT或NTT,等我补坑^^_


Increasing Subsequence


概率DP

这题一看就是概率DP

状态划分

d p [ i ] [ j ] [ k ] dp[i][j][k] dp[i][j][k]​,当前Alice和Bob上次所选位置为 i i i j j j,且是k=[0/1]为当前是Alice或Bob。

计算期望

d p [ i ] [ j ] [ 0 ] = ∑ d p [ i ] [ k ] [ 1 ] ∗ p + 1 dp[i][j][0]=\sum dp[i][k][1]*p+1 dp[i][j][0]=dp[i][k][1]p+1​, a [ k ] > a [ i ] , k > j a[k]>a[i],k>j a[k]>a[i]k>j​​​​

d p [ i ] [ j ] [ 1 ] = ∑ d p [ k ] [ j ] [ 0 ] ∗ p + 1 dp[i][j][1]=\sum dp[k][j][0]*p+1 dp[i][j][1]=dp[k][j][0]p+1​​​, a [ k ] > a [ j ] , k > i a[k]>a[j],k>i a[k]>a[j],k>i​​​​

其中 p p p​为事件发生的概率。即后面能选的数量。

之后递归来实现即可,但是这是这还远远不够,因为此时的复杂度,我们还需要对其优化。

递推优化

我们能将 d f s dfs dfs​​里面要乘上的概率放到 d f s dfs dfs​外面,然后在里面每次只 + 1 +1 +1的转移,即可将 d f s dfs dfs里面的 f o r for for循环优化掉。

优化前代码:

#include <iostream>
using namespace std;
typedef long long ll;
const int N=5007;
const ll mod=998244353;
int n,a[N];
ll inv[N],c[N][N];
ll dp[N][N][2];
ll ksm(ll x,ll p){
    ll res=1;
    while(p){
        if(p&1) res=res*x%mod;
        p>>=1;
        x=x*x%mod;
    }
    return res;
}
ll dfs(int p1,int p2,int s){
    if(dp[p1][p2][s]) return dp[p1][p2][s];
    ll ans=0;
    if(s==0){
        int cnt=c[p2+1][a[p1]];
        for(int i=p2+1;i<=n;i++){
            if(a[i]>a[p1])
                ans=(ans+(dfs(p1,i,1)+1)*inv[cnt]%mod)%mod;
        }
    }else{
        int cnt=c[p1+1][a[p2]];
        for(int i=p1+1;i<=n;i++){
            if(a[i]>a[p2])
                ans=(ans+(dfs(i,p2,0)+1)*inv[cnt])%mod;
        }
    }
    return dp[p1][p2][s]=ans;
}
int main(){
    scanf("%d",&n);
    for(int i=1;i<=n;i++){
        scanf("%d",&a[i]);
    }
    for(int i=1;i<=n;i++){
        inv[i]=ksm(i,mod-2);
    }
    for(int i=n;i>=1;i--){
        for(int j=1;j<=n;j++){
            c[i][j]=c[i+1][j];
            if(j<a[i]) c[i][j]++;
        }
    }
    ll res=0;
    for(int i=1;i<=n;i++){
        res=(res+(dfs(i,0,0)+1)*inv[n]%mod)%mod;
    }
    printf("%lld\n",res);
}

优化后代码

#include <iostream>
#include <cstring>
using namespace std;
typedef long long ll;
const int N=5002;
const ll mod=998244353;
int n,a[N],c[N][N];
int inv[N];
int dp[N][N][2];
ll ksm(ll x,ll p){
    ll res=1;
    while(p){
        if(p&1) res=res*x%mod;
        p>>=1;
        x=x*x%mod;
    }
    return res;
}
ll qu(ll x){
    ll cnt=x/mod;
    x-=cnt*mod;
    return x;
}
ll dfs(int p1,int p2,int s){
    if(p1>n||p2>n) return 0;
    if(~dp[p1][p2][s]) return dp[p1][p2][s];
    ll ans=0;
    if(s==0){
        int cnt=c[p1+1][a[p2]];
        if(a[p2]>a[p1]){
            ans=qu(ans+qu(qu(dfs(p1+1,p2,1)*inv[cnt])+1ll));
        }
        ans=qu(ans+dfs(p1,p2+1,0));
    }else{
        
        int cnt=c[p2+1][a[p1]];
        if(a[p1]>a[p2]){
            ans=qu(ans+qu(qu(dfs(p1,p2+1,0)*inv[cnt])+1ll));
        }
        ans=qu(ans+dfs(p1+1,p2,1));
    }
    return dp[p1][p2][s]=ans;
}
int main(){
    scanf("%d",&n);
    memset(dp,-1,sizeof dp);
    for(int i=1;i<=n;i++){
        scanf("%d",&a[i]);
    }
    for(int i=1;i<=n;i++){
        inv[i]=ksm(i,mod-2);
    }
    for(int i=n;i>=1;i--){
        for(int j=1;j<=n;j++){
            c[i][j]=c[i+1][j];
            if(j<a[i]) c[i][j]++;
        }
    }
    ll res=0;
    for(int i=1;i<=n;i++){
        int cnt=c[1][a[i]];
        res=qu(res+qu((qu(dfs(i,1,0)*inv[cnt])+1ll)*inv[n]));
    }
    printf("%lld\n",res);
}


Journey among Railway Stations

思路:

维护到该点到下个节点的最早时间 b t bt bt,该点最晚时间 e d ed ed,到下个点的花费 c c c,能否到该点 o k ok ok​用线段树来维护各个值即可

代码

#include <iostream>
#define lch (k<<1)
#define rch (k<<1|1)
#define mid (l+r>>1)
using namespace std;
typedef long long ll;
const int N=1e6+7;
int T,n,m;
struct node{ll bt,ed,c,ok;}tr[4*N];
int a[N],b[N],c[N];

node merge(node x,node y){
    node z;
    z.ok=x.ok&y.ok;
    if(x.bt>y.ed) z.ok=0;
    z.c=x.c+y.c;
    z.bt=max(x.bt+y.c,y.bt);
    z.ed=min(x.ed,y.ed-x.c);
    return z;
}
void pushup(int k){
    tr[k]=merge(tr[lch],tr[rch]);
}
void init(int k,int l,int r){
    if(l==r){
        tr[k].bt=(ll)a[l]+c[l];
        tr[k].ed=b[l];
        tr[k].c=c[l];
        tr[k].ok=1;
        return;
    }
    init(lch,l,mid);
    init(rch,mid+1,r);
    pushup(k);
}
void update(int k,int l,int r,int p,node v){
    if(l==r){
        tr[k]=v;
        return;
    }
    if(p<=mid) update(lch,l,mid,p,v);
    else update(rch,mid+1,r,p,v);
    pushup(k);
}
node query(int k,int l,int r,int ql,int qr){
    if(ql<=l&&r<=qr){
        return tr[k];
    }
    if(qr<=mid) return query(lch,l,mid,ql,qr);
    else if(ql>mid) return query(rch,mid+1,r,ql,qr);
    else return merge(query(lch,l,mid,ql,qr),query(rch,mid+1,r,ql,qr));
}

void solve(){
    scanf("%d",&n);
    for(int i=1;i<=n;i++) scanf("%d",&a[i]);
    for(int i=1;i<=n;i++) scanf("%d",&b[i]);
    for(int i=1;i<n;i++)  scanf("%d",&c[i]);
    init(1,1,n);
    scanf("%d",&m);
    while(m--){
        int op;
        scanf("%d",&op);
        if(op==0){
            //int l r;
            int l,r;
            scanf("%d %d",&l,&r);
            int ok=query(1,1,n,l,r).ok;
            if(ok) puts("Yes");
            else puts("No");
        }else if(op==1){
            int x,w;
            scanf("%d %d",&x,&w);
            node t=query(1,1,n,x,x);
            c[x]=w;
            t.bt=(ll)a[x]+w;
            t.c=w;
            update(1,1,n,x,t);
        }else{
            int x,p,q;
            scanf("%d %d %d",&x,&p,&q);
            node t=query(1,1,n,x,x);
            a[x]=p,b[x]=q;
            t.bt=(ll)p+t.c,t.ed=q;
            update(1,1,n,x,t);
        }
    }
}
int main(){
    scanf("%d",&T);
    while(T--) solve();
}


Knowledge Test about Match


待补^^_


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值