Newcoder 141 F.Sum Of Digit(数论+线段树)

186 篇文章 0 订阅
59 篇文章 0 订阅

Description

对于一个十进制数字 v v ,定义SOD(v):若 v<16 v < 16 SOD(v)=v S O D ( v ) = v ,否则 SOD(v)=SOD(S(v)) S O D ( v ) = S O D ( S ( v ) ) ,其中 S(v) S ( v ) 为将 v v 的十六进制每位数的和。现在给出一个由16进制数组成的长度为 n n 的字符串s,有 q q 次操作,操作分两种:

1 p c: s s 的第p位改成 c c

2 l r:对区间 [l,r] [ l , r ] 的所有非空子序列求 SOD S O D

Input

第一行两个整数 n,q n , q ,之后输入一个由十六进制数组成的长度为 n n 的字符串,最后q行每行一个操作

(1n,q105) ( 1 ≤ n , q ≤ 10 5 )

Output

对于每次查询操作,假设 SOD S O D 值为 i i 的子序列有ai个,那么输出 i=015ai1021i mod 109+7 ∑ i = 0 15 a i ⋅ 1021 i   m o d   10 9 + 7

Sample Input

5 2
12345
2 1 1
2 1 3

Sample Output

1021
267411465

Solution

对于十六进制数 v=vi16i v = ∑ v i 16 i ,在求 SOD(v) S O D ( v ) 时每递归一次 v v 会减少vi(16i1),而该减少值显然是 15 15 的倍数,直至 v v 不超过15递归终止,那么有

SOD(v)=015v%15v=0v>0,v%15=0else S O D ( v ) = { 0 v = 0 15 v > 0 , v % 15 = 0 v % 15 e l s e

而注意到 SOD(x16i+y)=SOD(x+y) S O D ( x ⋅ 16 i + y ) = S O D ( x + y ) ,故可以用线段树维护每个区间中 0,...,15 0 , . . . , 15 出现的次数,区间合并即为两个长度为 16 16 序列的卷积,时间复杂度 O(162nlogn) O ( 16 2 n l o g n )

Code

#include<cstdio>
#include<cstring>
using namespace std;
typedef long long ll;
#define maxn 100005 
#define mod 1000000007
int mul(int x,int y)
{
    ll z=1ll*x*y;
    return z-z/mod*mod;
}
int add(int x,int y)
{
    x+=y;
    if(x>=mod)x-=mod;
    return x;
}
int SOD(int x)
{
    if(x==0)return 0;
    if(x%15==0)return 15;
    return x%15;
}
int deal(char c)
{
    if(c>='0'&&c<='9')return c-'0';
    return 10+c-'A';
}
int n,q,f[16];
struct node
{
    int x[16];
    void init()
    {
        memset(x,0,sizeof(x));
    }
    node operator+(const node&b)const
    {
        node c;
        c.init();
        for(int i=0;i<16;i++)
            for(int j=0;j<16;j++)
                c.x[SOD(i+j)]=add(c.x[SOD(i+j)],mul(x[i],b.x[j]));
        return c;
    }
}Num[maxn<<2],ans;
char s[maxn];
#define ls (t<<1)
#define rs ((t<<1)|1) 
void push_up(int t)
{
    Num[t]=Num[ls]+Num[rs];
}
void build(int l,int r,int t)
{
    Num[t].init();
    if(l==r)
    {
        Num[t].x[0]=add(Num[t].x[0],1);
        Num[t].x[deal(s[l])]=add(Num[t].x[deal(s[l])],1);
        return ;
    }
    int mid=(l+r)/2;
    build(l,mid,ls),build(mid+1,r,rs);
    push_up(t);
}
void update(int x,int l,int r,int t,char c)
{
    if(l==r)
    {
        Num[t].x[deal(s[l])]=add(Num[t].x[deal(s[l])],mod-1);
        Num[t].x[deal(c)]=add(Num[t].x[deal(c)],1);
        return ;
    }
    int mid=(l+r)/2;
    if(x<=mid)update(x,l,mid,ls,c);
    else update(x,mid+1,r,rs,c);
    push_up(t); 
}
node query(int L,int R,int l,int r,int t)
{
    if(L==l&&r==R)return Num[t];
    int mid=(l+r)/2;
    if(R<=mid)return query(L,R,l,mid,ls);
    if(L>mid)return query(L,R,mid+1,r,rs); 
    return query(L,mid,l,mid,ls)+query(mid+1,R,mid+1,r,rs);
}
int main()
{
    f[0]=1;
    for(int i=1;i<16;i++)f[i]=mul(1021,f[i-1]);
    scanf("%d%d%s",&n,&q,s+1);
    build(1,n,1);
    while(q--)
    {
        int op,l,r;
        char c[3];
        scanf("%d",&op);
        if(op==1)
        {
            scanf("%d%s",&l,c);
            update(l,1,n,1,c[0]);
            s[l]=c[0];
        }
        else
        {
            scanf("%d%d",&l,&r);
            ans=query(l,r,1,n,1);
            ans.x[0]=add(ans.x[0],mod-1);
            int res=0;
            for(int i=0;i<16;i++)res=add(res,mul(f[i],ans.x[i]));
            printf("%d\n",res);
        }
    }
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值