某 CSPS 模拟赛 - 20191112【主席树 组合数学】

T1 命令方块(block)

题意

N N N 个字符串 s i s_i si,按给定顺序排开,你每次可以交换一对字符串,最后使得:

  • 对于任意任意 i < j < k i<j<k i<j<k,有: lcp ⁡ ( s i , s j ) ≥ lcp ⁡ ( s i , s k ) \operatorname{lcp}(s_i,s_j)\geq \operatorname{lcp}(s_i,s_k) lcp(si,sj)lcp(si,sk) lcp ⁡ ( s j , s k ) ≥ lcp ⁡ ( s i , s k ) \operatorname{lcp}(s_j,s_k)\geq \operatorname{lcp}(s_i,s_k) lcp(sj,sk)lcp(si,sk)

输出任意一种交换方案,要求交换次数 ≤ 1 0 6 \leq 10^6 106 N ≤ 1 0 6 , ∑ ∣ s i ∣ ≤ 1 0 7 N\leq 10^6, \sum |s_i|\leq 10^7 N106,si107

题解

(刚看到题目名称还有 SPJ 还以为第一道题就是造计算机题……)

首先把要求翻译成人话——对于任意一个字符串,向同一个方向看,离得越近的最长公共前缀越长。

最长公共前缀越长就相当于字典序更接近,所以按字典序 sort 一遍就行。

(实际上按字典树的任意 DFS 序排序都是可行的)

得到了排序后的序列,只需要对于每个没有归位的字符串 swap 一遍,就能得到方案。

代码

#include<bits/stdc++.h>
using namespace std;
int getint(){
    int ans=0,f=1;
    char c=getchar();
    while(c<'0'||c>'9'){
        if(c=='-')f=-1;
        c=getchar();
    }
    while(c>='0'&&c<='9'){
        ans=ans*10+c-'0';
        c=getchar();
    }
    return ans*f;
}

char c[10001000];
pair<string,int> s[1000100];
int pos[1000100],a[1000100];
pair<int,int>ans[1000100];
int cnt=0;

int main(){
    int n=getint();
    for(int i=0;i<n;i++){
        scanf("%s",c);
        s[i].first=c;
        s[i].second=i;
    }
    sort(s,s+n);
    for(int i=0;i<n;i++){
        pos[i]=i;
        a[i]=i;
    }
    for(int i=0;i<n;i++){
        if(s[i].second!=a[i]){
            int t=pos[s[i].second];
            ans[cnt]=make_pair(i+1,t+1);
            ++cnt;
            swap(a[i],a[t]);
            pos[a[i]]=i;
            pos[a[t]]=t;
        }
    }
    printf("%d\n",cnt);
    for(int i=0;i<cnt;i++){
        printf("%d %d\n",ans[i].first,ans[i].second);
    }
    return 0;
}

T2 骨粉(bone)

题意

n n n 个数 t i t_i ti,每一秒你可以将任意一个数减少 x x x,每一秒每一个数都会自然减少 1。求 s s s 秒后最大数的最小值。有多次询问。 n , m ≤ 1 0 5 ,    s i , x , ∑ ∣ t i ∣ ≤ 1 0 18 n,m\leq 10^5,\;s_i,x,\sum |t_i|\leq 10^{18} n,m105,si,x,ti1018

题解

首先目测可以二分答案。于是每次 check O ( n ) O(n) O(n) 扫一遍,统计 ∑ max ⁡ ( ⌈ t i − v x ⌉ , 0 ) \sum \max(\lceil \dfrac{t_i-v}{x}\rceil,0) max(xtiv,0)。60 分到手。

据说有一个很难想到但又并不偏门的套路:
⌈ t i − v x ⌉ = ⌈ ( ⌊ t i x ⌋ + { t i x } ) − ( ⌊ v x ⌋ + { v x } ) ⌉ = ⌈ ( ⌊ t i x ⌋ − ⌊ v x ⌋ ) + ( { t i x } − { v x } ) ⌉ = ⌊ t i x ⌋ − ⌊ v x ⌋ + ⌈ { t i x } − { v x } ⌉ = ⌊ t i x ⌋ − ⌊ v x ⌋ + [ t i   m o d   x > v   m o d   x ] \begin{aligned} &\left\lceil \dfrac{t_i-v}{x}\right\rceil\\ =&\left\lceil \left(\left\lfloor\dfrac{t_i}{x}\right\rfloor+\left\{\dfrac{t_i}{x}\right\}\right)-\left(\left\lfloor\dfrac{v}{x}\right\rfloor+\left\{\dfrac{v}{x}\right\}\right)\right\rceil\\ =&\left\lceil \left(\left\lfloor\dfrac{t_i}{x}\right\rfloor-\left\lfloor\dfrac{v}{x}\right\rfloor\right)+\left(\left\{\dfrac{t_i}{x}\right\}-\left\{\dfrac{v}{x}\right\}\right)\right\rceil\\ =&\lfloor\dfrac{t_i}{x}\rfloor-\lfloor\dfrac{v}{x}\rfloor+\left\lceil\{\dfrac{t_i}{x}\}-\{\dfrac{v}{x}\}\right\rceil\\ =&\lfloor\dfrac{t_i}{x}\rfloor-\lfloor\dfrac{v}{x}\rfloor+[t_i\bmod x>v\bmod x] \end{aligned} ====xtiv(xti+{xti})(xv+{xv})(xtixv)+({xti}{xv})xtixv+{xti}{xv}xtixv+[timodx>vmodx]
排序 t i t_i ti 之后,二分找到 t i > v t_i>v ti>v 的区间。 ⌊ t i x ⌋ \lfloor\dfrac{t_i}{x}\rfloor xti 的区间和很容易维护, ⌊ v x ⌋ \lfloor\dfrac{v}{x}\rfloor xv 的和直接计算,所以要着重考虑的是统计区间内 t i   m o d   x > v   m o d   x t_i\bmod x>v\bmod x timodx>vmodx 的个数。可以通过离线维护。也可以用可持久化数据结构,把 t i   m o d   x t_i\bmod x timodx 扔进主席树即可。

O ( log ⁡ n + m log ⁡ 2 n ) O(\log n+m\log^2n) O(logn+mlog2n)

代码

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
ll getint(){
    ll ans=0,f=1;
    char c=getchar();
    while(c<'0'||c>'9'){
        if(c=='-')f=-1;
        c=getchar();
    }
    while(c>='0'&&c<='9'){
        ans=ans*10+c-'0';
        c=getchar();
    }
    return ans*f;
}
const int N=1e5+10;
int n,m;
ll x;
ll a[N];
double sum[N];
ll maxa;

int sz[N*62],ch[N*62][2],root[N],tot=0;
void add(ll x,int num){
    ++tot;
    root[num]=tot;
    int p=root[num],q=root[num-1];
    for(int i=60;i>=0;--i){
        int t=(x>>i)&1;
        ch[p][t^1]=ch[q][t^1];
        sz[p]=sz[q]+1;
        ++tot;
        ch[p][t]=tot;
        p=ch[p][t],q=ch[q][t];
    }
    sz[p]=sz[q]+1;
}
int query(int r,ll x){
    //[1,r]>x
    r=root[r];
    int ans=0;
    for(int i=60;i>=0;--i){
        int t=(x>>i)&1;
        if(t==0)ans+=sz[ch[r][1]];
        r=ch[r][t];
    }
    return ans;
}

bool check(ll mid,ll s){
    int p=lower_bound(a+1,a+n+1,mid,greater<long long>())-a-1;
    double cnt=sum[p]-(mid/x)*1.0*p+query(p,mid%x);
    assert(sum[p]>=(mid/x)*1.0*p);
    //cerr<<"check "<<mid<<" "<<s<<" | "<<cnt<<" "<<p<<endl;
    return cnt<=s+1e-10;
}
ll getans(ll s){
    ll l=0,r=maxa,ans=0;
    while(l<=r){
        ll mid=l+r>>1;
        //cerr<<"> "<<l<<" "<<r<<" "<<mid<<endl;
        if(check(mid,s))ans=mid,r=mid-1;
        else l=mid+1;
    }
    return ans;
}

void putint(ll x){
    if(x>=10)putint(x/10);
    putchar('0'+x%10);
}

int main(){
    n=getint(),m=getint(),x=getint();
    for(int i=1;i<=n;i++){
        a[i]=getint();
        maxa=max(maxa,a[i]);
    }
    sort(a+1,a+n+1,greater<long long>());
    for(int i=1;i<=n;i++){
        sum[i]=sum[i-1]+a[i]/x;
        assert(sum[i]>=0);
        add(a[i]%x,i);
    }
    for(int i=0;i<m;i++){
        ll s=getint();
        ll ans=max(0ll,getans(s)-s);
        //printf("%lld\n",ans);
        putint(ans);putchar('\n');
    }
    return 0;
}

T3 字符串问题(string)

题意

有一个长为 n n n 的数字串,在其中加 k k k 个加号(允许出现前导零,不允许 1 + + 23 1++23 1++23 + 1 + 23 +1+23 +1+23 等格式),求所有合法表达式运算结果之和   m o d   998244353 \bmod 998244353 mod998244353 1 ≤ n ≤ 5 ⋅ 1 0 5 , 0 ≤ k < n 1\leq n\leq 5\cdot10^5,0\leq k<n 1n5105,0k<n

题解

对于每个数字单独计算它的贡献。它的贡献由它往右最近一个加号决定。(右侧没有加号特殊处理)

为了方便,数码从右往左从 0 到 n − 1 n-1 n1 编号。

假设第 i i i 个数码往右 j j j 个数才会遇到一个 + + +,答案分两部分:

  • i = j i=j i=j(即之后没有加号): ∑ i = 0 n − 1 a i × 1 0 i × ( n − i − 1 k ) \sum\limits_{i=0}^{n-1}a_i\times 10^i\times {n-i-1\choose k} i=0n1ai×10i×(kni1)
  • i ≠ j i\ne j i=j(即之后有加号):
    ∑ i = 0 n − 1 ∑ j = 0 i − 1 a i × 1 0 j × ( n − j − 2 k ) = ∑ i = 0 n − 1 a i ∑ j = 0 i − 1 1 0 j × ( n − j − 2 k ) = ∑ j = 0 n − 1 1 0 j × ( n − j − 2 k − 1 ) ∑ i = j + 1 n − 1 a i \begin{aligned} &\sum\limits_{i=0}^{n-1}\sum\limits_{j=0}^{i-1}a_i\times10^j\times{n-j-2\choose k}\\ =&\sum\limits_{i=0}^{n-1}a_i\sum\limits_{j=0}^{i-1}10^j\times{n-j-2\choose k}\\ =&\sum\limits_{j=0}^{n-1}10^j\times{n-j-2\choose k-1}\sum\limits_{i=j+1}^{n-1}a_i \end{aligned} ==i=0n1j=0i1ai×10j×(knj2)i=0n1aij=0i110j×(knj2)j=0n110j×(k1nj2)i=j+1n1ai

预处理 10 的次幂、阶乘、阶乘的逆元、 a i a_i ai 的后缀和(按照原题的顺序是前缀和)就能 O ( n ) O(n) O(n) 求出答案。

代码pow10 做变量名似乎会炸):

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
ll getint(){
    ll ans=0,f=1;
    char c=getchar();
    while(c<'0'||c>'9'){
        if(c=='-')f=-1;
        c=getchar();
    }
    while(c>='0'&&c<='9'){
        ans=ans*10+c-'0';
        c=getchar();
    }
    return ans*f;
}
const int N=5e5+10,mod=998244353;
int pooooooooow10[N],fac[N],ifac[N],sum[N];
int qpow(int x,int y){
    int ans=1;
    while(y){
        if(y&1)ans=ans*1ll*x%mod;
        x=x*1ll*x%mod;
        y>>=1;
    }
    return ans;
}
int getinv(int x){return qpow(x,mod-2);}
int c(int x,int y){
    if(x<0||y<0||y>x)return 0;
    return fac[x]*1ll*ifac[y]%mod*ifac[x-y]%mod;
}
int a[N];
int main(){
    int n=getint(),k=getint();
    for(int i=0;i<n;i++){
        char c=getchar();
        while(c<'0'||c>'9')c=getchar();
        a[i]=c-'0';
        if(i!=0)sum[i]=sum[i-1]+a[i];
        else sum[i]=a[i];
    }
    pooooooooow10[0]=fac[0]=ifac[0]=1;
    for(int i=1;i<=n;i++){
        pooooooooow10[i]=pooooooooow10[i-1]*10ll%mod;
        fac[i]=fac[i-1]*1ll*i%mod;
    }
    ifac[n]=getinv(fac[n]);
    for(int i=n-1;i>=0;--i){
        ifac[i]=ifac[i+1]*(i+1ll)%mod;
    }
    int ans=0;
    for(int i=0;i<=n-1;i++){
        ans=(ans+a[n-i-1]*1ll*pooooooooow10[i]%mod*c(n-i-1,k)%mod)%mod;
        if(n-i-2>=0)ans=(ans+sum[n-i-2]*1ll*pooooooooow10[i]%mod*c(n-i-2,k-1)%mod)%mod;
    }
    cout<<ans;
    return 0;
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值