2 条题解

  • 2
    @ 2026-6-2 23:09:14

    注意到

    个位、十位等逐位单独算每位数字总和

    用d记位权,c=n/(d*10) 算出完整周期数

    每周期该位 0~9 累加和 45,

    先累加45*d*c;再取余数 r=n%(d*10) 处理零散尾数

    h=r/d是尾数段当前位最大值,

    先补 0~h-1 的和 h(h-1)/2*d*,

    再加 h 出现 r%d+1 次的贡献,

    逐位累加得到 1~n 全部数位和。

    当然位数DP也可以在这里我就不写了

    当然位数DP也可以在这里我就不写了

    当然位数DP也可以在这里我就不写了

    C++

    ll n;
    int main()
    {
        AC;
        int t; cin>>t;
        while(t--)
        {
            cin>>n;
            if(n<=0)
            {
                cout<<0<<'\n';
                continue;
            }
            ll sum=0;
            ll d=1;
            while(d<=n)
            {
                ll c=n/(d*10);
                sum+=45*d*c;
                ll r=n%(d*10);
                if(r>=d)
                {
                    ll h=r/d;
                    sum+=(h*(h-1)/2)*d;
                    sum+=(r%d+1)*h;
                }
                d*=10;
            }
            cout<<sum<<'\n';
        }
        return 0;
    }
    

    Python

    import sys
    def main():
        data=sys.stdin.read().split()
        t=int(data[0])
        for n in map(int,data[1:t+1]):
            if n<=0:
                print(0)
                continue
            s=0
            d=1
            while d<=n:
                c=n//(d*10)
                s+=45*d*c
                r=n%(d*10)
                if r>=d:
                    h=r//d
                    s+=h*(h-1)//2*d
                    s+=(r%d+1)*h
                d*=10
            print(s)
    if __name__=="__main__":
        main()
    
    
    • 1
      @ 2026-6-3 12:13:33

      这个题的一个解法是数位dp

      就是直接统计每个数位的出现频率再乘上本身

      同时有一个类似的题

      题目链接

      数位dp推荐都写成dfs形式的,因为递推式的数位dp是很难处理边界条件的

      刚开始学数位dp我建议把状态全部记上,

      比如常见的有第ii数位,前导零,上下界,

      代码如下

      #include <bits/stdc++.h>
      #define int long long
      using namespace std;
      int a[20], memo[16][16][2][2];
      int dfs(int p,int sm,bool ld,bool lm,int tg) {
          if (p==0) {
              return sm;
          }
          // 这里的变量名含义,p是目前处理到第几个数位,
          // sm是当前该数位(也就是tg)一共出现了几次
          // ld就是是否含有前导0
          // lm就是是否贴着上界
      
          //处理上下界的原因是因为枚举该位的数字
      
          if (memo[p][sm][ld][lm]!=-1) {
              return memo[p][sm][ld][lm];
          }
          int res=0;
          int up=lm ? a[p] : 9;
          //如果是27000,如果最高位不是2,下一位枚举的数字可以是0-9,但是如果最高位是2,下一位枚举的数字只能是0-7
          for (int i=0;i<=up;i++) {
              int nsm=sm+ ((i==tg)&&!(ld && i==0));
              //处理前导0的原因是因为数字不含前导0,比如9,如果有前导0计数可能是009,会干扰到0的计数
      
              res+=dfs(p-1,nsm,ld && (i==0),lm && (i==up),tg);
              //这里如果下一位依然是0并且仍然处于前导0状态仍然记作前导0,
              //
          }
          return memo[p][sm][ld][lm]=res;
          //返回的时候直接把状态存上
      }
      int sol(int n, int tg) {
          int len = 0;
          while (n) a[++len] = n % 10, n /= 10;
          memset(memo, -1, sizeof(memo));
          return dfs(len,0,true,true,tg);
      }
      signed main() {
          ios::sync_with_stdio(false); cin.tie(nullptr);
          int t;
          cin>>t;
          while(t--) {
              int n;
              cin>>n;
              int ans=0;
              for (int i=0;i<=9;i++) {
                  ans+=sol(n,i)*i;
              }
              cout<<ans<<'\n';
          }
      }
      

      然后恭喜你收到了一个TLE的代码

      因为这个地方的 TT 很大,并且 TT 不受 NN 的约束

      所以数位dp时我们不能memset的方式来解决初始化

      memset是因为我们我们在处理0-9每个数位以及每组数据之间都要重置一下,

      我们可以直接把状态全部存完并且加上vis数组规避memset

      #include <bits/stdc++.h>
      #define int long long
      using namespace std;
      int a[20];
      int memo[20][20][2][2][10];//加上数位的状态
      bool vis[20][20][2][2][10];
      int dfs(int p, int sm, bool ld, bool lm, int tg) {
          if (p == 0) {
              return sm;
          }
          if (vis[p][sm][ld][lm][tg]) {
              return memo[p][sm][ld][lm][tg];
          }
          int res = 0;
          int up = lm ? a[p] : 9;
          for (int i = 0; i <= up; i++) {
              int nsm = sm + ((i == tg) && !(ld && i == 0));
              res += dfs(p - 1, nsm, ld && (i == 0), lm && (i == up), tg);
          }
          if (!lm) {
              vis[p][sm][ld][lm][tg] = true;
              return memo[p][sm][ld][lm][tg] = res;
          }
          return res;
      }
      int sol(int n, int tg) {
          int len = 0;
          while (n) a[++len] = n % 10, n /= 10;
          return dfs(len, 0, true, true, tg);
      }
      signed main() {
          ios::sync_with_stdio(false); cin.tie(nullptr);
          int t;
          cin >> t;
          while(t--) {
              int n;
              cin >> n;
              int ans = 0;
              for (int i = 0; i <= 9; i++) {
                  ans += sol(n, i) * i;
              }
              cout << ans << '\n';
          }
          return 0;
      }
      

      其实数位dp写多了你就能发现这东西其实是轮椅

      • 1

      信息

      ID
      1166
      时间
      1000ms
      内存
      256MiB
      难度
      5
      标签
      (无)
      递交数
      22
      已通过
      5
      上传者