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

本文介绍了一种针对十六进制数的特殊操作SOD值的计算方法,通过线段树数据结构高效处理区间内元素的SOD值计算及更新,实现了对给定十六进制字符串的快速查询与修改。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Description

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

1 p c:ss的第p位改成cc

2 l r:对区间[l,r][l,r]的所有非空子序列求SODSOD

Input

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

(1n,q105)(1≤n,q≤105)

Output

对于每次查询操作,假设SODSOD值为ii的子序列有ai个,那么输出i=015ai1021i mod 109+7∑i=015ai⋅1021i mod 109+7

Sample Input

5 2
12345
2 1 1
2 1 3

Sample Output

1021
267411465

Solution

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

SOD(v)=015v%15v=0v>0,v%15=0elseSOD(v)={0v=015v>0,v%15=0v%15else

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

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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值