In mathematics you don't understand things, you just get used to them.

分类 笔记 下的文章

Description

Link.

给定长度为 $n$ 的序列 $a_1, a_2, \dots, a_n$;共 $m$ 组询问,每次询问给出 $d,p_1,p_2$,求

$$ \sum_{i=0}^{d-1} \sum_{j=0}^{d-1} \sum_{k=0}^{d-1} a_{p_1+d\cdot i+j} a_{p_2 + d\cdot j + k} $$

Solution

$$ \sum_{i=0}^{d-1}\sum_{j=0}^{d-1}\sum_{k=0}^{d-1}a(p+di+j)a(q+dj+k) \\ \begin{aligned} &=\sum_{i=0}^{d-1}\sum_{j=0}^{d-1}a(p+di+j)\sum_{k=0}^{d-1}a(q+dj+k) \\ &=\sum_{j=0}^{d-1}\left(\sum_{i=0}^{d-1}a(p+di+j)\right)\left(\sum_{k=0}^{d-1}a(q+dj+k)\right) \\ &=\sum_{j=0}^{d-1}\left(\sum_{i=0}^{d-1}a(p+di+j)\right)\left(pre(q+dj+d-1)-pre(q+dj-1)\right) \\ \end{aligned} $$

由题意,$q+dj+k\le n$,也就是说 $q+d(d-1)+d(-1)=q+d^{2}\le n$,$q_{\min}=1$,所以 $d$ 是根号规模。

那么就可以直接来了,设 $s(i,j)$ 为前缀 $i$ 的间隔 $j$ 前缀和。比如 $\texttt{1 2 3 4 5 6}$ 的 $s(6,2)=12$,也就是 $\texttt{1}$$\texttt{ 2}$$\texttt{ 3}$$\texttt{ 4}$$\texttt{ 5}$$\texttt{ 6}$,蓝色表示被计入贡献,这个东西显然可以递推。

然后把这个带进式子

$$ \sum_{j=0}^{d-1}\left(\sum_{i=0}^{d-1}a(p+di+j)\right)\left(pre(q+dj+d-1)-pre(q+dj-1)\right) \\ \begin{aligned} &=\sum_{j=0}^{d-1}\left(s(p+j+d(d-1),d)-s(p+j,d)+a(p+j)\right)\left(pre(q+dj+d-1)-pre(q+dj-1)\right) \\ \end{aligned} $$

然后就可以直接算了。

然后你会发现你被卡常了,于是把 $s(i,j)$ 换成 $s(j,i)$,前面是根号大小寻址更快,就可以无压力过掉了。

#include<bits/stdc++.h>
typedef unsigned int ui;
const int border=450;
int n,m,opd,opp,opq;
ui a[200010],s[500][200010],prs[200010],ans;
namespace IO{
    const int sz=1<<22;
    char a[sz+5],b[sz+5],*p1=a,*p2=a,*t=b,p[105];
    inline char gc(){
        return p1==p2?(p2=(p1=a)+fread(a,1,sz,stdin),p1==p2?EOF:*p1++):*p1++;
    }
    template<class T> void gi(T& x){
        x=0; char c=gc();
        for(;c<'0'||c>'9';c=gc());
        for(;c>='0'&&c<='9';c=gc())
            x=(x<<3)+(x<<1)+(c-'0');
    }
    inline void flush(){fwrite(b,1,t-b,stdout),t=b; }
    inline void pc(char x){*t++=x; if(t-b==sz) flush(); }
    template<class T> void pi(T x,char c='\n'){
        if(x<0) x=-x;
        if(x==0) pc('0'); int t=0;
        for(;x;x/=10) p[++t]=x%10+'0';
        for(;t;--t) pc(p[t]); pc(c);
    }
    struct F{~F(){flush();}}f; 
}
using IO::gi;
using IO::pi;
int main()
{
    gi(n);
    for(int i=1;i<=n;++i)    gi(a[i]),prs[i]=prs[i-1]+a[i];
    for(int j=1;j<=border;++j)
    {
        for(int i=1;i<=j;++i)    s[j][i]=a[i];
        for(int i=j+1;i<=n;++i)    s[j][i]=s[j][i-j]+a[i];
    }
    gi(m);
    while(m--)
    {
        gi(opd),gi(opp),gi(opq);
        ans=0;
        for(int i=0;i<opd;++i)    ans+=(s[opd][opp+i+opd*(opd-1)]-s[opd][opp+i]+a[opp+i])*(prs[opq+opd*i+opd-1]-prs[opq+opd*i-1]);
        pi(ans);
    }
    return 0;
}

「CF 1520A」Do Not Be Distracted!

Link.

模拟。

#include<bits/stdc++.h>
char now;
char get_char(){char res=getchar();while(res<'A' || res>'Z')    res=getchar(); return res;}
bool vis[26];
int main()
{
    int T,n;
    scanf("%d",&T);
    while(T-->0)
    {
        scanf("%d",&n);
        int ans=0;
        for(int i=1;i<=n;++i)
        {
            char cur=get_char();
            if(cur!=now)
            {
                now=cur;
                if(vis[cur-'A'])    ans=1;
            }
            vis[cur-'A']=1;
        }
        puts(ans?"NO":"YES");
        memset(vis,0,sizeof vis);
    }
    return 0;
}

「CF 1520B」Ordinary Numbers

Link.

按位考虑。

#include<bits/stdc++.h>
typedef long long ll;
int main()
{
    int T;
    scanf("%d",&T);
    while(T-->0)
    {
        ll ans=0,n;
        scanf("%lld",&n);
        for(ll pw=1;pw<=n;pw=pw*10+1)    for(int now=1;now<=9;++now)    if(pw*now<=n)    ++ans;
        printf("%lld\n",ans);
    }
    return 0;
}

「CF 1520C」Not Adjacent Matrix

Link.

用 flows 的 trick 来构造,$(i+j)$ 为奇先填,为偶后填。

#include<bits/stdc++.h>
int main()
{
    int T,n;
    scanf("%d",&T);
    while(T-->0)
    {
        scanf("%d",&n);
        if(n==2)    puts("-1");
        else
        {
            int cur=0;
            static int ans[110][110];
            for(int i=1;i<=n;++i)    for(int j=1;j<=n;++j)    if((i+j)&1)    ans[i][j]=++cur;
            for(int i=1;i<=n;++i)    for(int j=1;j<=n;++j)    if((i+j)&1^1)    ans[i][j]=++cur;
            for(int i=1;i<=n;++i,puts(""))    for(int j=1;j<=n;++j)    printf("%d ",ans[i][j]);
        }
    }
    return 0;
}

「CF 1520D」Same Differences

Link.

式子移项。

#include<bits/stdc++.h>
typedef long long ll;
int a[200010],cnt[500010];
int main()
{
    int T,n;
    scanf("%d",&T);
    while(T-->0)
    {
        scanf("%d",&n);
        for(int i=1;i<=n;++i)    scanf("%d",&a[i]),a[i]-=i,a[i]+=200000,++cnt[a[i]];
        ll ans=0;
        for(int i=1;i<=n;++i)    ans+=cnt[a[i]]-1;
        printf("%lld\n",ans/2);
        for(int i=1;i<=n;++i)    --cnt[a[i]];
    }
    return 0;
}

「CF 1520E」Arranging The Sheep

Link.

所有牛往中间那头牛走。

#include<bits/stdc++.h>
typedef long long ll;
#define All(x) (x).begin(),(x).end()
int fuck[1000010];
int main()
{
    int T,n;
    scanf("%d",&T);
    while(T-->0)
    {
        scanf("%d",&n);
        int tot=0;
        for(int i=1;i<=n;++i)
        {
            char now=getchar();
            while((now^    '.') && (now^'*'))    now=getchar();
            if(now=='*')    fuck[++tot]=i;
        }
        int mpos=int(std::ceil(tot/2.0));
        ll ans=0;
        for(int i=1;i<=tot;++i)    ans+=std::abs(fuck[i]-fuck[mpos]+mpos-i);
        printf("%lld\n",ans);
    }
    return 0;
}

「CF 1520F1」Guess the K-th Zero (Easy version)

Link.

二分维护答案区间,询问 $\log_{2}$ 次。

#include<bits/stdc++.h>
#define fl fflush(stdout)
int n,t,k;
int q(int l,int r){int res=0; printf("? %d %d\n",l,r); fl; scanf("%d",&res); return res;}
int main()
{
    scanf("%d %d",&n,&t);
    while(t--)
    {
        scanf("%d",&k);
        int l=1,r=n,ans=0;
        while(l<=r)
        {
            int mid=(l+r)>>1,tmp=mid-l+1-q(l,mid);
            if(tmp>=k)    r=mid-1,ans=mid;
            else    k-=tmp,l=mid+1;
        }
        printf("! %d\n",ans);
        fl;
    }
    return 0;
}

「CF 1520F2」Guess the K-th Zero (Hard version)

Link.

把二分记忆化下来,用 std::map 和 BIT 实现。

#include<bits/stdc++.h>
#define fl fflush(stdout)
int n,t,k;
struct FWT
{
    #define l(x) ((x)&-(x))
    int tr[200010];
    FWT(){memset(tr,0,sizeof tr);}
    void i(int x){for(;x<=n;x+=l(x))    ++tr[x];}
    int $(int x){int r=0; for(;x;x^=l(x))    r+=tr[x]; return r;}
    int f(int l,int r){return $(r)-$(l-1);}
}fw;
std::map<std::tuple<int,int>,int> mp;
int q(int l,int r){
    if(mp.find(std::tie(l,r))==mp.end())
    {
        int res=0;
        printf("? %d %d\n",l,r);
        fl;
        scanf("%d",&res);
        mp[std::tie(l,r)]=res;
        return res;
    }
    else    return mp[std::tie(l,r)]+fw.f(l,r);
}
int main()
{
    scanf("%d %d",&n,&t);
    while(t--)
    {
        scanf("%d",&k);
        int l=1,r=n,ans=0;
        while(l<=r)
        {
            int mid=(l+r)>>1,tmp=mid-l+1-q(l,mid);
            if(tmp>=k)    r=mid-1,ans=mid;
            else    k-=tmp,l=mid+1;
        }
        printf("! %d\n",ans);
        fl;
        fw.i(ans);
    }
    return 0;
}

「CF 1520G」To Go Or Not To Go?

Link.

广搜。

#include<bits/stdc++.h>
typedef long long ll;
#define sf(x) scanf("%d",&x)
#define ssf(x) scanf("%lld",&x)
const int wax[4]={1,-1,0,0},way[4]={0,0,1,-1};
int n,m;
ll w,dis0[2010][2010],dis1[2010][2010],a[2010][2010];
bool Inside(int x,int y){return !(x<1 || x>n || y<1 || y>m);}
void Compute_0()
{
    std::queue<std::tuple<int,int>> q;
    q.emplace(1,1);
    dis0[1][1]=1;
    while(!q.empty())
    {
        int nowx,nowy;
        std::tie(nowx,nowy)=q.front();
        q.pop();
        for(int k=0;k<4;++k)
        {
            int tox=nowx+wax[k],toy=nowy+way[k];
            if(Inside(tox,toy) && !dis0[tox][toy] && ~a[tox][toy])    dis0[tox][toy]=dis0[nowx][nowy]+1,q.emplace(tox,toy);
        }
    }
    for(int i=1;i<=n;++i)    for(int j=1;j<=m;++j)    --dis0[i][j];
}
void Compute_1()
{
    std::queue<std::tuple<int,int>> q;
    q.emplace(n,m);
    dis1[n][m]=1;
    while(!q.empty())
    {
        int nowx,nowy;
        std::tie(nowx,nowy)=q.front();
        q.pop();
        for(int k=0;k<4;++k)
        {
            int tox=nowx+wax[k],toy=nowy+way[k];
            if(Inside(tox,toy) && !dis1[tox][toy] && ~a[tox][toy])    dis1[tox][toy]=dis1[nowx][nowy]+1,q.emplace(tox,toy);
        }
    }
    for(int i=1;i<=n;++i)    for(int j=1;j<=m;++j)    --dis1[i][j];
}
int main()
{
    sf(n),sf(m),ssf(w);
    for(int i=1;i<=n;++i)    for(int j=1;j<=m;++j)    ssf(a[i][j]);
    Compute_0(),Compute_1();
    ll mxtmp=std::numeric_limits<ll>::max();
    ll ans=~dis0[n][m]?w*dis0[n][m]:mxtmp,ozd=mxtmp;
    for(int i=1;i<=n;++i)    for(int j=1;j<=m;++j)    if(~dis1[i][j] && a[i][j]>=1)    ozd=std::min(ozd,a[i][j]+w*dis1[i][j]);
    for(int i=1;i<=n;++i)    for(int j=1;j<=m;++j)    if(~dis0[i][j] && a[i][j]>=1 && ozd!=mxtmp)    ans=std::min(ans,w*dis0[i][j]+a[i][j]+ozd);
    if(ans==mxtmp)    puts("-1");
    else    printf("%lld\n",ans);
    return 0;
}

Description

Link.

修改边权的动态 MST。

Solution

讲清楚点。

修改边权的 MST,考虑对时间分治。设我们当前操作的操作区间是 $[l,r]$,直接暴力找 MST 是不行的。

考虑找出必要的边和必不要的边。

  • 若把 $[l,r]$ 操作中的边边权改成 $+\infty$,拿原图点集和不包含 $[l,r]$ 中边的边集和 $[l,r]$ 中边权修改为 $+\infty$ 后的边的集合的并集作为边集跑 MST,此时如果不在 MST 上的边一定不在最终的 MST 边集中。
  • 同理,改成 $-\infty$,此时在 MST 边集里的边一定在最终的 MST 边集中。

这样暴力跑 Kruskal 的规模保证在了 $O(r-l+1)$。

复杂度 $O(n\log_{2}^{2}n)$。

#include<bits/stdc++.h>
const int INF=std::numeric_limits<int>::max();
struct edge
{
    int fr,ba,val,ID;
    edge():fr(0),ba(0),val(0),ID(0){}
    edge(int _fr,int _ba,int _val,int _id):fr(_fr),ba(_ba),val(_val),ID(_id){}
    friend bool operator < (const edge &lhs,const edge &rhs){return lhs.val<rhs.val;}
}edge_set[20][100010],ori_edge[100010],tmp_edge[100010];
struct oper
{
    int ID,val;
    oper():ID(0),val(0){}
    oper(int _id,int _val):ID(_id),val(_val){}
}ope[100010];
int tagcur[100010],tagope[100010],n,m,q,size_v[20],size_e[20],ver_set[20][100010],wgt[100010],srt[100010];
struct DSU
{
    int fa[100010];
    int f(int i){return fa[i];}
    void clear(int l){std::iota(fa+1,fa+l+1,1);}
    int find(int x){return (x^fa[x])?fa[x]=find(fa[x]):x;}
    bool merge(int x,int y){x=find(x),y=find(y); if(x^y)    return fa[x]=y,true; else    return false;}
}dsu;
int read()
{
    int x=0,f=1;
    char c=getchar();
    while(c<'0' || c>'9')
    {
        if(c=='-')    f=-1;
        c=getchar();
    }
    while(c>='0' && c<='9')    x=(x<<3)+(x<<1)+(c^'0'),c=getchar();
    if(~f)    return x;
    else    return -x;
}
int wrstk[100];
void write(long long x,char las='\n')
{
    int top=0;
    if(x<0)    x=-x,putchar('-');
    do wrstk[++top]=x%10,x/=10; while(x);
    while(top)    putchar(wrstk[top--]^'0');
    putchar(las);
}
void rawgrass(int l,int r,int dep,long long ans)
{
    const int &n=size_v[dep],&m=size_e[dep];
    if(l^r)
    {
        for(int i=1;i<=m;++i)    tagope[edge_set[dep][i].ID]=0;
        for(int i=l;i<=r;++i)    tagcur[ope[i].ID]=1;
        for(int i=1;i<=m;++i)
        {
            tmp_edge[i]=edge_set[dep][i];
            if(tagcur[tmp_edge[i].ID])    tmp_edge[i].val=INF;
        }
        std::sort(tmp_edge+1,tmp_edge+m+1);
        dsu.clear(n);
        for(int i=1;i<=m;++i)    if(!dsu.merge(tmp_edge[i].fr,tmp_edge[i].ba) && tmp_edge[i].val!=INF)    tagope[tmp_edge[i].ID]=-1;
        for(int i=l;i<=r;++i)    tagcur[ope[i].ID]=1;
        for(int i=1;i<=m;++i)
        {
            tmp_edge[i]=edge_set[dep][i];
            if(tagcur[tmp_edge[i].ID])    tmp_edge[i].val=-INF;
        }
        std::sort(tmp_edge+1,tmp_edge+m+1);
        dsu.clear(n);
        for(int i=1;i<=m;++i)    if(dsu.merge(tmp_edge[i].fr,tmp_edge[i].ba) && tmp_edge[i].val!=-INF)    tagope[tmp_edge[i].ID]=1;
        for(int i=l;i<=r;++i)    tagcur[ope[i].ID]=0;
        size_v[dep+1]=size_e[dep+1]=0;
        dsu.clear(n);
        for(int i=1;i<=m;++i)    if(tagope[edge_set[dep][i].ID]==1)    dsu.merge(edge_set[dep][i].fr,edge_set[dep][i].ba);
        for(int i=1;i<=n;++i)    if(dsu.f(i)==i)    ver_set[dep+1][++size_v[dep+1]]=ver_set[dep][i],srt[i]=size_v[dep+1];
        for(int i=1;i<=m;++i)
        {
            if(tagope[edge_set[dep][i].ID]==1)    ans+=edge_set[dep][i].val;
            else if(tagope[edge_set[dep][i].ID]==0)
            {
                int x=edge_set[dep][i].fr,y=edge_set[dep][i].ba;
                x=dsu.find(x),y=dsu.find(y);
                if(x^y)    edge_set[dep+1][++size_e[dep+1]]=edge(srt[x],srt[y],edge_set[dep][i].val,edge_set[dep][i].ID);
            }
        }
        int mid=(l+r)>>1;
        rawgrass(l,mid,dep+1,ans);
        for(int i=l;i<=mid;++i)    wgt[ope[i].ID]=ope[i].val;
        for(int i=1;i<=size_e[dep+1];++i)    if(wgt[edge_set[dep+1][i].ID]!=INF)    edge_set[dep+1][i].val=wgt[edge_set[dep+1][i].ID];
        for(int i=l;i<=mid;++i)    wgt[ope[i].ID]=INF;
        rawgrass(mid+1,r,dep+1,ans);
    }
    else
    {
        for(int i=1;i<=m;++i)
        {
            tmp_edge[i]=edge_set[dep][i];
            if(tmp_edge[i].ID==ope[l].ID)    tmp_edge[i].val=ope[l].val;
        }
        std::sort(tmp_edge+1,tmp_edge+m+1);
        dsu.clear(n);
        for(int i=1;i<=m;++i)    if(dsu.merge(tmp_edge[i].fr,tmp_edge[i].ba))    ans+=tmp_edge[i].val;
        write(ans);
    }
}
int main()
{
    n=read(),m=read(),q=read();
    for(int i=1,x,y,z;i<=m;++i)    x=read(),y=read(),z=read(),ori_edge[i]=edge(x,y,z,i);
    for(int i=1,id,k;i<=q;++i)    id=read(),k=read(),ope[i]=oper(id,k);
    size_v[0]=n;
    size_e[0]=m;
    for(int i=1;i<=size_v[0];++i)    ver_set[0][i]=i;
    for(int i=1;i<=size_e[0];++i)    edge_set[0][i]=ori_edge[i],wgt[i]=INF;
    rawgrass(1,q,0,0);
    return 0;
}

Description

Link.

一共 $n$ 天,每天可以卖出或者买入两种股票 $A$ 和 $B$。这两种股票在第 $i$ 天的价值为 $A_i$ 和 $B_i$。

每天可以花所有的现金买入股票,这些股票中 $A$ 股与 $B$ 股的数量比为 $rate_i$。每天也可以把所有的股票以当天的价值卖出,获得现金。已知接下来 $n$ 天的 $A_i,B_i,rate_i$,求出 $n$ 天后能够获得的最大价值。

Solution

设 $f(i)$ 为第 $i$ 天的最大钱数,$g_{1}(i)$ 为 A 券的第 $i$ 天能换的数量,$g_{2}(i)$ 则为 B 券。

转移可以解方程得:

$$ f(i)=\max\{f(i-1),a(i)g_{1}(j),b(i)g_{2}(j)\},j\in[1,i) \\ g_{1}(i)\frac{f(i)rate(i)}{a(i)rate(i)+b(i)} \\ g_{2}(i)=\frac{f(i)}{rate(i)\times a(i)+b(i)} \\ $$

两个 $g$ 都没啥问题,主要来看 $f(i)$。提一下可得:

$$ f(i)=\max\{b(i)\max_{j=1}^{i-1}\{\frac{a(i)}{b(i)}g_{1}(j)+g_{2}(j)\},f(i-1)\} $$

斜率优化的形式,但斜率并无单调性。那个 $f(i-1)$ 可以最后来线性做,所以可以先不管。然后就是 li-chao tree 的板子。

#include<bits/stdc++.h>
std::vector<double> pri;
int n,nodes[400010];
double g[5][100010],f[100010],a[100010],b[100010],rate[100010],slp[100010];
double _f(int i,int j){return pri[i-1]*g[1][j]+g[2][j];}
void ins(int l,int r,int x,int i)
{
    if(l^r)
    {
        int mid=(l+r)>>1;
        if(_f(mid,i)>_f(mid,nodes[x]))    std::swap(nodes[x],i);
        if(_f(l,i)>_f(l,nodes[x]))    ins(l,mid,x<<1,i);
        else    ins(mid+1,r,x<<1|1,i);
    }
    else if(_f(l,i)>_f(l,nodes[x]))    nodes[x]=i;
}
double find(int l,int r,int x,int i)
{
    if(l^r)
    {
        int mid=(l+r)>>1;
        if(mid>=i)    return std::max(find(l,mid,x<<1,i),_f(i,nodes[x]));
        else    return std::max(find(mid+1,r,x<<1|1,i),_f(i,nodes[x]));
    }
    else    return _f(i,nodes[x]);
}
#define All(x) (x).begin(),(x).end()
int main()
{
    double pref=0,nowf=0;
    scanf("%d %lf",&n,&nowf);
    pref=nowf;
    for(int i=1;i<=n;++i)
    {
        scanf("%lf %lf %lf",&a[i],&b[i],&rate[i]);
        slp[i]=a[i]/b[i];
        pri.emplace_back(slp[i]);
    }
    std::sort(All(pri));
    for(int i=1;i<=n;++i)
    {
        nowf=std::max(pref,b[i]*find(1,n,1,std::lower_bound(All(pri),slp[i])-pri.begin()+1));
        g[1][i]=nowf*rate[i]/(a[i]*rate[i]+b[i]);
        g[2][i]=nowf/(a[i]*rate[i]+b[i]);
        ins(1,n,1,i);
        pref=nowf;
    }
    printf("%.3f\n",nowf);
    return 0;
}

Description

Link.

求 $\sum\limits_{i=1}^n\sum\limits_{j=1}^n(i+j)^kf(\gcd(i,j))\gcd(i,j)$。

Solution

$$ \begin{aligned} \textbf{ANS}&=\sum_{i=1}^{n}\sum_{j=1}^{n}(i+j)^{k}\mu^{2}(\gcd(i,j))\gcd(i,j) \\ &=\sum_{d=1}^{n}\sum_{i=1}^{n}\sum_{j=1}^{n}(i+j)^{k}\mu^{2}(d)d[\gcd(i,j)=d] \\ &=\sum_{d=1}^{n}d^{k+1}\times\mu^{2}(d)\sum_{i=1}^{\lfloor\frac{n}{d}\rfloor}\sum_{j=1}^{\lfloor\frac{n}{d}\rfloor}(i+j)^{k}\sum_{h|i,h|j}\mu(h) \\ &=\sum_{d=1}^{n}d^{k+1}\times\mu^{2}(d)\sum_{h=1}^{\lfloor\frac{n}{d}\rfloor}\mu(h)\times h^{k}\times\sum_{i=1}^{\lfloor\frac{n}{dh}\rfloor}\sum_{j=1}^{\lfloor\frac{n}{dh}\rfloor}(i+j)^{k} \\ &=\sum_{d=1}^{n}d^{k+1}\times\mu^{2}(d)\sum_{h=1}^{\lfloor\frac{n}{d}\rfloor}\mu(h)\times h^{k}\times\sum_{i=1}^{\lfloor\frac{n}{dh}\rfloor}\sum_{j=1}^{\lfloor\frac{n}{dh}\rfloor}(i+j)^{k} \\ \end{aligned} \\ $$

前面两个和式里面显然能算,考虑怎么对于 $x$ 算 $\sum_{i=1}^{x}\sum_{j=1}^{x}(i+j)^{k}$。考虑对其差分:

$$ \begin{aligned} \left(\sum_{i=1}^{x+1}\sum_{j=1}^{x+1}(i+j)^{k}\right)-\left(\sum_{i=1}^{x}\sum_{j=1}^{x}(i+j)^{k}\right)&=\sum_{i=1}^{x}\sum_{j=1}^{x+1}(i+j)^{k}+\sum_{i=1}^{x+1}(x+1+i)^{k}-\sum_{i=1}^{x}\sum_{j=1}^{x}(i+j)^{k} \\ &=\sum_{i=1}^{x}\left(\sum_{j=1}^{x+1}(i+j)^{k}-\sum_{j=1}^{x}(i+j)^{k}\right)+\sum_{i=1}^{x+1}(x+1+i)^{k} \\ &=\sum_{i=1}^{x}(x+1+i)^{k}+\sum_{i=1}^{x+1}(x+1+i)^{k} \\ \end{aligned} $$

然后滚个前缀和就可以算了。

#include<bits/stdc++.h>
typedef long long LL;
const int MOD=998244353;
int norm( LL x ) {
    if( x<0 ) {
        x+=MOD;
    }
    if( x>=MOD ) {
        x%=MOD;
    }
    return x;
}
int n,k,ans;
int qpow( int bas,int fur ) {
    int res=1;
    while( fur ) {
        if( fur&1 ) {
            res=norm( LL( res )*bas );
        }
        bas=norm( LL( bas )*bas );
        fur>>=1;
    }
    return norm( res+MOD );
}
std::tuple<std::vector<int>,std::vector<int>> makePrime( int n ) {
    std::vector<int> prime,tag( n+1 ),mu( n+1 ),pw( n+1 );
    pw[0]=1;
    mu[1]=pw[1]=1;
    for( int i=2;i<=n;++i ) {
        if( !tag[i] ) {
            mu[i]=norm( -1 );
            prime.emplace_back( i );
            pw[i]=qpow( i,k );
        }
        for( int j=0;j<int( prime.size() ) && i*prime[j]<=n;++j ) {
            tag[i*prime[j]]=1;
            pw[i*prime[j]]=norm( LL( pw[i] )*pw[prime[j]] );
            if( i%prime[j]==0 ) {
                mu[i*prime[j]]=0;
                break;
            } else {
                mu[i*prime[j]]=norm( -mu[i] );
            }
        }
    }
    return std::tie( mu,pw );
}
int main() {
    LL tmp;
    scanf( "%d %lld",&n,&tmp );
    k=tmp%( MOD-1 );
    std::vector<int> mu,pw,prt( n+1 ),exprt( n+1 ),preSum( n+1 );
    // prt: i^(k+1)*mu^2(i)
    // exprt: mu(i)*i^k
    // preSum sum sum (i+j)^k
    std::tie( mu,pw )=makePrime( n<<1|1 );
    for( int i=1;i<=n;++i ) {
        prt[i]=norm( prt[i-1]+norm( LL( norm( LL( norm( LL( mu[i] )*mu[i] ) )*pw[i] ) )*i ) );
        exprt[i]=norm( exprt[i-1]+norm( LL( mu[i] )*pw[i] ) );
    }
    for( int i=1;i<=( n<<1 );++i ) {
        pw[i]=norm( pw[i]+pw[i-1] );
    }
    for( int i=1;i<=n;++i ) {
        preSum[i]=norm( norm( preSum[i-1]+norm( pw[i<<1]-pw[i] ) )+norm( pw[(i<<1)-1]-pw[i] ) );
    }
    for( int l=1,r;l<=n;l=r+1 ) {
        r=n/( n/l );
        int tmp=0;
        for( int exl=1,exr,m=n/l;exl<=m;exl=exr+1 ) {
            exr=m/( m/exl );
            tmp=norm( tmp+norm( LL( norm( exprt[exr]-exprt[exl-1] ) )*preSum[m/exl] ) );
        }
        ans=norm( ans+LL( norm( prt[r]-prt[l-1] ) )*tmp );
    }
    printf("%d\n",ans);
    return 0;
}