NOIP2024加赛2

NOIP2024加赛2

题目来源: 2023NOIP A层联测18

\(T1\) HZTG5733. 新的阶乘 \(100pts\)

  • 预处理素数后直接对 \(1 \sim n\) 进行质因数分解因为有太多冗余枚举导致无法通过。

  • 考虑枚举最终形式。具体地,从质因数的角度入手,设当前枚举的质数为 \(p\) ,暴力求出 \(ip\)\(p\) 的指数即可。

  • 时间复杂度为 \(1 \sim n\) 中每个数质因数分解后的各指数之和 \(=O(n \log n)\) ,在 \(1s\) 内仅能处理 \(3 \times 10^{7}\) 以内的数据。

    • 时间复杂度在于暴力求 \(ip\)\(p\) 的指数。
    点击查看代码
    ll prime[700010],c[700010],len=0;
    bool vis[10000010];
    void isprime(ll n)
    {
        memset(vis,0,sizeof(vis));
        for(ll i=2;i<=n;i++)
        {
            if(vis[i]==0)
            {
                len++;
                prime[len]=i;
            }
            for(ll j=1;j<=len&&i*prime[j]<=n;j++)
            {
                vis[i*prime[j]]=1;
                if(i%prime[j]==0)
                {
                    break;
                }
            }
        }
    }
    ll ask(ll n,ll p)
    {
        ll ans=0;
        while(n%p==0)
        {
            ans++;
            n/=p;
        }
        return ans;
    }
    int main()
    {
        freopen("factorial.in","r",stdin);
        freopen("factorial.out","w",stdout);
        ll n,cnt,mul,i,j;
        scanf("%lld",&n);
        isprime(n);
        for(i=1;i<=len;i++)
        {
            for(j=1;prime[i]*j<=n;j++)
            {
                c[i]+=(ask(j,prime[i])+1)*(n-prime[i]*j+1);
            }
        }
        printf("f(%lld)=",n);
        for(i=1;i<=len;i++)
        {
            printf("%lld",prime[i]);
            if(c[i]!=1)
            {
                printf("^%lld",c[i]);
            }
            if(i!=len)
            {
                printf("*");
            }
        }
        fclose(stdin);
        fclose(stdout);
        return 0;
    }	
    
  • 官方题解中省去了暴力求指数的一步。

\(T2\) HZTG5734. 博弈树 \(80pts\)

  • 部分分

    • \(80pts\) :暴力建出博弈的搜索树,因为状态数规模为 \(O(n^{2})\) ,加个记忆化即可,可能需要 \(O(1)\) 的求 \(\operatorname{LCA}\) (?)。
    点击查看代码
    struct node
    {
        int nxt,to;
    }e[200010];
    int head[100010],dfn[100010],dep[100010],cnt=0,tot=0;
    unordered_map<int,bool>f[100010];
    void add(int u,int v)
    {
        cnt++;
        e[cnt].nxt=head[u];
        e[cnt].to=v;
        head[u]=cnt;
    }
    int sx_min(int x,int y)
    {
        return dfn[x]<dfn[y]?x:y;
    }
    struct ST
    {
        int fminn[100010][20];
        void init(int n)
        {
            for(int j=1;j<=log2(n);j++)
            {
                for(int i=1;i+(1<<j)-1<=n;i++)
                {
                    fminn[i][j]=sx_min(fminn[i][j-1],fminn[i+(1<<(j-1))][j-1]);
                }
            }
        }
        int query(int l,int r)
        {
            int t=log2(r-l+1);
            return sx_min(fminn[l][t],fminn[r-(1<<t)+1][t]);
        }
    }T;
    void get_dep(int x,int fa)
    {
        tot++;
        dfn[x]=tot;
        dep[x]=dep[fa]+1;
        T.fminn[tot][0]=fa;
        for(int i=head[x];i!=0;i=e[i].nxt)
        {
            if(e[i].to!=fa)
            {
                get_dep(e[i].to,x);
            }
        }
    }
    int lca(int u,int v)
    {
        if(dfn[u]>dfn[v])
        {
            swap(u,v);
        }
        return (dfn[u]==dfn[v])?u:T.query(dfn[u]+1,dfn[v]);
    }
    int get_dis(int u,int v)
    {
        return dep[u]+dep[v]-2*dep[lca(u,v)];
    }
    bool dfs(int x,int last,int n)
    {
        if(f[x].find(last)!=f[x].end())
        {
            return f[x][last];
        }
        for(int i=1;i<=n;i++)
        {
            int d=get_dis(x,i);
            if(d>last&&dfs(i,d,n)==false)
            {
                return f[x][last]=true;
            }
        }
        return f[x][last]=false;
    }
    int main()
    {
        freopen("tree.in","r",stdin);
        freopen("tree.out","w",stdout);
        int n,m,u,v,i;
        cin>>n>>m;
        for(i=1;i<=n-1;i++)
        {
            cin>>u>>v;
            add(u,v);
            add(v,u);
        }
        get_dep(1,0);
        T.init(n);
        for(i=1;i<=m;i++)
        {
            cin>>u;
            if(dfs(u,0,n)==true)
            {
                cout<<"Alice"<<endl;
            }
            else
            {
                cout<<"Bob"<<endl;
            }
        }
        fclose(stdin);
        fclose(stdout);
        return 0;
    }	
    
  • 正解

    • 若节点 \(x\) 是直径的端点,那么直接判定 Alice 必胜。否则谁先移动到直径的端点谁就输。
    • 考虑删去所有直径的端点(一定是叶子节点),若节点 \(x\) 是删完后的树的直径端点,那么 Alice 也是必胜的。因为 Alice 可以将点移动到删完后的树的另一个直径端点,而 Bob 就只能移到原树的直径端点上,从而得到 Alice 必胜。
    • 考虑模拟删叶子的过程,若删到最后只剩一个点说明在这个点时 Bob 是必胜的,否则 Alice 一定可以通过不断将点移动到下一层的直径端点取得胜利(对于 Bob 的干扰, Alice 可以通过利用直径的最长性进行反制)。
    • 而最后剩下的这个点一定是直径的中点(直径长度为奇数时),预处理即可。
    点击查看代码
    struct node
    {
        int nxt,to;
    }e[200010];
    int head[200010],dep[200010],fa[200010],cnt=0;
    vector<int>d;
    void add(int u,int v)
    {
        cnt++;
        e[cnt].nxt=head[u];
        e[cnt].to=v;
        head[u]=cnt;
    }
    void dfs(int x,int father)
    {
        fa[x]=father;
        dep[x]=dep[father]+1;
        for(int i=head[x];i!=0;i=e[i].nxt)
        {
            if(e[i].to!=father)
            {
                dfs(e[i].to,x);
            }
        }
    }
    int main()
    {
        freopen("tree.in","r",stdin);
        freopen("tree.out","w",stdout);
        int n,m,u,v,rt1=0,rt2=0,i;
        cin>>n>>m;
        for(i=1;i<=n-1;i++)
        {
            cin>>u>>v;
            add(u,v);
            add(v,u);
        }
        dfs(1,0);
        for(i=1;i<=n;i++)
        {
            rt1=(dep[i]>dep[rt1])?i:rt1;
        }
        dfs(rt1,0);
        for(i=1;i<=n;i++)
        {
            rt2=(dep[i]>dep[rt2])?i:rt2;
        }
        while(rt2!=0)
        {
            d.push_back(rt2);
            rt2=fa[rt2];
        }
        for(i=1;i<=m;i++)
        {
            cin>>u;
            if(d.size()%2==0||d[d.size()/2]!=u)
            {
                cout<<"Alice"<<endl;
            }
            else
            {
                cout<<"Bob"<<endl;
            }
        }
        fclose(stdin);
        fclose(stdout);
        return 0;
    }	
    

\(T3\) HZTG5735. 划分 \(9pts\)

  • 部分分

    • 测试点 \(1 \sim 2\) :爆搜。
    • 测试点 \(9 \sim 12\)
      • 设第一个 \(1\) 出现的位置为 \(pos\) 。那么最大值一定为 \(s_{pos \sim n}\) ,因为 \([pos,n]\) 中任意断开就会导致最大值变小。方案数仅会由前面的 \(0\) 决定,即 \(\sum\limits_{i=k}^{pos}\dbinom{pos-1}{i-1}\)
      • 若不存在 \(1\) ,则最大值为 \(0\) ,方案数为 \(\sum\limits_{i=k}^{n}\dbinom{n-1}{i-1}\)
    点击查看代码
    const ll p=998244353;
    ll inv[2000010],jc[2000010],jc_inv[2000010],ans=0,cnt=0;
    char s[2000010];
    ll ask(ll l,ll r)
    {
        ll ans=0;
        for(ll i=l;i<=r;i++)
        {
            ans=(ans*2+s[i]-'0');
        }
        return ans;
    }
    ll work(ll l,ll r)
    {
        ll ans=0;
        for(ll i=l;i<=r;i++)
        {
            ans=(ans*2%p+s[i]-'0')%p;
        }
        return ans;
    }
    void dfs(ll pos,ll n,ll k,ll sum,ll num,ll last)
    {
        if(pos==n)
        {
            num++;
            sum+=ask(last+1,n);
            if(num>=k)
            {
                if(sum>ans)
                {
                    ans=sum;
                    cnt=1;
                }
                else
                {
                    if(sum==ans)
                    {	
                        cnt++;
                    }
                }
            }
        }
        else
        {
            dfs(pos+1,n,k,sum,num,last);
            dfs(pos+1,n,k,sum+ask(last+1,pos),num+1,pos);
        }
    }
    ll C(ll n,ll m,ll p)
    {
        return (n>=m&&n>=0&&m>=0)?(jc[n]*jc_inv[n-m]%p)*jc_inv[m]%p:0;
    }
    int main()
    {
        freopen("divide.in","r",stdin);
        freopen("divide.out","w",stdout);
        ll n,k,pos=0,sum=0,i;
        cin>>n>>k;
        for(i=1;i<=n;i++)
        {
            cin>>s[i];
            pos=(pos==0&&s[i]=='1')?i:pos;
        }
        if(pos>k||pos==0)
        {	
            inv[1]=1;
            jc[0]=jc_inv[0]=jc[1]=jc_inv[1]=1;
            for(i=2;i<=n;i++)
            {
                inv[i]=(p-p/i)*inv[p%i]%p;
                jc[i]=jc[i-1]*i%p;
                jc_inv[i]=jc_inv[i-1]*inv[i]%p;
            }
            if(pos==0)
            {
                for(i=k;i<=n;i++)
                {
                    sum=(sum+C(n-1,i-1,p))%p;
                }
                cout<<0<<" "<<sum<<endl;
            }
            else
            {
                for(i=k;i<=pos;i++)
                {
                    sum=(sum+C(pos-1,i-1,p))%p;
                }
                cout<<work(pos,n)<<" "<<sum<<endl;
            }
        }
        else
        {
            dfs(1,n,k,0,0,0);
            cout<<ans%p<<" "<<cnt%p<<endl;
        }
        fclose(stdin);
        fclose(stdout);
        return 0;
    }	
    
  • 正解

    • 考虑不保证 \(\forall i \in [1,k],s_{i}=0\) 时怎么做。
    • 最优解的划分方案一定形如选出一段 \(n-k+1\) 单独作为一段,剩下的每个数单独作为一段。
    • 因为 \(1\) 的数量是一定的,所以所有的构造方案对应的 \(n-k+1\) 这段数的前 \(n-k\) 位一定相同。
    • 考虑二分哈希找到最大的划分点(取长度为 \(n-k+1\) 的段),然后哈希判断有多少个其他划分点的前 \(n-k\) 位与其相等,每一个划分点都对应一种构造方式。
    • 需要特判 \(n=k\) 时方案数为 \(1\)
    点击查看代码
    const ll p=998244353,mod=1000000007,base=13331;
    ll inv[2000010],C_jc[2000010],jc_inv[2000010],jc[2000010],a[2000010];
    char s[2000010];
    ll C(ll n,ll m,ll p)
    {
        return (n>=m&&n>=0&&m>=0)?(C_jc[n]*jc_inv[n-m]%p)*jc_inv[m]%p:0;
    }
    void sx_hash(char s[],ll len)
    {
        for(ll i=0;i<=len;i++)
        {
            a[i]=(i==0)?0:(a[i-1]*base%mod+s[i])%mod;
        }
    }
    ll ask_hash(ll l,ll r)
    {
        return (a[r]-a[l-1]*jc[r-l+1]%mod+mod)%mod;
    }
    bool cmp(ll l1,ll r1,ll l2,ll r2)
    {
        if(l1==r1)
        {
            return s[l1]>s[l2];
        }
        ll mid1=(l1+r1)/2,mid2=(l2+r2)/2;
        if(ask_hash(l1,mid1)==ask_hash(l2,mid2))
        {
            return cmp(mid1+1,r1,mid2+1,r2);
        }
        else
        {
            return cmp(l1,mid1,l2,mid2);
        }
    }
    int main()
    {
        freopen("divide.in","r",stdin);
        freopen("divide.out","w",stdout);
        ll n,k,len,pos=0,ans=0,sum=0,i;
        cin>>n>>k;
        jc[0]=1;
        for(i=1;i<=n;i++)
        {
            cin>>s[i];
            pos=(pos==0&&s[i]=='1')?i:pos;
            jc[i]=jc[i-1]*base%mod;
        }
        if(pos>k||pos==0)
        {
            inv[1]=1;
            C_jc[0]=jc_inv[0]=C_jc[1]=jc_inv[1]=1;
            for(i=2;i<=n;i++)
            {
                inv[i]=(p-p/i)*inv[p%i]%p;
                C_jc[i]=C_jc[i-1]*i%p;
                jc_inv[i]=jc_inv[i-1]*inv[i]%p;
            }
            if(pos==0)
            {
                for(i=k;i<=n;i++)
                {
                    sum=(sum+C(n-1,i-1,p))%p;
                }
            }
            else
            {
                for(i=k;i<=pos;i++)
                {
                    sum=(sum+C(pos-1,i-1,p))%p;
                }
                for(i=pos;i<=n;i++)
                {
                    ans=(ans*2%p+s[i]-'0')%p;
                }
            }
        }
        else
        {
            sx_hash(s,n);
            len=n-k+1;
            pos=1;
            for(i=2;i+len-1<=n;i++)
            {
                if(cmp(i,i+len-1,pos,pos+len-1)==true)
                {
                    pos=i;
                }
            }
            for(i=pos;i<=pos+len-1;i++)
            {
                ans=(ans*2%p+s[i]-'0')%p;
            }
            for(i=1;i<=pos-1;i++)
            {
                ans=(ans+s[i]-'0')%p;
            }
            for(i=pos+len;i<=n;i++)
            {
                ans=(ans+s[i]-'0')%p;
            }
            len=n-k;
            if(n==k)
            {
                sum=1;
            }
            else
            {
                for(i=1;i+len-1<=n;i++)
                {
                    sum+=(ask_hash(pos,pos+len-1)==ask_hash(i,i+len-1));
                }
            }
        }
        cout<<ans<<" "<<sum<<endl;
        fclose(stdin);
        fclose(stdout);
        return 0;
    }	
    

\(T4\) HZTG5736. 灯笼 \(0pts\)

总结

  • \(T2\) 第一遍读题时以为是比上一局对方走的距离更长,写完爆搜后才发现自己读假题了。
  • \(T3\) 没有想 \(\forall i \in [1,k],s_{i}=0\)\(15pts\) 部分分。
posted @ 2024-11-06 16:30  hzoi_Shadow  阅读(45)  评论(0编辑  收藏  举报
扩大
缩小