ta 是邪恶的数位 dp,我的午饭终结者
(调着调着就忘记午饭了)
首先看一道例题
Example 01 [P2602 数字统计]
求 [l,r] 中每个数字出现了多少次
(1 <= l,r <= 1e12)
Solution
这题直接 for 肯定会 TLE
我们思考用一个 dp 数组
dp[i][j][k] 表示搜到第 i 位,保证最高位为 j,数字 k 的出现次数
(比如 dp[2][1][1] 就表示 [0,19] 有多少个 1)
显然区间比较难
我们借用前缀和思想
将 ans[l,r] 转化为 ans[1,r]-ans[1,l-1] 即可
首先得到一个方程
dp[i][j][k] 是 [j...00~j...99]中 k 的次数
所以 dp[i][j][k] = 所有 j 中 k 出现次数 + [000000000~999999999] 中 k 出现次数
= 所有 j 中 k 出现次数 + sum(dp[i-1][iterator][k]) [0~9]
现在我们只差第一部分怎么求
如果 j!=k 第一部分肯定是 0
否则就是 1e(i-1)
所以完整版的转移方程即为
dp[i][j][k]={ sum(dp[i-1][iterator][k]) [0~9] (j!=k)
{ 1e(i-1) + sum(dp[i-1][iterator][k]) [0~9] (j==k)
这个肯定是能求的
于是有如下 Code
const unsigned long long ten[16]={1,1e2,1e3,1e4,1e5,1e6,1e7,1e8,1e9,1e10,1e11,1e12,1e13,1e14,1e15};
unsigned long long number[16][11][11];
void _memset(){
for(int i=0;i<=9;i++)number[1][i][i]=1;
for(int i=2;i<=13;i++)for(int j=0;j<=9;j++)for(int k=1;k<=9;k++){
if(j==k)number[i][j][k]+=ten[i-1];
for(int it=0;it<=9;it++)number[i][j][k]+=number[i-1][it][k];
}
}
这部分非常简单
接下来考虑如何求解
比如说 6187
首先我们拆开每一位,变成 6 1 8 7 (C++ 语法入门)
接下来统计三种
# 01 位数与原数一样,高位较小的数 (for 循环使用)
ans+=[1000,1999]+[2000,2999]+[3000,3999]+[4000,4999]+[5000,5999]
# 02 位数较小,无前导零 (for 循环使用)
ans+=[0]+...+[9]+[10,19]+[20,29]+....+[900,999]
我们发现已经将 [0,5999] 的 ans 统计完了
还差 [6000,6187]
接下来我们直接将区间拆开
[6000,6099]
[6100,6109]+[6110,6119]+...+[6170,6179]
[6180]+[6181]+...+[6187]
我们再将每个区间劈开
k*[60]+[00,99]
k*[610]+[0,9]+k*[611]+[0,9].......
.....
(太抽象了)
一波玄学统计
劈开的后一半好算
可前一半怎么算呢
以[6100,6179]为例
明显 61 的次数 = 1e1*8
于是太离谱了,还是开码吧
#Warning \*注意拆分数字的部分! *\
\*最后一个数字不会计算所以最终需要 -1 ! *\
\*ten 数组不能直接 1e... 会CE! *\
Code
#include<bits/stdc++.h>
using namespace std;
typedef unsigned long long ULL;
const ULL ten[16]={1,10,100,1000,10000,100000,1000000,10000000,100000000,1000000000,10000000000,100000000000,1000000000000,10000000000000};
ULL number[16][11][11]={};
stack<int> st;
vector<int> digit;
void _memset(){
for(int i=0;i<=9;i++)number[1][i][i]=1;
for(int i=2;i<=12;i++)for(int j=0;j<=9;j++)for(int k=0;k<=9;k++){
if(j==k)number[i][j][k]+=ten[i-1];
for(int it=0;it<=9;it++)number[i][j][k]+=number[i-1][it][k];
}
}
ULL cut(ULL x,int k){
ULL ans=0;
int len=0;
digit.clear();
digit.push_back(-1);
while(x) st.push(x%10),x/=10,++len;
while(!st.empty()) digit.push_back(st.top()),st.pop();
for(int i=1;i<digit[1];i++) ans+=number[len][i][k];
for(int it_len=1;it_len<len;it_len++)for(int i=1;i<=9;i++) ans+=number[it_len][i][k];
for(int lock_len=1;lock_len<len;lock_len++){
for(int i=0;i<digit[lock_len+1];i++)ans+=number[len-lock_len][i][k];
for(int i=1;i<=lock_len;i++) if(digit[i]==k) ans+=digit[lock_len+1]*ten[len-lock_len-1];
}
return ans;
}
int main(){
_memset();
ULL lim,rim;
scanf("%llu%llu",&lim,&rim);
for(int i=0;i<=9;i++)printf("%llu ",cut(rim+1,i)-cut(lim,i));
return 0;
}
Question 01 [P2567 Windy Number]
定义任意相邻数字相差至少为 2 的数为 Windy Number
求 [l,r] 中 Windy Number 的个数
Solution 01
先不看题解推理一下
盲猜还是前缀和 minus
首先显然我们思考能否求出一段
[000 ... 0000,999 ... 9999] 之中的 Windy 个数
发现显然是可以的
设 Windy[i][j] 为 i 位数字首位为 j 的 Windy 个数
那么显然有转移
Windy[i][j]=sum(Windy[i-1][k]) (0<=k<=j-2) (j+2<=k<=9)
而边界条件 Windy[1][i]=1 (0<=i<=9)
非常的 Nice
Code
void Wind(){
for(int i=0;i<=9;i++)Windy[1][i]=1;
for(int len=2;len<=LIM;len++){
for(int num=0;num<=9;num++){
for(int it=0;it<=num-2;it++)Windy[len][num]+=Windy[len-1][it];
for(int it=num+2;it<=9;it++)Windy[len][num]+=Windy[len-1][it];
}
}
}
发现这题貌似也可以拆开
仍然是 Example 中的三部分
(以下引用 Example 中的 Solution)
Sample 6112
# 01 位数与原数一样,高位较小的数 (for 循环使用)
ans+=[1000,1999]+[2000,2999]+[3000,3999]+[4000,4999]+[5000,5999]
# 02 位数较小,无前导零 (for 循环使用)
ans+=[0]+...+[9]+[10,19]+[20,29]+....+[900,999]
# 03 其余的几个数
做了几道题大家其实也发现了
这部分才是难点
现在思考如何求 [6000,6112] 中的 Windy 数
思考还拆成原来的 [6000,6099] [6100,6109] [6110] [6111] [6112]
每一次判断当前枚举位和上一位是否合法
合法就加上
特别的如果当前最大位不合法那就直接 return
so 6110 以后都不用算了
记得一定要加一!
(貌似这是我第一个不看题解 AC 的蓝题)
(其实难度之有绿的样子)
Code
#include<bits/stdc++.h>
using namespace std;
const int LIM=10;
typedef unsigned long long ULL;
ULL lim,rim,Windy[LIM+5][13];
void Wind(){
for(int i=0;i<=9;i++)Windy[1][i]=1;
for(int len=2;len<=LIM;len++){
for(int num=0;num<=9;num++){
for(int it=0;it<=num-2;it++)Windy[len][num]+=Windy[len-1][it];
for(int it=num+2;it<=9;it++)Windy[len][num]+=Windy[len-1][it];
}
}
}
stack<int> st;
vector<int> digit;
ULL Windy_Num(ULL pos){
ULL ans=0;
int len=0;
digit.clear();
digit.push_back(-1);
while(pos) st.push(pos%10),pos/=10,++len;
while(!st.empty()) digit.push_back(st.top()),st.pop();
for(int i=1;i<digit[1];i++)ans+=Windy[len][i];
for(int i=1;i<len;i++) for(int num=1;num<=9;num++)ans+=Windy[i][num];
for(int lock_len=1;lock_len<len;lock_len++){
for(int num=0;num<digit[lock_len+1];num++){
if(abs(digit[lock_len]-num)<2)continue;
ans+=Windy[len-lock_len][num];
}
if(abs(digit[lock_len+1]-digit[lock_len])<2)return ans;
}
return ans;
}
int main(){
Wind();
scanf("%llu%llu",&lim,&rim);
printf("%llu",Windy_Num(rim+1)-Windy_Num(lim));
return 0;
}
Question 02 [ACP2195 Amount Of Degrees]
求一段区间 [L,R] 内满足下列条件的整数个数:
这个数恰好等于 K 个互不相等的 B 的整数次幂之和。
1<=L<=R<=INT_MAX
Solution
再试一次不看题解拿 AC
首先这题和原来有一点不一样
我们发现这个到最后其实就是把 L R 转化成一个 B 进制数
并且其中刚好有 K 个一
一波玄学思考之后我们发现在拆开每一位的时候搞点花活就可以了
接下来设 dp[i][k] 为 [0000000-???????] 中有k个一的个数
则转移不难
DP[i][k]=DP[i-1][k]*(B-1)+DP[i-1][k-1]
接下来就是拆数字
还是以 6112 为例
(B=10)
[0000,0999](k) =[000,999](k)
[1000,1999](k) =[000,999](k-1)
[2000,2999](k) =[000,999](k)
[3000,3999](k) =[000,999](k)
[4000,4999](k) =[000,999](k)
[5000,5999](k) =[000,999](k)
[6000,6099](k) =[00,99](k)
[6100,6109](k) =[0,9](k-1)
[6110]
[6111]
[6112]
然后作者开码写出了下面的玩意
Code Warning:并非正确代码
#include<bits/stdc++.h>
using namespace std;
typedef unsigned long long ULL;
const int LIM=70;
int K,B;
ULL Pow[LIM+3],dp[LIM+3][LIM+3],L,R;
void MemSet(){
Pow[0]=1;
for(int i=1;i<=LIM;i++)Pow[i]=Pow[i-1]*10,dp[0][0]=1;
for(int i=1;i<=LIM;i++){
for(int j=0;j<=i;j++){
dp[i][j]=dp[i-1][j]*(B-1);
if(j)dp[i][j]+=dp[i-1][j-1];
}
}
}
stack<int>st;
vector<int> k;
int len;
ULL ans(ULL x){
k.clear(),k.push_back(0);
while(x) st.push(x%B),x/=B;
while(!st.empty()) k.push_back(st.top()),st.pop();
len=k.size()-1;
ULL sum=0;
int cnt=0,r;
for(int l=0;l<len;l++){
for(int i=0;i<k[l+1];i++){
r=K-cnt;
if(i==1)--r;
if(r<0)continue;
sum+=dp[len-l-1][r];
}
if(k[l+1]==1)++cnt;
if(cnt>K)break;
}
return sum;
}
int main(){
scanf("%llu%llu%d%d",&L,&R,&K,&B);
MemSet();
printf("%llu\n",ans(R+1)-ans(L));
}
submit,WA 60pts
哪里错了呢?
再看一眼题目
WC!其余位数根据题意必须为 0
(此处并非有意坑人,实属作者亲身经历)
转移方程改吧改吧
DP[i][k]=DP[i-1][k]+DP[i-1][k-1]
最后拆数的时候加个判断不满足最高位为一直接 break
AC Code
#include<bits/stdc++.h>
using namespace std;
typedef unsigned long long ULL;
const int LIM=70;
int K,B;
ULL dp[LIM+3][LIM+3]={},L,R;
void MemSet(){
dp[0][0]=1;
for(int i=1;i<=LIM;i++){
for(int j=0;j<=i;j++){
dp[i][j]=dp[i-1][j];
if(j)dp[i][j]+=dp[i-1][j-1];
}
}
}
stack<int>st;
vector<int> k;
int len;
ULL ans(ULL x){
k.clear(),k.push_back(0);
while(x) st.push(x%B),x/=B;
while(!st.empty()) k.push_back(st.top()),st.pop();
len=k.size()-1;
ULL sum=0;
int cnt=0,r;
for(int l=0;l<len;l++){
for(int i=0;i<k[l+1]&&i<2;i++){
r=K-cnt;
if(i==1)--r;
if(r<0)continue;
sum+=dp[len-l-1][r];
}
if(k[l+1]==1)++cnt;
if(cnt>K)break;
if(k[l+1]>1)break;
}
return sum;
}
int main(){
scanf("%llu%llu%d%d",&L,&R,&K,&B);
MemSet();
printf("%llu\n",ans(R+1)-ans(L));
}
Question 03 [ACP2196 数字游戏]
求 [L,R] 直间满足各数位从左到右单调不降的数的个数
有多组测试数据。每组只含两个数字
Solution
Windy 数简单版本
DP[i][j] 表示 [j00000000,j99999999] 中满足条件的个数
转移方程 DP[i][j]=sum{DP[i-1][k]} (k>=j)
边界设 Dp[1][j]=1
其余和 Windy 数基本一样不细写了,看代码吧
(Dalao:无良作者,举报!)
Tips:
1. 记得是多测!
2. 如果下一位小于当前位一定要 Break! 这不是可有可无的剪枝!
因为如果不这样譬如 613 614 这样的答案也会计入从而让你喜提 WA 0pts
(LOJ 单测试点让人暖心)
Code
#include<bits/stdc++.h>
using namespace std;
typedef unsigned long long ULL;
const int LIM=18;
ULL dp[LIM+3][10];
void MemSet(){
for(int i=0;i<=9;i++)dp[1][i]=1;
for(int l=2;l<=LIM;l++)for(int i=0;i<=9;i++)for(int j=i;j<=9;j++)dp[l][i]+=dp[l-1][j];
}
stack<int> st;
vector<int> k;
int len;
ULL ans(ULL x){
k.clear(),k.push_back(0);
while(x)st.push(x%10),x/=10;
while(!st.empty())k.push_back(st.top()),st.pop();
len=k.size()-1;
ULL sum=0;
for(int l=0;l<len;l++){
if(k[l+1]<k[l])break;
for(int i=k[l];i<k[l+1];i++){
sum+=dp[len-l][i];
}
}
return sum;
}
int main(){
ULL L,R;
MemSet();
while(scanf("%llu%llu",&L,&R)!=EOF) printf("%llu\n",ans(R+1)-ans(L));
return 0;
}
Question 04 [ACP2198 取模数]
由于科协里最近真的很流行数字游戏,某人又命名了一种取模数,这种数字必须满足各位数字之和 mod N 为 0。
现在大家又要玩游戏了,指定一个整数闭区间 [a,b],问这个区间内有多少个取模数。
Solution
思考一下发现可以设 DP[i][j][k] 表示共有 i 位,首位为 j,模 N 为 k 的个数
转移也不难
DP[i][j][k]=Sum{DP[i-1][iterator][k-i]}
这个大致是对的
但不要忘了取模和处理负数
边界也好写:dp[1][j][j%N]=1 即可
多测不清空,爆零两行泪!
Code
#include<bits/stdc++.h>
using namespace std;
typedef unsigned long long ULL;
const int LIM=17,M=108;
ULL dp[LIM+4][10][M+4];
int K;
void MemSet(){
for(int i=0;i<=LIM;i++)for(int j=0;j<=9;j++)for(int tmp=0;tmp<=M;tmp++)dp[i][j][tmp]=0;
for(int i=0;i<=9;i++)dp[1][i][i%K]=1;
for(int l=2;l<=LIM;l++)for(int i=0;i<=9;i++)for(int it=0;it<K;it++)for(int j=0;j<=9;j++)dp[l][i][it]+=dp[l-1][j][((it-i)%K+K)%K];
}
vector<int> k;
stack<int> st;
ULL ans(ULL x){
k.clear(),k.push_back(0);
while(x)st.push(x%10),x/=10;
while(!st.empty())k.push_back(st.top()),st.pop();
int len=k.size()-1,cnt=k[1]%K;
ULL sum=0;
for(int l=1;l<len;l++)for(int i=1;i<=9;i++)sum+=dp[l][i][0];
for(int i=1;i<k[1];i++)sum+=dp[len][i][0];
for(int l=1;l<len;l++){
for(int i=0;i<k[l+1];i++)sum+=dp[len-l][i][(K-cnt)%K];
cnt=(cnt+k[l+1])%K;
}
return sum;
}
int main(){
ULL L,R;
while(scanf("%llu%llu%d",&L,&R,&K)!=EOF){
MemSet();
printf("%llu\n",ans(R+1)-ans(L));
}
return 0;
}
Question 05 [ACP2199 不要62]
求给定的 [L,R] 之间不含有 62 连号或含有 4 的数目
多测 0 < L <= R <= 1e7
Solution
1e7 跑 O(n log n)
你 LOJ 神机差不多能过
但 ACC 肯定不行
还是正经写数位 DP 吧
这题和以前见得 数位 DP 不太一样
正难则反
找不吉利的有多少用总数减去即可
思考如果当前位是 4 直接 += 1e...
如果当前位是 6 就把除了下一位是二的不吉利数字加上
再加上二下一位所有的数字
否则直接求 sum 即可
设 DP[i][j] 为已经来到第 i 位首位为 j
则有以下方程
DP[i][j]={ 10^(i-1) i==4
{ sum{dp[i-1][除了2]}+10^(i-2) i==6
{ sum{dp[i-1][0~9] 其余情况
剩下的已经很熟悉了我就不 BB 了
开码!
Code
#include<bits/stdc++.h>
using namespace std;
const int LIM=7;
int dp[LIM+5][10],ten[LIM+5];
void MemSet(){
dp[1][4]=1,ten[0]=1;
for(int i=1;i<=LIM;i++)ten[i]=ten[i-1]*10;
for(int l=2;l<=LIM;l++)for(int i=0;i<=9;i++){
if(i==4){dp[l][i]=ten[l-1];continue;}
for(int it=0;it<=9;it++)dp[l][i]+=dp[l-1][it];
if(i==6)dp[l][i]=dp[l][i]-dp[l-1][2]+ten[l-2];
}
}
stack<int> st;
vector<int> k;
int ans(int x){
k.clear(),k.push_back(0);
while(x)st.push(x%10),x/=10;
while(!st.empty())k.push_back(st.top()),st.pop();
int len=k.size()-1,sum=0;
bool flag=0;
for(int l=1;l<len;l++)for(int i=1;i<=9;i++)sum+=dp[l][i];
for(int i=1;i<k[1];i++)sum+=dp[len][i];
for(int l=1;l<len;l++){
if(k[l]==4||(k[l-1]==6&&k[l]==2))flag=1;
for(int i=0;i<k[l+1];i++){
if(flag)sum+=ten[len-l-1];
else if(k[l]==6&&i==2)sum+=ten[len-l-1];
else sum+=dp[len-l][i];
}
}
return sum;
}
int main(){
int l,r;
MemSet();
while(true){
scanf("%d%d",&l,&r);
if(l+r==0)return 0;
printf("%d\n",r-l+1-ans(r+1)+ans(l));
}
}
Question 06 [ACP2200 恨7不成妻]
如果一个整数符合下面三个条件之一,那么我们就说这个整数和 7 有关:
1. 整数中某一位是 7
2. 整数的每一位加起来的和是 7 的整数倍
3. 这个整数是 7 的整数倍
求 [L,R] 内和 7 无关的数字的平方和模 1e9+7 的值。
多测 0 < L <= R <= 1e18
Solution
思考发现限制 1,2 就是上一题和上上题
限制 3 怎么搞?
平方和又怎么搞?
[1,n] 平方和经查是 n(n+1)(2n+1)/6
限制 3 经过思考(看了眼题解)得到可以在 DP 数组中加一维记录然后就可以了 QwQ
设 DP[i][j][k][l] 为 [共有 i 位 & 首位为 j & 除 7 余 k & 所有数字加和模 7 得 l] 的数字平方和(Look here!)
但这就产生了一个很尴尬的事情
就是限制 1 不太好搞
我们不好给他放进DP数组里
思考上一题 Code 里的 Flag 操作
为了避免写一些恶心无比的容斥原理
我们正难则反的思路也被放弃
而是直接在 DP 的时候就把七跳过
最后遇到七也直接舍弃后面的答案 Break 掉
可是事情到这里并未结束
现在思考我们已经知道 a^2+b^2+c^2+...
如何求出 (a+K)^2+... 呢?
古希腊先贤柏拉图曾说过:巧妇饿不死瞎家雀
所以我们展开原式得到 a^2+b^2+c^2+...+2aK+2bK+2cK+...+K^2+K^2+K^2+...
设 Qsum 为a^2+b^2+c^2+...,即所有数的平方和
设 sum 为a+b+c+d...,即所有数的和
设 cnt 为这些数的个数 (比如 a,b,c cnt=3)
一波大力落奇迹之后 得到
原式=Qsum+2*sum*K+cnt*K*K
其中 Qsum 和 K 为已知
cnt 也容易维护
思考 sum 咋整
也不难,直接 +=cnt*K 即可
Warning!
Warning!
Warning!
此处需要先更新 Qsum 再更新 sum!
(MarkDown 的卡了我三个小时)
所以我们可以写一下DP转移方程了
K=10^(i-1)*j
iterator=[0,6]+[8,9]
DP_cnt[i][j][k][l]=SUM{DP_cnt[i-1][iterator][((k-10^(i-1)*j)%7+7)%7][((l-i)%7+7)%7]}
DP_sum[现在(ijkl)]=SUM{DP_sum[同上]}+K*DP_cnt[现在]
DP_Qsum[现在]=SUM{DP_Qsum[同上]}+2*DP_sum[现在]*K+DP_cnt[现在]*K*K
拆数之类的基本和前面一样不做赘述
然后就没有了
开码!
Code
#include<bits/stdc++.h>
#define loop(i,l,r) for(int i=l;i<=r;i++)
using namespace std;
typedef unsigned long long ULL;
const ULL MOD=1e9+7;
const int LIM=18;
ULL Q[LIM+5][10][7][7],S[LIM+5][10][7][7],C[LIM+5][10][7][7],ten[LIM+5];
void MemSet(){
ten[0]=1;
loop(i,1,LIM) ten[i]=ten[i-1]*10;
loop(i,0,9){
if(i==7)continue;
C[1][i][i%7][i%7]=1;
S[1][i][i%7][i%7]=i;
Q[1][i][i%7][i%7]=i*i;
}
ULL K;
int aa,bb,kk;
loop(i,2,LIM) loop(j,0,9){
if(j==7)continue;
K=ten[i-1]*j;kk=K%7;K%=MOD;
loop(a,0,6) loop(b,0,6){
aa=(a-kk+70)%7,bb=(b-j+70)%7;
loop(it,0,9){
if(it==7)continue;
C[i][j][a][b]=(C[i][j][a][b]+C[i-1][it][aa][bb])%MOD;
S[i][j][a][b]=(S[i][j][a][b]+S[i-1][it][aa][bb])%MOD;
Q[i][j][a][b]=(Q[i][j][a][b]+Q[i-1][it][aa][bb])%MOD;
}
Q[i][j][a][b]=(S[i][j][a][b]*K%MOD*2%MOD+C[i][j][a][b]*K%MOD*K%MOD+Q[i][j][a][b])%MOD;
S[i][j][a][b]=(C[i][j][a][b]*K%MOD+S[i][j][a][b])%MOD;
}
}
}
vector<int> k;
stack<int> st;
inline ULL ans(ULL x){
k.clear(),k.push_back(0);
while(x)st.push(x%10),x/=10;
while(!st.empty())k.push_back(st.top()),st.pop();
int len=k.size()-1,tot=0,tot2=0;
ULL sum=0,cnt=0,tmp;
loop(l,0,len-1){
loop(i,0,k[l+1]-1){
if(i==7)continue;
loop(a,0,6) {
loop(b,0,6){
if((tot2+a)%7==0)continue;
if((tot+b)%7==0)continue;
tmp=(S[len-l][i][a][b]*cnt%MOD*2%MOD+C[len-l][i][a][b]*cnt%MOD*cnt%MOD+Q[len-l][i][a][b])%MOD;
sum=(tmp+sum)%MOD;
}
}
}
if(k[l+1]==7)break;
cnt+=ten[len-l-1]%MOD*k[l+1]%MOD;
tot=(tot+k[l+1])%7;
tot2=(tot2+(k[l+1]%7)*(ten[len-l-1]%7))%7;
}
return sum;
}
int main(){
int T;
ULL L,R;
MemSet();
scanf("%d",&T);
while(T--){
scanf("%llu%llu",&L,&R);
printf("%llu\n",(ans(R+1)+MOD-ans(L))%MOD);
}
return 0;
}
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 清华大学推出第四讲使用 DeepSeek + DeepResearch 让科研像聊天一样简单!
· 推荐几款开源且免费的 .NET MAUI 组件库
· 实操Deepseek接入个人知识库
· 易语言 —— 开山篇
· Trae初体验