dp训练记录 2025.1
Min-Fund Prison (Medium)
https://www.gxyzoj.com/d/hzoj/p/CF1970G2
显然,要加的边数就是联通快的个数-1,所以只需要让x,y尽量接近即可
因为要分成两个联通快,所以删掉的边一定是割边或加上的边
有一个性质,经过边双缩点后的图一定是一个森林,记深度较大的节点为u,x子树大小为
考虑dp,因为算平方很难统计,考虑可行性dp
设
显然的,如果没有断开,直接连到对应联通快即可,有:
连到第二个集合
连到第一个集合
对于断开已知边,必然会分成两部分,一部分连在第一个联通快,另一部分在第二个
最后看每一个j值是否可行即可
初始状态为
点击查看代码
#include<cstdio>
#include<algorithm>
#define ll long long
using namespace std;
int n,m,T,head[305],edgenum,head1[305],edgenum1,cnt;
ll c;
struct edge{
int to,nxt;
}e[606],e1[606];
void add_edge(int u,int v)
{
e[++edgenum].nxt=head[u];
e[edgenum].to=v;
head[u]=edgenum;
}
void add_edge1(int u,int v)
{
e1[++edgenum1].nxt=head1[u];
e1[edgenum1].to=v;
head1[u]=edgenum1;
}
int s[305],dfn[305],low[305],idx,top,ecnt,id[305],siz[305];
bool bri[305];
void tarjan(int i,int in_edge)
{
dfn[i]=low[i]=++idx;
s[++top]=i;
for(int u=head[i];u;u=e[u].nxt)
{
int j=e[u].to;
if(!dfn[j])
{
tarjan(j,u);
low[i]=min(low[i],low[j]);
if(low[j]>dfn[i])
{
bri[u]=bri[u^1]=1;
}
}
else if(u!=(in_edge^1))
{
low[i]=min(low[i],dfn[j]);
}
}
if(dfn[i]==low[i])
{
ecnt++;
int v=0;
while(v!=i)
{
v=s[top];
top--;
id[v]=ecnt;
siz[ecnt]++;
}
}
}
int rt[305],st[305];
bool vis[305],dp[305][305][2];
void dfs(int u,int root)
{
vis[u]=1;
rt[u]=root;
for(int i=head1[u];i;i=e1[i].nxt)
{
int v=e1[i].to;
if(vis[v]) continue;
dfs(v,root);
// printf("%d %d %d %d\n",u,v,siz[u],siz[v]);
siz[u]+=siz[v];
}
}
int main()
{
scanf("%d",&T);
while(T--)
{
scanf("%d%d%lld",&n,&m,&c);
for(int i=1;i<=n;i++)
{
head[i]=head1[i]=dfn[i]=low[i]=0;
siz[i]=vis[i]=bri[i]=0;
}
top=cnt=idx=ecnt=0;
edgenum=1,edgenum1=0;
for(int i=1;i<=m;i++)
{
int u,v;
scanf("%d%d",&u,&v);
add_edge(u,v);
add_edge(v,u);
}
for(int i=1;i<=n;i++)
{
if(!dfn[i])
{
tarjan(i,0);
}
}
for(int i=1;i<=n;i++)
{
// printf("%d ",id[i]);
for(int j=head[i];j;j=e[j].nxt)
{
int v=e[j].to;
if(id[i]!=id[v])
{
// printf("%d %d\n",i,v);
add_edge1(id[i],id[v]);
}
}
}
if(ecnt==1)
{
printf("-1\n");
continue;
}
cnt=0;
// for(int i=1;i<=ecnt;i++)
// {
// printf("%d ",siz[i]);
// }
for(int i=1;i<=ecnt;i++)
{
if(!vis[i])
{
dfs(i,i);
st[++cnt]=i;
}
}
// printf("%d\n",ecnt);
for(int i=1;i<=cnt;i++)
{
for(int j=0;j<=n;j++)
{
dp[i][j][0]=dp[i][j][1]=0;
}
}
dp[0][0][0]=1;
c=(cnt-1)*c;
for(int i=1;i<=cnt;i++)
{
// printf("%d ",st[i]);
int srt=siz[st[i]];
for(int j=0;j<=n;j++)
{
if(j>=srt)
{
dp[i][j][0]|=dp[i-1][j-srt][0];
dp[i][j][1]|=dp[i-1][j-srt][1];
}
dp[i][j][0]|=dp[i-1][j][0];
dp[i][j][1]|=dp[i-1][j][1];
for(int k=1;k<=ecnt;k++)
{
if(rt[k]!=st[i]||k==st[i]) continue;
if(j>=srt-siz[k]) dp[i][j][1]|=dp[i-1][j-srt+siz[k]][0];
if(j>=siz[k]) dp[i][j][1]|=dp[i-1][j-siz[k]][0];
}
}
}
ll ans=1e17;
for(int i=0;i<=n;i++)
{
if(dp[cnt][i][0]||dp[cnt][i][1])
{
ans=min(ans,1ll*i*i+1ll*(n-i)*(n-i));
// printf("%d %d\n",dp[cnt][i][0],dp[cnt][i][1]);
}
}
printf("%lld\n",ans+c);
}
return 0;
}
Construct Tree
https://www.gxyzoj.com/d/hzoj/p/4477
先将l按从小到大排序,如果
因为最优当前情况一定是将所有多余的边连在同一个点上,所以如果有一种情况能包含最长边,就一定有解
所以设
另外一种情况是虽然无法用到最长边,但是可以将直径分成两部分,满足两部分长度均大于
设
此时,判断
因为时间复杂度为
点击查看代码
#include<cstdio>
#include<bitset>
#include<algorithm>
using namespace std;
int T,a[2005],f[2005],n,d;
bitset<2005> b[2001];
int main()
{
scanf("%d",&T);
while(T--)
{
scanf("%d%d",&n,&d);
for(int i=0;i<=d;i++)
{
b[i]=0,f[i]=0;
}
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
}
sort(a+1,a+n+1);
if(a[n]+a[n-1]>d)
{
printf("No\n");
continue;
}
f[0]=1;
for(int i=1;i<n;i++)
{
for(int j=d-a[n];j>=a[i];j--)
{
f[j]|=f[j-a[i]];
}
}
if(f[d-a[n]])
{
printf("Yes\n");
continue;
}
b[0][0]=1;
for(int i=1;i<=n;i++)
{
for(int j=d;j>=a[i];j--)
{
b[j]|=(b[j]<<a[i]);
b[j]|=b[j-a[i]];
}
for(int j=0;j<a[i];j++)
{
b[j]|=(b[j]<<a[i]);
}
}
bool fl=0;
for(int i=a[n];i<=d;i++)
{
int j=d-i;
if(j<a[n]) break;
if(b[i][j])
{
fl=1;
}
}
if(fl) printf("Yes\n");
else printf("No\n");
}
return 0;
}
Light Bulbs (Hard Version)
https://www.gxyzoj.com/d/hzoj/p/4479
人类智慧题(我的秒随机数被卡了!!!),CF题解说这是异或哈希
我们可以将所有从1到i每个数出现次数均为偶数的点标记,两点之间的定义为偶区间(不包括左,包括右)
那么偶区间每个数出现次数均为偶数,必然可以通过改变一个状态满足条件
因为如果这个区间长度大于二的情况下,左端点和它对应的数之间必然存在别的数,不然可以分成功更小的区间
而其中的数同理,所以,必然可以通过改变左端点使其满足条件
找左端点是容易的,但是如何统计方案数
在一段区间中,如果其中的一段所有数字的个数均为偶数,因为没有相同的数,那么改变这个区间的数必然不能影响到其他部分
所以,应当除去这些段,才是一个偶区间的总方案数
考虑如何去除这些段,因为异或有一个性质,当一个数疑惑两次同一个数时,值不变
所以可以先求出异或前缀和,假设当前点为0,则就是偶区间的右端点
因为每个数只会出现两次,所以同一个值至多出现两次,记录每一个值最后出现的位置
此时,假设当前点为i,值为x,另一个为x的点在j,记为
所以,每次直接遍历
点击查看代码
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int mod=998244353;
int n,T,a[400004];
ll w[400005],s[400005];
ll ans;
map<ll,int> lst;
mt19937_
ll get_rnd()
{
ll x=0;
while(!x)
{
x=rnd();
}
return x;
}
int main()
{
scanf("%d",&T);
while(T--)
{
// srand((unsigned)time(0));
ans=1;
lst.clear();
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
w[i]=get_rnd();
}
ll cnt=0;
for(int i=1;i<=n*2;i++)
{
scanf("%d",&a[i]);
s[i]=s[i-1]^w[a[i]];
lst[s[i]]=i;
if(!s[i]) cnt++;
}
for(int i=0;i<n*2;i++)
{
if(s[i]) continue;
int j=i+1,res=1;
while(s[j])
{
j=lst[s[j]]+1;
res++;
}
ans=ans*res%mod;
// printf("%d ",res);
}
printf("%lld %lld\n",cnt,ans);
}
return 0;
}
Twin Friends
https://www.gxyzoj.com/d/hzoj/p/4480
其实是简单题
其实A串的顺序没有很大关系,主要看B串的对应情况
因为分为两种情况,直接对应和+1对应,所以设
此时,暴力的转移方程为
因为这样重复的相加过多,会T,前缀和优化即可
点击查看代码
#include<cstdio>
#include<string>
#include<iostream>
#include<algorithm>
#define ll long long
using namespace std;
const int mod=998244353;
int n,m,cnta[30],cntb[30];
ll fac[200005],inv[200005],s[200005],f[27][200005];
string a,b;
ll qpow(ll x,int y)
{
ll res=1;
while(y)
{
if(y&1) res=res*x%mod;
x=x*x%mod;
y>>=1;
}
return res;
}
ll C(int x,int y)
{
if(x>y) return 0;
return fac[y]*inv[x]%mod*inv[y-x]%mod;
}
int main()
{
scanf("%d%d",&n,&m);
cin>>a;
cin>>b;
// cout<<b;
for(int i=0;i<a.size();i++)
{
cnta[a[i]-'A'+1]++;
}
for(int i=0;i<b.size();i++)
{
cntb[b[i]-'A'+1]++;
}
fac[0]=1;
for(int i=1;i<=n;i++)
{
fac[i]=fac[i-1]*i%mod;
}
inv[n]=qpow(fac[n],mod-2);
for(int i=n-1;i>=0;i--)
{
inv[i]=inv[i+1]*(i+1)%mod;
}
ll sum=fac[n];
for(int i=0;i<=cntb[1];i++)
{
s[i]=1;
}
// for(int i=1;i<=26;i++)
// {
// printf("%d ",cntb[i]);
// }
for(int i=1;i<=26;i++)
{
// printf("%d %d\n",cnta[i],cntb[i]);
for(int j=max(0,cnta[i]-cntb[i]);j<=min(cntb[i+1],cnta[i]);j++)
{
f[i][j]=s[cntb[i]-cnta[i]+j]*C(j,cnta[i])%mod;
// printf("%d %d %lld\n",i,j,f[i][j]);
}
s[0]=f[i][0];
for(int j=1;j<=cntb[i+1];j++)
{
s[j]=(s[j-1]+f[i][j])%mod;
}
sum=sum*inv[cnta[i]]%mod;
}
printf("%lld",s[0]*sum%mod);
return 0;
}
One-X
https://www.gxyzoj.com/d/hzoj/p/CF1905E
对于一个节点x,假设它左区间有a个叶子,右区间有b个叶子,它对答案的贡献就是
所以第一要考虑区间的大小和个数,第二要考虑编号
先证明一个结论,每一层至之多有两种长度
假设当前根长度为
两个之多相差一,要么相等,要么一奇一偶,对于一奇一偶的情况,先将偶数除以2,此时,将奇数家去这个值,必然只差一,以此类推,显然
接下来考虑长度,可以统计两个量,区间个数和编号和
因为左边是直接乘2,所以转移时乘2就是新的编号和,右区间要+1,因为每个区间都能分裂,座椅最后应在乘2的基础上+区间个数
注意,当区间长度均为1时,终止
点击查看代码
#include<cstdio>
#define ll long long
using namespace std;
const int mod=998244353;
int T;
ll n,a,b,cnta,cntb,suma,sumb,ans;
ll qpow(ll x,ll y)
{
ll res=1;
while(y)
{
if(y&1) res=res*x%mod;
x=x*x%mod;
y>>=1;
}
return res;
}
int main()
{
scanf("%d",&T);
while(T--)
{
scanf("%lld",&n);
a=n,cnta=1,suma=1;
b=cntb=sumb=0;
ll a1,b1,cnta1,cntb1,suma1,sumb1;
ans=0;
while(1)
{
if(a==1) break;
a1=b1=cnta1=cntb1=suma1=sumb1=0;
ll x=(a+1)/2;
a1=x,cnta1=cnta,suma1=suma*2;
ll y=a/2;
if(y==a1) cnta1+=cnta,suma1+=suma*2+cnta;
else b1=y,cntb1=cnta,sumb1=suma*2+cnta;
ans=(ans+(qpow(2,x)-1)*(qpow(2,y)-1)%mod*suma%mod)%mod;
if(b>1)
{
x=(b+1)/2;
if(x==a1) cnta1+=cntb,suma1+=sumb*2;
else b1=x,cntb1+=cntb,sumb1+=sumb*2;
y=b/2;
if(y==a1) cnta1+=cntb,suma1+=sumb*2+cntb;
else b1=y,cntb1+=cntb,sumb1+=sumb*2+cntb;
ans=(ans+(qpow(2,x)-1)*(qpow(2,y)-1)%mod*sumb%mod)%mod;
}
else if(b==1) ans=(ans+sumb)%mod;
a=a1,cnta=cnta1%mod,suma=suma1%mod;
b=b1,cntb=cntb1%mod,sumb=sumb1%mod;
}
printf("%lld\n",(ans+suma)%mod);
}
return 0;
}
Jellyfish and EVA
https://www.gxyzoj.com/d/hzoj/p/4253
注意题面,说的是单项边,而且只能连向编号更大的点,所以是DAG,,可以dp
挂个之前写过的:https://www.cnblogs.com/wangsiqi2010916/p/18077354
Another MEX Problem
https://www.gxyzoj.com/d/hzoj/p/4481
因为异或和mex不好转移,考虑可行性dp
设
但是暴力是
因为区间很多,只考虑什么区间是有贡献的
假设区间[l,r]是优的,那么必然存在当前的mex值去掉左或右的一个会变小,不然小区间更优
最后合法的区间很少,可以暴力枚举
点击查看代码
#include<cstdio>
#include<vector>
using namespace std;
int T,n,a[5005],mex[5005][5005];
bool vis[5005],f[5005][10005];
vector<int> v[5005];
int main()
{
scanf("%d",&T);
while(T--)
{
scanf("%d",&n);
for(int i=0;i<=n;i++)
{
for(int j=0;j<=8191;j++)
{
f[i][j]=0;
}
}
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
}
for(int i=1;i<=n;i++)
{
int now=0;
for(int j=0;j<=n;j++)
{
vis[j]=0;
}
for(int j=i;j<=n;j++)
{
vis[a[j]]=1;
while(vis[now]) now++;
mex[i][j]=now;
}
}
for(int i=1;i<=n;i++)
{
for(int j=i;j<=n;j++)
{
if((i==j)||(mex[i][j-1]!=mex[i][j]&&mex[i][j]!=mex[i+1][j]))
{
v[i-1].push_back(j);
}
}
}
f[0][0]=1;
for(int i=0;i<=n;i++)
{
for(int j=0;j<=8191;j++)
{
if(!f[i][j]) continue;
f[i+1][j]|=f[i][j];
for(int k=0;k<v[i].size();k++)
{
int x=v[i][k];
f[x][mex[i+1][x]^j]|=f[i][j];
}
}
}
int ans=0;
for(int i=0;i<=8192;i++)
{
if(f[n][i]) ans=i;
}
printf("%d\n",ans);
}
return 0;
}
Travel Plan
https://www.gxyzoj.com/d/hzoj/p/CF1868C
因为是路径最大值,而且m不大,考虑枚举
假设一条路径的长度为x,那么最大值小于等于y的方案数为
而要恰好等于y,减去小于等于y-1的即可
接下来考虑如何统计路径长度对应的个数,显然的,,每条路经都可以拆成两条链,假设一条长x,一条长y
对于一个节点,假设它深度为
如果是满二叉树,直接看所对应层及以上的节点数即可
但是在最后一层有节点的情况下,考虑哪些地方会多出解
首先,记这一层的节点数为t,那么会多有
对于剩下的,看可以分在几个子树中,然后看能否构成即可
点击查看代码
#include<cstdio>
#include<cmath>
#include<algorithm>
#define ll long long
using namespace std;
const int mod=998244353;
int T;
ll n,m,p[70],vis[500],l;
ll get(ll x,ll y)
{
if(!x&&!y) return n;
ll u,v,a,b;
if(x) u=p[x-1]%mod;
else u=1;
if(y) v=p[y-1]%mod;
else v=1;
ll tmp=u*v%mod,tmp2=(n-p[l]+1)%p[max(x,y)];
ll t=(p[l-max(x,y)]-1)+(n-p[l]+1)/p[max(x,y)];
t%=mod;
tmp=(tmp*t%mod)%mod;
if(x>=y) a=min(p[x-1],tmp2)%mod;
else a=u;
if(x<=y) b=max(0ll,tmp2-p[y-1])%mod;
else b=v;
tmp=(tmp+a*b%mod)%mod;
return tmp;
}
ll qpow(ll x,ll y)
{
ll res=1;
while(y)
{
if(y&1) res=res*x%mod;
x=x*x%mod;
y>>=1;
}
return res;
}
int main()
{
scanf("%d",&T);
p[0]=1;
for(int i=1;i<=60;i++)
{
p[i]=p[i-1]*2;
}
while(T--)
{
scanf("%lld%lld",&n,&m);
l=0;
for(int i=1;i<=60;i++)
{
if(p[i]<=n) l=i;
}
ll ans=0;
for(int i=0;i<=l;i++)
{
for(int j=0;j<=l;j++)
{
vis[i+j+1]=(vis[i+j+1]+get(i,j))%mod;
}
}
ll t1=qpow(m,mod-2),t2=qpow(m,n);
for(int i=1;i<=m;i++)
{
ll a=1,b=1,now=t2,sum=0;
for(int j=1;j<=2*l+1;j++)
{
a=a*i%mod,b=b*(i-1)%mod,now=now*t1%mod;
sum=(sum+now*((a-b)%mod+mod)%mod*vis[j]%mod)%mod;
}
ans=(ans+sum*i%mod)%mod;
}
printf("%lld\n",ans);
for(int i=1;i<=l*2+1;i++) vis[i]=0;
}
return 0;
}
Mighty Rock Tower
https://www.gxyzoj.com/d/hzoj/p/4255
挂个博客,不想打LaTeX了 https://www.luogu.com.cn/article/g9o3xdhh
BZOJ2164 采矿
https://www.gxyzoj.com/d/hzoj/p/4482
线段树维护去x个的最大值和最大区间和,树剖求解即可
点击查看代码
#include<cstdio>
#include<algorithm>
#define ll long long
#define lid id<<1
#define rid id<<1|1
using namespace std;
int n,m,head[20004],edgenum;
struct edge{
int nxt,to;
}e[20004];
void add_edge(int u,int v)
{
e[++edgenum].nxt=head[u];
e[edgenum].to=v;
head[u]=edgenum;
}
ll x=1<<16;
signed long int a,b,y=(1ll<<31)-1,q;
ll get()
{
a=((a^b)+b/x+b*x)&y;
b=((a^b)+a/x+a*x)&y;
return (a^b)%q;
}
int dep[20005],f[20005],siz[20005],son[20005],top[20005];
int dfn[20005],cnt,rnk[20005];
void dfs(int u,int fa)
{
dep[u]=dep[fa]+1;
f[u]=fa,siz[u]=1;
int maxn=-1;
for(int i=head[u];i;i=e[i].nxt)
{
int v=e[i].to;
if(v==fa) continue;
dfs(v,u);
siz[u]+=siz[v];
if(siz[v]>maxn)
{
maxn=siz[v],son[u]=v;
}
}
}
void dfs2(int u,int t)
{
top[u]=t,dfn[u]=++cnt;
rnk[cnt]=u;
if(son[u])
{
dfs2(son[u],t);
}
for(int i=head[u];i;i=e[i].nxt)
{
int v=e[i].to;
if(v==son[u]) continue;
dfs2(v,v);
}
}
ll w[20005][51];
struct seg_tr{
int l,r;
ll f[51],g[51];
}tr[160005];
seg_tr merge(seg_tr x,seg_tr y,int l,int r)
{
seg_tr tmp;
for(int i=0;i<=m;i++)
{
tmp.f[i]=tmp.g[i]=0;
}
for(int i=0;i<=m;i++)
{
tmp.g[i]=max(x.g[i],y.g[i]);
for(int j=0;j<=i;j++)
{
int k=i-j;
tmp.f[i]=max(tmp.f[i],x.f[j]+y.f[k]);
}
}
tmp.l=l,tmp.r=r;
return tmp;
}
void build(int id,int l,int r)
{
tr[id].l=l,tr[id].r=r;
if(l==r)
{
for(int i=0;i<=m;i++)
{
tr[id].f[i]=tr[id].g[i]=w[l][i];
}
return;
}
int mid=(l+r)>>1;
build(lid,l,mid);
build(rid,mid+1,r);
tr[id]=merge(tr[lid],tr[rid],l,r);
}
void update(int id,int x)
{
if(tr[id].l==tr[id].r)
{
for(int i=0;i<=m;i++)
{
tr[id].f[i]=tr[id].g[i]=w[x][i];
}
return;
}
int mid=(tr[id].l+tr[id].r)>>1;
if(x<=mid) update(lid,x);
else update(rid,x);
tr[id]=merge(tr[lid],tr[rid],tr[id].l,tr[id].r);
}
seg_tr query(int id,int l,int r)
{
if(tr[id].l==l&&tr[id].r==r)
{
return tr[id];
}
int mid=(tr[id].l+tr[id].r)>>1;
if(r<=mid) return query(lid,l,r);
else if(l>mid) return query(rid,l,r);
else return merge(query(lid,l,mid),query(rid,mid+1,r),0,0);
}
seg_tr getsum(int u,int v)
{
seg_tr res;
for(int i=0;i<=m;i++)
{
res.f[i]=res.g[i]=0;
}
while(top[u]!=top[v])
{
if(dep[top[u]]<dep[top[v]]) swap(u,v);
res=merge(res,query(1,dfn[top[u]],dfn[u]),0,0);
u=f[top[u]];
}
if(dep[u]<dep[v]) swap(u,v);
res=merge(res,query(1,dfn[v],dfn[u]),0,0);
return res;
}
int main()
{
// freopen("1.txt","w",stdout);
scanf("%d%d%ld%ld%ld",&n,&m,&a,&b,&q);
for(int i=2;i<=n;i++)
{
int x;
scanf("%d",&x);
add_edge(x,i);
}
// printf("1");
dfs(1,0);
dfs2(1,1);
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++)
{
w[dfn[i]][j]=get();
}
sort(w[dfn[i]]+1,w[dfn[i]]+m+1);
}
build(1,1,n);
int c;
scanf("%d",&c);
while(c--)
{
int opt;
scanf("%d",&opt);
if(opt==1)
{
int u,v;
scanf("%d%d",&u,&v);
seg_tr tmp=query(1,dfn[u],dfn[u]+siz[u]-1);
if(u!=v)
{
seg_tr t1=getsum(f[u],v);
ll ans=0;
for(int i=0;i<=m;i++)
{
int j=m-i;
ans=max(ans,tmp.f[i]+t1.g[j]);
// if(u==132&&v==72)
// printf("%d \n",ans);
}
printf("%lld\n",ans);
}
else printf("%lld\n",tmp.f[m]);
}
else
{
int x;
scanf("%d",&x);
for(int j=1;j<=m;j++)
{
w[dfn[x]][j]=get();
}
sort(w[dfn[x]]+1,w[dfn[x]]+m+1);
update(1,dfn[x]);
}
}
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律