1. 程式人生 > 其它 >【考試總結】2022-03-03

【考試總結】2022-03-03

A.排隊 B.暱稱 C.帝國防衛

排隊

\(nm\)\(\rm DP\) 是顯然的,發現每 \(\bmod\) 次轉移係數是一致的,那麼可以合併計算

有了矩陣快速冪的雛形關鍵在於計算矩陣中每個位置的係數,要計算 \(x\) 行的係數可以把數組裡面序號為 \(x\) 的位置置 \(1\) 做一次 \(\rm Dp\) 來實現

複雜度 \(\Theta(m^3\bmod)\)

Code Display
int n,m,tot,dp[2010][20][2][2],id[11][2][2],vec[50],f[2][20][2][2];
struct Mat{
    int a[50][50]; Mat(){}
}mat;
int a[50][50],tmp[50];
signed main(){
    freopen("queue.in","r",stdin); freopen("queue.out","w",stdout);
    n=read(); m=read(); mod=read();
    dp[2][1][1][0]=dp[2][1][0][1]=1;
    for(int i=1;i<=m;++i){
        rep(j,0,1) rep(k,0,1) id[i][j][k]=++tot;
    }
    int now=2;
    while((n-now)%mod!=0){
        for(int j=1;j<=m;++j){
            rep(k,0,1) rep(l,0,1) if(dp[now][j][k][l]){
                ckadd(dp[now+1][j+(!k)][1][l],dp[now][j][k][l]);
                ckadd(dp[now+1][j+(!l)][k][1],dp[now][j][k][l]);
                int gap=j*2-k-l,rem=now-1-gap; gap%=mod;
                if(k) gap--,ckadd(dp[now+1][j][0][l],dp[now][j][k][l]);
                if(l) gap--,ckadd(dp[now+1][j][k][0],dp[now][j][k][l]);
                ckadd(dp[now+1][j][k][l],mul(dp[now][j][k][l],gap));
                ckadd(dp[now+1][j+1][k][l],mul(dp[now][j][k][l],rem%mod));
            }
        }
        ++now;
    }
    if(now==n){
        int ans=add(add(dp[n][m][0][0],dp[n][m][0][1]),add(dp[n][m][1][0],dp[n][m][1][1]));
        print(ans);        
        exit(0);
    }
    rep(i,1,m) rep(j,0,1) rep(k,0,1) vec[id[i][j][k]]=dp[now][i][j][k];
    int tim=(n-now)/mod;
    rep(x,1,m) rep(y,0,1) rep(z,0,1){
        int cur=0;
        f[cur][x][y][z]=1;
        rep(i,now,now+mod-1){
            for(int j=1;j<=m;++j){
                rep(k,0,1) rep(l,0,1) if(f[cur][j][k][l]){
                    ckadd(f[cur^1][j+(!k)][1][l],f[cur][j][k][l]);
                    ckadd(f[cur^1][j+(!l)][k][1],f[cur][j][k][l]);
                    int gap=j*2-k-l,rem=i-1-gap; gap%=mod;
                    if(k) gap--,ckadd(f[cur^1][j][0][l],f[cur][j][k][l]);
                    if(l) gap--,ckadd(f[cur^1][j][k][0],f[cur][j][k][l]);
                    ckadd(f[cur^1][j][k][l],mul(f[cur][j][k][l],gap));
                    ckadd(f[cur^1][j+1][k][l],mul(f[cur][j][k][l],rem%mod));
                    f[cur][j][k][l]=0;
                }
            } cur^=1;
        }
        rep(i,1,m) rep(j,0,1) rep(k,0,1) mat.a[id[x][y][z]][id[i][j][k]]=f[cur][i][j][k],f[cur][i][j][k]=0;
    }
    while(tim){
        if(tim&1){
            rep(i,1,tot) rep(j,1,tot) ckadd(tmp[j],mul(vec[i],mat.a[i][j]));
            rep(i,1,tot) vec[i]=tmp[i],tmp[i]=0;
        }
        rep(i,1,tot) rep(k,1,tot) rep(j,1,tot) ckadd(a[i][j],mul(mat.a[i][k],mat.a[k][j]));
        rep(i,1,tot) rep(j,1,tot) mat.a[i][j]=a[i][j],a[i][j]=0;
        tim>>=1;
    }
    int ans=add(vec[id[m][0][0]],add(vec[id[m][1][1]],add(vec[id[m][1][0]],vec[id[m][0][1]])));
    print(ans);
    return 0;
}

暱稱

使用 \(\rm AC\) 自動機實現一個計算不超過 \(\overline{a_1\dots a_n}\) 的數字中有多少個滿足包含給定的 \(S\)

所以我們先二分最後字串的長度,之後按位確定每個數位是幾

注意到一定有連續的一段是 \(S\),那麼對於每個自由位置先判定是不是必須要填 \(S\) 即可,判定次數是自由元個數的級別,也就是 \(\log 10^18\)

那麼複雜度也就降到了 \(\Theta(n^2\log n)\),常數還是蠻大的

Code Display
const int N=2010;
int n,fail[N],son[N][10],m;
char s[N];
int dp[2][N][2][2],up[N];
//dp[cur][match][bound][pas]
inline bool check(int len){
    memset(dp,0,sizeof(dp));
    int cur=0; dp[cur][0][1][0]=1;
    for(int i=0;i<len;++i){
        for(int j=0;j<=n&&j<=i;++j){
            rep(k,0,1) rep(l,0,1) if(dp[cur][j][k][l]){
                if(dp[cur][j][k][l]>=m&&l) return 1;
                if(!l&&len-i<n-j) continue;
                int Rbound=k?up[i+1]:9;
                for(int e=0;e<=Rbound;++e){
                    int nk=k&&(e==up[i+1]);
                    dp[cur^1][son[j][e]][nk][l|(son[j][e]==n)]+=dp[cur][j][k][l];
                }
                dp[cur][j][k][l]=0;
            }
        } cur^=1;
    }
    int ans=0;
    for(int i=0;i<=n;++i){
        if(ans+dp[cur][i][1][1]>=m) return 1;
        ans+=dp[cur][i][1][1];
        if(ans+dp[cur][i][0][1]>=m) return 1;
        ans+=dp[cur][i][0][1];  
    } return 0;
}
int ans[N];
signed main(){
    freopen("nickname.in","r",stdin); freopen("nickname.out","w",stdout);
    scanf("%s",s+1); n=strlen(s+1); m=read();
    son[0][s[1]-'0']=1;
    for(int i=0;i<n;++i) son[i][s[i+1]-'0']=i+1;
    for(int i=1;i<=n;++i){
        for(int j=0;j<=9;++j){
            if(j==s[i+1]-'0') fail[i+1]=son[fail[i]][j];
            else son[i][j]=son[fail[i]][j];
        }
    }
    int l=n,r=n+40,tar=r+1;
    while(l<=r){
        int mid=(l+r)>>1;
        rep(i,1,n+50) up[i]=0;
        rep(i,1,mid) up[i]=9;
        if(check(mid)) r=mid-1,tar=mid;
        else l=mid+1;
    }
    bool Push=0;
    memset(up,0,sizeof(up));
    rep(i,1,tar) ans[i]=9,up[i]=9;
    for(int i=1;i<=tar;++i){
        rep(j,1,tar) up[j]=ans[j];
        if(Push==0&&i+n-1<=tar){
            for(int j=0;j<n;++j) up[i+j]=s[j+1]-'0';
            if(check(tar)){
                int ee=i+n-1;
                while(up[ee]==0) up[ee]=9,--ee;
                up[ee]--;
                if(!check(tar)){
                    rep(j,0,n-1) ans[i+j]=s[j+1]-'0';
                    i+=n-1;
                    Push=1;
                    continue; 
                }
            }
        }
        rep(j,1,tar) up[j]=ans[j];
        l=0,r=8; ans[i]=9;
        while(l<=r){
            int mid=(l+r)>>1; up[i]=mid;
            if(check(tar)) ans[i]=mid,r=mid-1;
            else l=mid+1;
        }
    }
    for(int i=1;i<=tar;++i) putchar(ans[i]+'0'); putchar('\n');
    return 0;
}


帝國防衛

使用整體二分來計算每個點騎士數量不小於 \(c_i\) 的時間,考慮對於每個位置維護 \(Add_i\) 表示距離為 \(i\) 的點在這個點能得到的貢獻,同時維護 \(Del_i\) 表示從哪裡跳上來的要減掉

注意這裡貢獻不能寫作加和右移,因為這和右移之後再加和是完全不一樣的,這也就是為什麼每個深度要單獨維護陣列的原因了

每次修改跳 \(\log\) 個父親,每個父親對應修改其深度即可

剩下的都是整體二分和樹狀陣列 \(\rm dfs\) 序的基礎操作了,不再贅述

時間複雜度是 \(\Theta(n\log^3n)\) 可能通過樹上查分和虛樹將暴力跳父親的過程優化掉

Code Display
const int N=1e5+10;
int n;
struct BIT{
    int c[N];
    inline void insert(int x){for(;x<=n;x+=x&(-x)) c[x]++; return ;}
    inline int query(int x){int res=0; for(;x;x-=x&(-x)) res+=c[x]; return res;}
}T;
int c[N],fa[N],val[N],in[N],out[N],dfn;
vector<int> G[N],node[N];

inline void get_dfn(int x,int fat){
    in[x]=++dfn; fa[x]=fat;
    for(auto t:G[x]) if(t!=fat) get_dfn(t,x);
    out[x]=dfn;
}
int sum[N],Add[N][19],Del[N][19];
int x[N],delt[N],typ[N],tim[N];
inline int Query(int x){
    int res=sum[x],lst=x; x=fa[x];
    int up=1;
    while(up<=18&&x){
        res+=Add[x][up]-Del[lst][up];
        ++up; x=fa[lst=x];
    } return res;
}
inline void insert(int x,int val,int fl){
    int now=x,tmp=val;
    while(now&&tmp) sum[now]+=tmp*fl,tmp>>=1,now=fa[now];
    int lst=0;
    while(val&&x){
        for(int j=1;val>>j;++j) Add[x][j]+=fl*(val>>j),Del[lst][j]+=fl*(val>>j);
        x=fa[lst=x]; val>>=1;
    }
    return ;
}
inline void solve(int ql,int qr,vector<int> now){
    if(ql==qr){
        for(auto t:now) tim[t]=ql;
        return ;
    }
    int mid=(ql+qr)>>1;
    for(int i=ql;i<=mid;++i) if(typ[i]==1) insert(x[i],delt[i],1);
    vector<int> lef,rig;
    for(auto e:now) if(Query(e)>=c[e]) lef.push_back(e); else rig.push_back(e);
    solve(mid+1,qr,rig); 
    for(int i=ql;i<=mid;++i) if(typ[i]==1) insert(x[i],delt[i],-1);
    solve(ql,mid,lef);
    return ;
}
signed main(){
    freopen("empire.in","r",stdin); freopen("empire.out","w",stdout);
    n=read(); rep(i,1,n) c[i]=read();
    for(int i=1;i<n;++i){
        int u=read(),v=read();
        G[u].push_back(v); G[v].push_back(u);
    }
    get_dfn(1,0); 
    int Q=read();
    for(int i=1;i<=Q;++i){
        typ[i]=read();
        if(typ[i]==1) x[i]=read(),delt[i]=read(),insert(x[i],delt[i],1);
        else x[i]=read();
    }
    vector<int> now;
    for(int i=1;i<=n;++i) if(Query(i)>=c[i]) now.push_back(i); 
    for(int i=1;i<=Q;++i) if(typ[i]==1) insert(x[i],delt[i],-1);
    solve(1,Q,now);
    for(int i=1;i<=n;++i) if(tim[i]) node[tim[i]].push_back(i);
    for(int i=1;i<=Q;++i){
        if(typ[i]==2){
            print(T.query(out[x[i]])-T.query(in[x[i]]-1));
        }else{
            for(auto t:node[i]) T.insert(in[t]);
        }
    }
    return 0;
}

感覺這套題目許多同學現在考和聯賽前考得分並不會有什麼不一樣