[62] (NOIP 集训) NOIP2024加赛 2

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\) 个数更多,谁的答案就更大

发现有人不理解这句话:实际上,因为你 \(1\) 的个数是固定的,而每个 \(1\) 都会给答案带来一个 \(2^k\) 的贡献(\(k\)\(1\) 所在的位置),因此你肯定是要最大化每个 \(k\) 的值,也就是高位 \(1\) 个数尽可能多/高,而我们说最低位和单独的 \(1\) 没用,是因为无论怎么分,每个 \(1\) 总是会有至少 \(2^0\) 的贡献,相当于最低位和单独的 \(1\) 没做出任何额外贡献,在这一点上二者是等价的

因此只比较那个长区间,直接暴力比较是 \(O(len)\) 的,为了规避暴力比较,可以用二分+哈希求出需要比较的两个区间的最长公共前缀,然后直接比较后一位,可以做到 \(O(\log len)\)

这里注意最长公共前缀等于原串的情况,有可能会越界

还是那句话,谁的高位 \(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(){
    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;
}

D.灯笼


这是什么

posted @ 2024-11-06 16:13  HaneDaniko  阅读(48)  评论(0编辑  收藏  举报