9.4 上午 becoder 模擬賽總結&題解

tkdqmx發表於2024-09-04

T1 東方記者

簡單 DP,設 \(dp_{i,j}\) 表示最後走到了第 \(i\) 個事件的發生地點,且一共收集了 \(j\) 處資料的最小移動距離。

根據定義,可以知道對於所有 \(dp_{i,j}\),如果走回原點後,依然滿足移動距離小於 \(d\),則有:\(ans=max\{j\}\)

狀態轉移也很簡單:\(dp_{i,j}=min_{k=0}^{k<i}\{dp_{k,j-1}+\lvert x_i-x_k\rvert+\lvert y_i-y_k\rvert\}\),初值 \(dp_{0,0}=x_0=y_0=0\)

不作過多贅述,程式碼如下(100pts):

#define N 105
long long n,d,ans,x[N],y[N],dp[N][N];
int main(){
    memset(dp,0x3f,sizeof dp),dp[0][0]=0,scanf("%lld",&n);
    for(int i=1;i<=n;i++)  dp[i][0]=0,scanf("%lld%lld",x+i,y+i);
    scanf("%lld",&d);
    for(int i=1;i<=n;i++){
        for(int j=0;j<i;j++)
            for(int k=1;k<=j+1;k++)
                dp[i][k]=min(dp[i][k],dp[j][k-1]+abs(x[i]-x[j])+abs(y[i]-y[j]));
        for(int j=ans;j<=i;j++)  if(dp[i][j]+abs(x[i])+abs(y[i])<=d)  ans=j;
    }
    printf("%lld\n",ans);
}

T2 金鑰破解

先說 30pts 的做法:

先透過試除法給 \(N\) 分解質因數求出 \(p\)\(q\),然後就能求出 \(r\)

\(d\) 就是 \(e\) 在模 \(r\) 意義下的逆元,用 exgcd 求出就行了,最後 \(n\) 快速冪計算 \(c^d \mod N\) 的值就可以了。

100pts 的做法只是在此基礎上把分解質因數的方法換成了 Pollard Rho,並且因為 \(N\) 比較大,需要龜速乘或者 __int128 進行計算。

程式碼如下,思路其實很簡單,主要難度在 Pollard Rho(100pts):

#define LL long long
LL e,n,c,r;
LL quick_mul(LL x,LL y,LL mod)//x和y的積對mod取模
LL quick_pow(LL x,LL y)//x的y次方對n取模
void exgcd(LL a,LL b,LL &x,LL &y){
    if(!b)  return x=1,y=0,void();
    exgcd(b,a%b,y,x),y=(y-quick_mul(a/b,x,r)+r)%r;
}
mt19937 mrand(time(NULL));
LL gcd(LL x,LL y){return !y?x:gcd(y,x%y);}
LL pollard_rho(LL c){
    LL i=0,k=2,x=mrand()*mrand()%(n-1)+1,y=x,d;
    while(1){
        x=(quick_mul(x,x,n)+c)%n,d=gcd((x-y+n)%n,n);
        if(d!=1&&d!=n)  return d;
        if(x==y)  return n;
        if(++i==k)  k<<=1,y=x;
    }
}
int main(){
    scanf("%lld%lld%lld",&e,&n,&c);
    LL p=n,d,tmp;
    while(p==n)  p=pollard_rho(mrand()*1ll*mrand()%(n-1)+1);
    r=(p-1)*(n/p-1),exgcd(e,r,d,tmp);
    return printf("%lld %lld\n",d,quick_pow(c,d)),0;
}

T3 琪露諾數

經典數位 DP,但是是高精度版本。

對於數位 DP 列舉到的這一位,如果是迴文串的前半段,就正常列舉就行。

如果是後半段就不用列舉了,直接判斷對應位的取值在這一位能否取到,能就繼續遞迴,不能就直接返回 0,遞迴到末尾時返回 1。

有個小技巧就是在後半段時如果 \(limit\) 已經為 0,也就是可以任意取值的時候,可以直接返回 1。

至於如何判斷是在前後哪一個半段,記搜的時候加一個值 \(st\) 表示是從哪一位開始不是前導 0 的,也就是記錄數的位數。

本題的主要難點不在於數位 DP,而是高精度的加減乘除,程式碼放下面,高精度部分不做贅述(100pts):

#include<bits/stdc++.h>
using namespace std;
#define LL long long
LL t,num[205],s[205];
string l,r,dp[205][205];
string add(string a,string b)//高精度十進位制整數a+b
string sub(string a,string b)//高精度十進位制整數a-b
string mul(string a,string b)//高精度十進位制整數a*b
string div(string a,string b,string res="")//高精度十進位制整數a/b(向下取整)
string dfs(int pos,int st,bool limit){
    if(!pos)  return "1";
    if(st!=-1&&!limit&&dp[pos][st]!="")  return dp[pos][st];
    if(st-pos>=pos){
        if(!limit)  return "1";
        if(num[pos]<s[st-pos+1])  return "0";
        return dfs(pos-1,st,s[st-pos+1]==num[pos]);
    }
    string res="0";
    for(int i=0;i<=(limit?num[pos]:8);i++){
        s[pos]=i;
        if(!i&&st==-1)  res=add(res,dfs(pos-1,st,limit&&i==num[pos]));
        else if(st==-1)  res=add(res,dfs(pos-1,pos,limit&&i==num[pos]));
        else  res=add(res,dfs(pos-1,st,limit&&i==num[pos]));
    }
    if(st!=-1&&!limit) dp[pos][st]=res;
    return res;
}
string solve(string x,int pos=0){
    if(x=="0")  return "1";
    while(x!="0"){
        string tmp=div(x,"9");
        num[++pos]=sub(x,mul(tmp,"9"))[0]-'0',x=tmp;
    }
    return dfs(pos,-1,1);
}
int main(){
    cin>>t;
    while(t--){
        cin>>l>>r;
        cout<<sub(solve(r),solve(sub(l,"1")))<<"\n";
    }
}