[62] (NOIP 集訓) NOIP2024加賽 2

HaneDaniko發表於2024-11-06

lhx: 為啥你不會線性篩,這麼重要的東西
lhx: 你不篩積性函式嗎
我: 我一般做數學題推不到需要上線性篩的地方
lhx: 哦那你確實用不到

A.新的階乘

找質數 \(p\) 在式子中的冪,其實就是帶權找 \(p\)\([1,n]\) 所有數中的出現次數

發現 \([1,n]\) 中所有數只有形如 \(kp\) 的數能夠被找出因子 \(p\)

因此嘗試列舉所有 \(p\) 的倍數來更新 \(p\) 的答案

發現這玩意和埃氏篩天作之合,你列舉倍數的時候順便就把質數篩了,考慮直接上埃氏篩

因為不會怎麼求因子出現個數於是用了複雜度很假的暴力做法

理論 \(n\log\log n\log n\),實測 0.3s

#include<bits/stdc++.h>
using namespace std;
#define int long long
int n;
bool notprime[10000001];
vector<int>ans;
inline int powof(int i,int j){
    int res=0;
    while(j){
        if(j%i==0) j/=i,res++;
        else break;
    }
    return res;
}
signed main(){
    cin>>n;
    notprime[0]=true;
    notprime[1]=true;
    for(int i=2;i<=n;++i){
        if(notprime[i]==false){
            ans.push_back(i);
            int cnt=n-i+1;
            for(int j=2;j*i<=n;++j){
                notprime[i*j]=true;
                cnt+=(powof(i,j)+1)*(n-j*i+1);
            }
            ans.push_back(cnt);
        }
    }
    cout<<"f("<<n<<")=";
    if(n<=1) cout<<n;
    for(int i=0;i<=(int)ans.size()-1;i+=2){
        if(i!=0) cout<<'*';
        if(ans[i+1]==1){
            cout<<ans[i];
        }
        else{
            cout<<ans[i]<<'^'<<ans[i+1];
        }
    }
}

B.博弈樹

寫爆搜打表找規律發現:所有節點中,Bob 至多隻有一個

爆搜
#include<bits/stdc++.h>
using namespace std;
int n,q;
vector<int>e[1001];
int deep[1001],fa[10][1001],w[10][1001];
void dfs(int now,int last){
    deep[now]=deep[last]+1;
    fa[0][now]=last;
    for(int i:e[now]){
        if(i!=last){
            w[0][i]=1;
            dfs(i,now);
        }
    }
}
inline void prework(){
    for(int i=1;i<=9;++i){
        for(int j=1;j<=n;++j){
            fa[i][j]=fa[i-1][fa[i-1][j]];
            w[i][j]=w[i-1][j]+w[i-1][fa[i-1][j]];
        }
    }
}
inline int dis(int x,int y){
    if(deep[x]<deep[y]) swap(x,y);
    int res=0;
    for(int i=9;i>=0;--i){
        if(deep[fa[i][x]]>=deep[y]){
            res+=w[i][x];
            x=fa[i][x];
        }
    }
    if(x==y) return res;
    for(int i=9;i>=0;--i){
        if(fa[i][x]!=fa[i][y]){
            res+=w[i][x]+w[i][y];
            x=fa[i][x];
            y=fa[i][y];
        }
    }
    return res+w[0][x]+w[0][y];
}
//true = win
bool bydfs(int now,int lastdis){
    for(int i=1;i<=n;++i){
        if(i!=now){
            int tmp=dis(i,now);
            if(tmp>lastdis){
                if(bydfs(i,tmp)==false) return true;
            }
        }
    }
    return false;
}
int main(){
    cin>>n>>q;
    for(int i=1;i<=n-1;++i){
        int x,y;cin>>x>>y;
        e[x].push_back(y);
        e[y].push_back(x);
    }
    dfs(1,0);
    prework();
    while(q--){
        int t;cin>>t;
        cout<<(bydfs(t,0)?"Alice":"Bob")<<'\n';
    }
}

而且這個點離樹的中心極其近

額外發現當樹的重心有兩個的時候,沒有 Bob

嘗試直接特判樹的重心假完了

手摸了出錯的資料發現,應該是自己樹的重心這個思路不對,考慮到 Bob 和 Alice 的博弈過程,這裡應該是某種和樹上距離有關的 “重心”

於是想出了正確的結論

  • 定義重心為使得到所有點的距離的最大值最小的節點
  • 如果重心只有一個,Bob 會在此處獲勝,否則均為 Alice 獲勝

證明:

考慮如果先手在直徑的一個端點上,可以透過移動到另一個端點來直接勝出,否則不能移動到直徑的端點上,否則後手會移動到另一個端點而直接勝出

考慮刪除了原樹所有直徑的端點的樹,如果初始點在這棵樹上為直徑的某一個端點,那麼也一定是先手必勝的,因為先手可以將點移動到直徑另一個端點,這樣後手就一定會將點移動到原樹的直徑端點上,並且移動的長度一定小於原樹直徑,這樣先手就可以將點移動到原樹的另一個直徑端點取得勝利

因此後手勝利,當且僅當先手無論如何都會移動到某層的直徑端點上,這樣的情況只會在只存在一個點不在某層的直徑端點上的圖上,並且該點恰好為重心

但是這個東西卻是 \(n^2\) 的,過不了 \(10^5\)

但是你沒必要去列舉所有的點,因為重心一定在樹的直徑上,而且一定是直徑上靠近中點的位置,你直接去判斷靠近直徑中點那幾個點就行了

\(n=1\) 卡了,\(n=1\) 無論如何都是 Bob 贏

#include<bits/stdc++.h>
using namespace std;
int n,q;
vector<int>e[100001];
vector<int>zx;
int size[100001];
int maxsonsize[100001];
int deep[100001],fa[20][100001],w[20][100001];
void dfs(int now,int last){
    deep[now]=deep[last]+1;
    fa[0][now]=last;
    for(int i:e[now]){
        if(i!=last){
            w[0][i]=1;
            dfs(i,now);
        }
    }
}
inline void prework(){
    for(int i=1;i<=19;++i){
        for(int j=1;j<=n;++j){
            fa[i][j]=fa[i-1][fa[i-1][j]];
            w[i][j]=w[i-1][j]+w[i-1][fa[i-1][j]];
        }
    }
}
inline int dis(int x,int y){
    if(deep[x]<deep[y]) swap(x,y);
    int res=0;
    for(int i=19;i>=0;--i){
        if(deep[fa[i][x]]>=deep[y]){
            res+=w[i][x];
            x=fa[i][x];
        }
    }
    if(x==y) return res;
    for(int i=19;i>=0;--i){
        if(fa[i][x]!=fa[i][y]){
            res+=w[i][x]+w[i][y];
            x=fa[i][x];
            y=fa[i][y];
        }
    }
    return res+w[0][x]+w[0][y];
}
int minsize=0x7fffffff;
void dfs2(int i){
    int maxdis=0;
    for(int j=1;j<=n;++j){
        maxdis=max(maxdis,dis(i,j));
    }
    if(maxdis<minsize){
        minsize=maxdis;
        zx={i};
    }
    else if(maxdis==minsize){
        zx.push_back(i);
    }
}
int dis5[100001];
int dis6[100001];
int maxdis5,maxdis5id;
int maxdis6,maxdis6id;
void dfs5(int now,int last){
    for(int i:e[now]){
        if(i!=last){
            dis5[i]=dis5[now]+1;
            if(dis5[i]>maxdis5){
                maxdis5=dis5[i];
                maxdis5id=i;
            }
            dfs5(i,now);
        }
    }
}
void dfs6(int now,int last){
    for(int i:e[now]){
        if(i!=last){
            dis6[i]=dis6[now]+1;
            if(dis6[i]>maxdis6){
                maxdis6=dis6[i];
                maxdis6id=i;
            }
            dfs6(i,now);
        }
    }
}
vector<int>zj;
bool dfs7(int now,int last,int tar){
    if(now==tar){
        zj.push_back(now);
        return true;
    }
    for(int i:e[now]){
        if(i!=last){
            if(dfs7(i,now,tar)){
                zj.push_back(now);
                return true;
            }
        }
    }
    return false;
}
void dfs8(){
    int tmp=(int)zj.size()/2;
    int tmp2=tmp-1;
    if(tmp>=0) for(int i:e[zj[tmp]]) dfs2(i);
    if(tmp2>=0) for(int i:e[zj[tmp2]]) dfs2(i);
}
int main(){
    freopen("tree.in","r",stdin);
    freopen("tree.out","w",stdout);
    cin>>n>>q;
    if(n==1){
        while(q--){
            cout<<"Bob\n";
        }
        return 0;
    }
    for(int i=1;i<=n-1;++i){
        int x,y;
        cin>>x>>y;
        e[x].push_back(y);
        e[y].push_back(x);
    }
    dfs(1,0);
    prework();
    dfs5(1,0);
    dfs6(maxdis5id,0);
    dfs7(maxdis5id,0,maxdis6id);
    dfs8();
    while(q--){
        int t;cin>>t;
        if(zx.size()!=1ull) cout<<"Alice\n";
        else if(zx[0]!=t) cout<<"Alice\n";
        else cout<<"Bob\n";
    }
}

C.劃分

先說 corner case

  • \(n=k\)

顯然了,只有唯一的劃分方式

  • 字串最前面有超過 \(k\) 位全是 \(0\)

這個時候直接在前面劃就行了,無論你怎麼拆後面帶 \(1\) 的字串都只會讓答案更小

設前面有 \(p\)\(0\),劃分成 \(k\) 段,需要插 \(k-1\) 塊板,注意最後一個 \(0\) 後面也是能插的,答案即為 \(\sum^p_{i=k-1}C_{p}^{i}\),預處理階乘直接算即可

注意這個 case 有一個全 \(0\) 的特殊情況,在第三個 hack

  • 通解

上面兩個 corner case 都要判掉再做通解

最優解一定形如選出 \(k-1\) 個單個的區間,剩下一個連續的大區間單獨分一段(區間越長高位貢獻越大)

這樣剩餘的情況就只有 \(k\) 種了,可以暴力判斷哪一種情況更優

因為 \(1\) 的個數一樣,實際上誰的高位 \(1\) 個數更多,誰的答案就更大

因此只比較那個長區間,直接暴力比較是 \(O(k)\) 的,為了規避暴力比較,可以用二分+雜湊求出需要比較的兩個區間的最長公共字首,然後直接比較後一位,可以做到 \(O(\log k)\)

這裡注意最長公共字首等於原串的情況,有可能會越界

還是那句話,誰的高位 \(1\) 個數更多,誰的答案就更大,因此我們判方案數的時候也是同理,因為最低位的 \(1\) 和單獨的 \(1\) 貢獻是一樣的,因此兩個串答案相同,當且僅當最長的區間去除最低位後相等,依舊用雜湊判斷

注意卡模數的問題

#include<bits/stdc++.h>
using namespace std;
#define int long long
const int p=998244353;
const int P1=1e9+3,P2=1e7+0721;
int n,k;
string x;
int power(int a,int t){
    int base=a,ans=1;
    while(t){
        if(t&1){
            ans=ans*base%p;
        }
        base=base*base%p;
        t>>=1;
    }
    return ans;
}
int fact[2000001];
int C(int n,int m){
    return fact[n]*power(fact[m]*fact[n-m]%p,p-2)%p;
}
int cal(int l,int r){
    int res=0;
    for(int i=l;i<=r;++i){
        res=(res*2+x[i]-'0')%p;
    }
    return res;
}
const unsigned long long num=233,num2=2333;
unsigned long long h[2000001],basenum[2000001];
unsigned long long geth(int l,int r){
    if(l>r) return 0;
    return (h[r]-h[l-1]*basenum[r-l+1]%P1+P1)%P1;
}
unsigned long long h2[2000001],basenum2[2000001];
unsigned long long geth2(int l,int r){
    if(l>r) return 0;
    return (h2[r]-h2[l-1]*basenum2[r-l+1]%P2+P2)%P2;
}
bool great(int now,int ori){
    int l=0,r=n-k+1,ans=0;
    while(l<=r){
        int mid=(l+r)/2;
        if(geth(now,now+mid-1)==geth(ori,ori+mid-1) and geth2(now,now+mid-1)==geth2(ori,ori+mid-1)){
            l=mid+1;
            ans=mid;
        }
        else r=mid-1;
    }
    if(ans==n-k+1) return false;
    return x[now+ans]>x[ori+ans];
}
inline int cal2(int l,int r){
    int res=0;
    for(int i=1;i<l;++i){
        res=(res+x[i]-'0')%p;
    }
    int res2=0;
    for(int i=l;i<=r;++i){
        res2=(res2*2+x[i]-'0')%p;
    }
    res=(res+res2)%p;
    for(int i=r+1;i<=n;++i){
        res=(res+x[i]-'0')%p;
    }
    return res;
}
signed main(){
    freopen("divide.in","r",stdin);
    freopen("divide.out","w",stdout);
    cin>>n>>k>>x;x=" "+x;
    if(n==k){
        int res=0;
        for(int i=1;i<=n;++i){
            res=(res+x[i]-'0')%p;
        }
        cout<<res<<" "<<1;
        return 0;
    }
    fact[0]=1;
    basenum[0]=1;
    basenum2[0]=1;
    for(int i=1;i<=n;++i){
        fact[i]=fact[i-1]*i%p;
        h[i]=(h[i-1]*num+x[i])%P1;
        basenum[i]=basenum[i-1]*num%P1;
        h2[i]=(h2[i-1]*num2+x[i])%P2;
        basenum2[i]=basenum2[i-1]*num2%P2;
    }
    int first1=n;
    for(int i=1;i<=n;++i){
        if(x[i]=='1'){
            first1=i;
            break;
        }
    }
    if(first1>=k){
        cout<<cal(1,n)<<' ';
        int res=0;
        for(int i=k-1;i<=first1-1;++i){
            res=(res+C(first1-1,i))%p;
        }
        cout<<res;
        return 0;
    }
    int maxid=0;
    for(int i=1;i<=k;++i){
        if(maxid==0) maxid=i;
        else{
            if(great(i,maxid)){
                maxid=i;
            }
        }
    }
    cout<<cal2(maxid,maxid+n-k)<<" ";
    int res=0;
    for(int i=1;i<=k;++i){
        if(geth(i,i+n-k-1)==geth(maxid,maxid+n-k-1) and geth2(i,i+n-k-1)==geth2(maxid,maxid+n-k-1)) res++;
    }
    cout<<res;
}

相關文章