P11229 [CSP-J 2024] 小木棍(民间数据)dp题解

· · 题解

题外话

考场上这题花了一小时才调出来,终于把大样例过了,写篇题解庆祝一下🎉

思路

你们别说dp不能AC,我倒是成功把大样例过了,这也证实了我对这题的第一感觉。

第一步(MLE 0pts)

先设出状态转移数组:

node f[100010];//f[i]表示用i根火柴棒能拼成的最小数字
(node是我已经写好的100000位高精度)
这样肯定会 MLE ,而且状态转移过程中还可能 TLE

再写出状态转移方程:

f[j]=min(f[j],f[j-a[i]]*10+i);//a[i]表示i这个数字所需的火柴棒根数,下同
数位dp?继续往下看吧

第二步核心优化(100pts)

就直接核心优化了???
是的,就这么简单。可是为什么这么多人想不到呢?

带入一个情景吧

这个东西一年级都知道
给你几个数字,请问按什么顺序排才能使拼出的数最小呢?(0\le 给你的数字\le 9 且所有数字不都为0)

是不是这样:

1.找出最小不为0数字作为首位
2.从小到大排列剩下的数字

核心结论:只要知道0~9每个数字的个数,就能确定一个 唯一 的数字

所以高精度就变成了:

struct node{
int a[20],len;
}f[100017],t;//f[i]:用i根火柴能品出的最小数字(a[i]:这个数字有多少个i  (0 ≤i ≤ 9)  ) 

那么又出现了一个难点,这样一来,如何比较两个数的大小呢?

比较两数大小

其实很简单,就和刚刚带入的情境差不多:
1.比较两数的长度
2.比较两数的最小非0数字
3.从小到大比较剩下的数字

(输出参考刚刚带入的情境)

Code(有注释)

#include<bits/stdc++.h>
#define N 100000
using namespace std;
struct node{
    int a[20],len;
}f[100017],t;//f[i]:用i根火柴能品出的最小数字(a[i]:这个数字有多少个i  (0 ≤i < 10)  ) 
int ds[20]={6,2,5,5,4,5,6,3,7,6};
void print(node x){
    if(x.len==-1){//如果这个状态不存在 
        cout<<"-1\n";
        return ;
    }
    int l=1;
    while(x.a[l]==0)++l;
    cout<<l;
    //输出的第一步 
    x.a[l]--;
    for(int i=0;i<10;++i){//输出的第二步 
        while(x.a[i]--)cout<<i;
    }
    cout<<'\n';
}
int main(){
//  ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
    for(int i=1;i<=N;++i){
        f[i].len=-1;
    }
    f[2]={{0,1},1};
    f[3]={{0,0,0,0,0,0,0,1},1};
    f[4]={{0,0,0,0,1},1};
    f[5]={{0,0,1},1};
    f[6]={{0,0,0,0,0,0,1},1};
    f[7]={{0,0,0,0,0,0,0,0,1},1};
    //手动打出使用2、3、4、5、6、7根火柴棒所拼出的数字 
    for(int i=0;i<10;++i){
        for(int j=2;j<=N;++j){
            if(f[j].len==-1)continue;//如果这个状态不存在 
            t=f[j];
            t.a[i]++,t.len++;
            if(f[j+ds[i]].len==-1)f[j+ds[i]]=t;//如果这个状态不存在 
            if(f[j+ds[i]].len==t.len){//比较的第一步 
                int l=1;
                while(f[j+ds[i]].a[l]==0&&t.a[l]==0)++l;
                if(f[j+ds[i]].a[l]==0){
                    f[j+ds[i]]=t;
                    continue;
                }
                //比较的第二步 
                else if(t.a[l]>0){
                    for(int k=0;k<10;++k){//比较的第三步  
                        if(t.a[k]>f[j+ds[i]].a[k]){
                            f[j+ds[i]]=t;
                        }
                        if(t.a[k]!=f[j+ds[i]].a[k]){
                            break;
                        }
                    }
                }
            }
            else if(f[j+ds[i]].len>t.len){//比较的第一步 
                f[j+ds[i]]=t;
            }
        }
    }
    int t,n;
    cin>>t;
    while(t--){
        cin>>n;
        print(f[n]);
    }
    return 0;
}