2023NOIP A层联测18-划分

news/2024/7/5 3:26:30

对于一个长度为 n n n01 字符串 S S S,请你求出将其分为至少 k k k 段,将每一段看为二进制数求和后的最大值以及取到这个最大值的划分方案的数量。

n ≤ 2 × 1 0 6 n\le2\times10^6 n2×106


字符串编号从 1 1 1 开始。

n = k n=k n=k,最大值很好求,方案数就是 1 1 1

若前 k k k 个都没有 1 1 1,设第一个 1 1 1 出现的位置为 m m m,最大值是选 m m m 开始的后缀,划分方案是在前 m m m 个空位插大于等于 k − 1 k-1 k1 个隔板,用组合数可以轻松求出。

否则,最大值一定是选长度为 n − k + 1 n-k+1 nk+1 的子串 a a a,剩下每个数单独为一个段。

现在考虑怎样选这样的子串使答案最大。显然选二进制最大的。

证明:设二进制最大的子串为 a a a,另一个不是最大的子串为 b b b,设 1 1 1 的总数为 x x x。由于 1 1 1 的总数始终不变,即证明: x − popcount(a) ⁡ + a ≥ x − popcount ⁡ ( b ) + b x-\operatorname{popcount(a)}+a\ge x-\operatorname{popcount}(b)+b xpopcount(a)+axpopcount(b)+b。化简得 a − popcount(a) ⁡ ≥ b − popcount ⁡ ( b ) a-\operatorname{popcount(a)}\ge b-\operatorname{popcount}(b) apopcount(a)bpopcount(b)

f ( n ) = n − popcount ⁡ ( n ) f(n)=n-\operatorname{popcount}(n) f(n)=npopcount(n),即证它是不减函数。对其差分得 popcount ⁡ ( n ) − popcount ⁡ ( n + 1 ) + 1 \operatorname{popcount}(n)-\operatorname{popcount}(n+1)+1 popcount(n)popcount(n+1)+1,显然若 n + 1 n+1 n+1 popcount ⁡ ( n ) \operatorname{popcount}(n) popcount(n) 要么增加 1 1 1(此时 n n n 为偶数时取等),要么减少,得证。

由上面结论和取等条件,我们要做的是:先找最大的子串,然后统计前 n − k n-k nk 个相同的子串个数,即为方案数。

问题转换为求长度为 n − k + 1 n-k+1 nk+1 的字典序最大的子串。

这个问题可以用二分+哈希解决。

我们有 k k k 个子串待比较,令第一个子串是当前的最大子串,后面更新。

对于当前最大子串 s s s 和左端点为 i i i 的子串 t t t,若 t t t 可以更新 s s s,即 t > s t>s t>s,则 s s s t t t 会有一段公共前缀(可能没有),接下来一个数字 t t t 1 1 1 s s s 0 0 0,我们想要快速求出公共前缀的长度,这里就二分长度 m i d mid mid,如果二者长度为 m i d mid mid 的子串不相等,就把 m i d mid mid 变小,否则变大;快速判断子串是否相等可以用哈希。

时间复杂度为 O ( k log ⁡ ( n − k ) ) O(k\log(n-k)) O(klog(nk))

具体实现看代码

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll mod=998244353,mod1=1e9+7;
const int N=2e6+1;
int n,k,sum[N];
char a[N];
ll f[N],inv[N],pw1[N],pw2[N],a1[N],a2[N];
ll ksm(ll a,ll b)
{
    ll ans=1;
    while(b){
        if(b&1) ans=ans*a%mod;
        b>>=1;
        a=a*a%mod;
    }
    return ans;
}
struct node
{
    int fl,son[2];
}tr[2000001];
pair<ll,ll> geth(int l,int r)
{
    return make_pair((a1[r]-a1[l-1]*pw1[r-l+1]%mod+mod)%mod,(a2[r]-a2[l-1]*pw2[r-l+1]%mod1+mod1)%mod1);
}
int main()
{
    freopen("divide.in","r",stdin);
    freopen("divide.out","w",stdout);
    scanf("%d%d%s",&n,&k,a+1);
    pw1[0]=pw2[0]=1;
    for(int i=1;i<=n;i++) pw1[i]=pw1[i-1]*2%mod,pw2[i]=pw2[i-1]*2%mod1;
    for(int i=1;i<=n;i++) a1[i]=(a1[i-1]*2+a[i]-48)%mod,a2[i]=(a2[i-1]*2+a[i]-48)%mod1,sum[i]=sum[i-1]+a[i]-48;
    int fl=0;
    for(int i=1;i<=k;i++) if(a[i]==49){fl=1;break;}
    if(n==k){
        int x=0;
        for(int i=1;i<=n;i++) x+=a[i]-48;
        printf("%d 1",x);
    }
    else if(!fl){
        int m=0;
        while(m<n&&a[m+1]=='0') m++;
        if(m==n) m--;
        f[0]=1;
        for(int i=1;i<=n;i++) f[i]=f[i-1]*i%mod;
        inv[n]=ksm(f[n],mod-2);
        for(int i=n-1;i>=0;i--) inv[i]=inv[i+1]*(i+1)%mod;
        ll x=0,ans=0;
        for(int i=m+1;i<=n;i++) x=(x*2+a[i]-48)%mod;
        for(int i=k-1;i<=m;i++) ans=(ans+f[m]*inv[i]%mod*inv[m-i])%mod;
        printf("%lld %lld",x,ans);
    }
    else{
        int maxi=1,ans=0;
        for(int i=2;i<=k;i++){
            int l=0,r=n-k+1,ans=l;
            while(l<=r){
                int mid=l+r>>1;
                if(geth(i,i+mid-1)!=geth(maxi,maxi+mid-1)) r=mid-1;
                else l=mid+1,ans=mid;
            }
            if(a[maxi+ans]<a[i+ans]) maxi=i;
        }
        for(int i=1;i<=k;i++) if(geth(i,i+n-k-1)==geth(maxi,maxi+n-k-1)) ans++;
        printf("%lld %d",(geth(maxi,maxi+n-k).first+sum[maxi-1]+sum[n]-sum[maxi+n-k])%mod,ans);
    }
}

http://lihuaxi.xjx100.cn/news/1721050.html

相关文章

Java基于ssm+vue开发的失物招领小程序

演示视频&#xff1a; 小程序 https://www.bilibili.com/video/BV1sg4y1d75T/?share_sourcecopy_web&vd_source11344bb73ef9b33550b8202d07ae139b 管理员 https://www.bilibili.com/video/BV1UH4y167xe/?share_sourcecopy_web&vd_source11344bb73ef9b33550b8202d07a…

解决vi编辑器方向按键和删除按键无法使用问题

1.解决上下左右键出现ABCD现象 首先执行 echo "set nocp" >> ~/.vimrc 再执行下面代码即可 source ~/.vimrc 2.解决删除键无法使用现象 首先执行 echo "set backspace2" >> ~/.vimrc 再执行下面代码即可 source ~/.vimrc

2011-2021年北大数字普惠金融指数数据(包括省市县)第四期

2011-2021年北大省市县数字普惠金融指数数据&#xff08;第四期&#xff09; 1、时间&#xff1a;2011-2021年 2、指标&#xff1a;index_aggregate、coverage_breadth、usage_depth、payment、insurance、monetary_fund、investment、credit、credit_investigation、digitiz…

RK3399平台开发系列讲解(基础篇)嵌入式编码规范有哪些

🚀返回专栏总目录 文章目录 一、什么是GNU二、GNU C 编码规范2.1、格式2.2、注释2.3、语法约定2.4、命名2.5、系统可移植性2.6、CPU 可移植性2.7、系统函数2.8、国际化2.9、字符集沉淀、分享、成长,让自己和他人都能有所收获!😄 📢 GNU 编码规范的出发点,是确保所有 G…

Zoho Mail荣登福布斯2023年企业邮箱榜单,引领行业新方向!

几十年来&#xff0c;电子邮件一直是电子通信的重要形式&#xff0c;并且在未来的许多年里&#xff0c;它可能会无处不在。尽管有大量免费电子邮件服务可供用户和企业使用&#xff0c;但其中许多服务缺乏专门的功能&#xff0c;例如适合办公室使用的集中管理。 福布斯小型企业顾…

Codeforces Round 734

Portal. B1. Wonderful Coloring - 1 Portal. 记 c n t i cnt_i cnti​ 代表第 i i i 种字母在 s s s 中的出现次数&#xff0c;有两种情况&#xff1a; 若 c n t i > 1 cnt_i>1 cnti​>1&#xff0c;此时对于这种字母&#xff0c;我们将它出现的某次染成红色&…

2022年12月 Python(一级)真题解析#中国电子学会#全国青少年软件编程等级考试

Python等级考试(1~6级)全部真题・点这里 一、单选题(共25题,每题2分,共50分) 第1题 关于Python语言的注释,以下选项中描述错误的是?( ) A: Python语言有两种注释方式:单行注释和多行注释 B: Python语言的单行注释以#开头 C: Python多行注释使用###来做为标记 D: …

postgresSQL 数据库本地创建表空间读取本地备份SQL文件

使用pgAdmin4&#xff0c;你安装PG得文件夹****/16/paAdmin 4 /runtime/pgAdmin4.exe 第一步&#xff1a;找到Tablespaces 第二步&#xff1a;创建表空间名称 第三步&#xff1a;指向数据文件 第四步&#xff1a;找到Databases&#xff0c;创建表空间 第五步&#xff1a;输入数…