四月好题
typewriter
题意
有 个字符集,第 个字符集为 。可以选择任意一个字符集,然后用这个字符集的字符打出长为 的字符串。求最后能够打出多少种字符串。 只包含小写字母。
解法
由于 而字符集大小为 ,故而直接使用容斥的方式计算所有可能的字符串数量即可(比看任一个字符集是否是某个字符集的子集更优)。
代码
点此查看代码
#include <bits/stdc++.h> using namespace std; #define ll long long const int md=998244353; int n,l,i,j,v,p,q,msk; int m[20],k[20]; char s[30]; ll x; ll Pow(ll d,int z){ ll ret=1; do{ if(z&1){ret*=d;if(ret>=md) ret%=md;} d*=d;if(d>=md) d%=md; }while(z>>=1); return ret; } int main(){ scanf("%d%d",&n,&l); for(i=0;i<n;++i){ scanf("%s",s+1); m[i]=strlen(s+1); for(j=1;j<=m[i];++j) k[i]|=(1<<(s[j]-'a')); } const int siz=(1<<n)-1; for(i=1;i<=siz;++i){ msk=67108863;v=-1;q=0; for(p=0;p<n;++p){ if(i&(1<<p)){ msk&=k[p]; v=-v; } } for(;msk;msk^=(msk&-msk)) ++q; x+=Pow(q,l)*v; if(x<0) x+=md; if(x>=md) x-=md; } printf("%lld",x); return 0; }
Game On Tree 3
题意
有两个人和一棵大小为 的树,根节点为 ,非根节点上均有一个非负权值,其中 点的权值为 。后手有一枚在 结点的棋子。这两个人可以在树上轮流操作。
先手每次操作后可以将任意一个节点上的 值改为 ,而后手可以将棋子从此节点移动到其任意一个子结点上。
后手可以在其某次移动完棋子后立即结束操作,而在棋子移到叶子节点上时则必须结束操作。
先手会最小化操作结束时棋子所在的节点上的 值,后手会最大化这个值。
求先后手采取最优操作后棋子所在处的 值。。
解法
考虑后手能否走到某个不小于某个值 的点。
此时可以令 值小于 的点作为黑点,其他点作为白点。这样问题转化成先手每次可以将某个白点变黑,而后手需要走到白点上才能取胜,否则先手取胜。
设 为先手至少需要在后手到 点之前,使得后手不能在 树上走到白点时在 树上变黑的白点数。显然有 。若后手走到 点,先手进行操作后, 树上变黑的白点数小于 ,则先手肯定能找到 的一个子节点 满足 树上变黑的白点数小于 ,先手不能取胜。同时若后手在 点时,先手能再次在 树上变黑一个白点,满足 不存在一个子节点 ,其中 满足 树上变黑的白点数小于 。如果 点是白点,则必须事先把 点变黑。
如果根节点的 值非 ,则后手一定能走到一个白点上,先手不能必胜;否则先手必胜。
若后手能够到达 值不小于 的点,则 ,其一定能到达 值不小于 的点。可以二分处理,求出答案。
代码
点此查看代码
#include <bits/stdc++.h> using namespace std; const int maxn=200010; int n,i,u,v,t,s,m,l,r; int h[maxn],a[maxn],dp[maxn]; struct edge{int to,nxt;}E[maxn<<1]; void dfs(const int &p,const int &f){ int lp,to,sm=0; for(lp=h[p];lp;lp=E[lp].nxt){ to=E[lp].to; if(to==f) continue; dfs(to,p); sm+=dp[to]; } --sm; if(sm<0) sm=0; if(a[p]>m) ++sm; dp[p]=sm; } int main(){ scanf("%d",&n); for(i=2;i<=n;++i){ scanf("%d",a+i); r=max(r,a[i]); } for(i=1;i<n;++i){ scanf("%d%d",&u,&v); E[++t]={v,h[u]};h[u]=t; E[++t]={u,h[v]};h[v]=t; } while(l<r){ m=(l+r)>>1; dfs(1,0); if(dp[1]) l=m+1; else r=m; } printf("%d",l); return 0; }
01? Queries
题意
有一个长为 的 串 ,其中 可以看作 或 。同时有 次修改,每次修改串中的一个字符,求串中的不同子序列个数模 。。
解法
考虑无修改时的做法。
设 表示 中以 结尾的不同子串个数, 表示 中以 结尾的不同子串个数。考虑 到 的转移。
为了方便,我们只考虑 中的子串均以 结尾的情况(如果不以 结尾,则可以把结尾换成 ,不会造成其他影响)。显然,若只这样考虑,则 即为 的所有不同子串数(包括空串),即 , 即为 。若 ,则 。
考虑使用矩阵快速幂对修改后的 值进行计算。可以发现可以用矩阵乘法优化如下:
边界条件即为 。使用线段树维护矩阵即可。
代码
点此查看代码
#include <bits/stdc++.h> using namespace std; const int maxn=100010; const int md=998244353; char s[maxn]; int n,q,u,x,_i,_j,_k; struct seg{ long long mat[3][3]; int l,r,m; }tr[maxn<<2]; #define l(p) tr[p].l #define r(p) tr[p].r #define m(p) tr[p].m #define ls(p) p<<1 #define rs(p) p<<1|1 #define mat(p,a,b) tr[p].mat[a][b] inline void Assign(const int &p){ if(s[l(p)]=='0'){ mat(p,0,1)=mat(p,0,2)= mat(p,1,2)=mat(p,2,1)=0; mat(p,1,0)=mat(p,2,0)= mat(p,0,0)=mat(p,1,1)=mat(p,2,2)=1; } else if(s[l(p)]=='1'){ mat(p,0,2)=mat(p,1,0)= mat(p,1,2)=mat(p,2,0)=0; mat(p,0,0)=mat(p,2,2)= mat(p,0,1)=mat(p,1,1)=mat(p,2,1)=1; } else{ mat(p,0,2)=mat(p,1,2)=0; mat(p,0,0)=mat(p,0,1)= mat(p,1,0)=mat(p,1,1)= mat(p,2,0)=mat(p,2,1)=mat(p,2,2)=1; } } inline void Pushup(const int &p){ for(_i=0;_i<3;++_i){ for(_j=0;_j<3;++_j){ mat(p,_i,_j)=mat(ls(p),_i,0)*mat(rs(p),0,_j); mat(p,_i,_j)+=mat(ls(p),_i,1)*mat(rs(p),1,_j); mat(p,_i,_j)+=mat(ls(p),_i,2)*mat(rs(p),2,_j); if(mat(p,_i,_j)>=md) mat(p,_i,_j)%=md; } } } void Build(const int p,const int l,const int r){ l(p)=l;r(p)=r; m(p)=(l+r)>>1; if(l(p)==r(p)){ Assign(p); return; } Build(ls(p),l,m(p)); Build(rs(p),m(p)+1,r); Pushup(p); } void Change(const int p,const int &x){ if(l(p)==r(p)){ Assign(p); return; } if(x<=m(p)) Change(ls(p),x); else Change(rs(p),x); Pushup(p); } int main(){ scanf("%d%d%s",&n,&q,s+1); Build(1,1,n); while(q--){ scanf("%d",&u); scanf(" %c",s+u); Change(1,u); x=mat(1,2,0)+mat(1,2,1); if(x>=md) x-=md; printf("%d\n",x); } return 0; }
MinimizOR
题意
有一个长为 的数组 ,同时有 次询问,每次给出一个区间 ,询问 。。
解法
可以猜测 中的 中的 均在 中最小的 个数中。
证明:设 中的每个数均在 中可以选前 小的数可以得出 ,考虑 中每个数均在 之间能否通过选择前 小的数得出 。
如果这 个数全在 中,则显然其中有 。若不全在但是至少有两个在 中,则选择在 中的数一定有结果。若在 内的数不足两个,则必有 ,也就是说 ,,所以只需要将 中每个数模 的最小的 个选出。可以发现这些数中第 小到第 小的数模 一定是 中第 小到最大的数模 的所有值中前 小的数,此时再考虑第 小的数后,最后 个数模 的值一定包含了 中所有数模 的数中前 小的数。
所以可以直接使用线段树维护 ,查询时在区间中取前 小的数,两两按位或即可求得答案。
代码
点此查看代码
#include <bits/stdc++.h> using namespace std; const int maxn=100010; int t,n,i,j,q,lt,rt,ap,tp,ans; int a[maxn],st[40]; struct seg{ int l,r,m; int mn,mp; }tr[maxn<<2]; #define l(p) tr[p].l #define r(p) tr[p].r #define m(p) tr[p].m #define ls(p) p<<1 #define rs(p) p<<1|1 #define mn(p) tr[p].mn #define mp(p) tr[p].mp inline void pushup(const int &p){ if(mn(ls(p))<mn(rs(p))){ mn(p)=mn(ls(p)); mp(p)=mp(ls(p)); } else{ mn(p)=mn(rs(p)); mp(p)=mp(rs(p)); } } void build(const int p,const int l,const int r){ l(p)=l;r(p)=r; m(p)=(l+r)>>1; if(l==r){ mn(p)=a[l]; mp(p)=l; return; } build(ls(p),l,m(p)); build(rs(p),m(p)+1,r); pushup(p); } void change(const int p,const int &x,const int &v){ if(l(p)==r(p)){ mn(p)=v; return; } if(x<=m(p)) change(ls(p),x,v); else change(rs(p),x,v); pushup(p); } void query(const int p){ if(lt<=l(p)&&rt>=r(p)){ if(ans>mn(p)){ ans=mn(p); ap=mp(p); } return; } if(lt<=m(p)) query(ls(p)); if(rt>m(p)) query(rs(p)); } int main(){ scanf("%d",&t); while(t--){ scanf("%d",&n); for(i=1;i<=n;++i) scanf("%d",a+i); build(1,1,n); scanf("%d",&q); while(q--){ scanf("%d%d",<,&rt); for(i=32;i;--i){ ans=1145141919; query(1); if(ans==1145141919) break; change(1,ap,1145141919); st[++tp]=ap; } ans=1145141919; for(i=1;i<tp;++i) for(j=i+1;j<=tp;++j) ans=min(ans,a[st[i]]|a[st[j]]); printf("%d\n",ans); for(i=1;i<=tp;++i) change(1,st[i],a[st[i]]); tp=0; } } return 0; }
Cards
题意
有 张卡片,第 张卡片写有 和 。 均为 排列。
现在需要选取一些卡片,使得 均至少在这些卡片之一上。求方案数。。
解法
听说 GaryH 在这道题上卡了几乎整整一场比赛
考虑把写有 的卡片看成是在 和 之间连接的无向边,则每个点的度均为 (不考虑形成自环的点),可以把整个情境看成由若干个简单环或自环;选择卡片的方案即为选择若干条边,满足每个点连接的边至少有一条被选。
直接选择边考虑较为困难,考虑先选择所有的边再删去某些边,满足删去的边不能为自环上的边,同时删去的任意两条边不共点。令在一个大小为 的简单有标号环上删边的方案为 (包括不删)(特别地,令 ),则 。手推可以猜想:对于 。
证明:令在一条有 条边的链上进行类似的删边的方案有 种,则由某条边是否被删则 ,而 (具体证明可以看下图)。特别地,。
从而 ,有
而手推可得 ,均满足 。故而始终有 成立。
代码
点此查看代码
#include <bits/stdc++.h> using namespace std; #define ll long long const int maxn=200010; const int md=998244353; int n,i,d,t,c; ll ans; bool vis[maxn]; int h[maxn],p[maxn],q[maxn],f[maxn]; struct edge{int to,nxt;}E[maxn<<1]; void dfs(const int p){ vis[p]=1;++c; int lp,to; for(lp=h[p];lp;lp=E[lp].nxt){ to=E[lp].to; if(vis[to]) continue; dfs(to); } } int main(){ scanf("%d",&n); f[0]=1;f[1]=2;f[2]=3;f[3]=4;ans=1; for(i=4;i<=n;++i){ f[i]=f[i-1]+f[i-2]; if(f[i]>=md) f[i]-=md; } f[1]=1; for(i=1;i<=n;++i){ scanf("%d",&d); p[d]=i; } for(i=1;i<=n;++i){ scanf("%d",&d); q[d]=i; } for(i=1;i<=n;++i){ E[++t]={p[i],h[q[i]]};h[q[i]]=t; E[++t]={q[i],h[p[i]]};h[p[i]]=t; } for(i=1;i<=n;++i){ if(vis[i]) continue; c=0;dfs(i); ans*=f[c]; if(ans>=md) ans%=md; } printf("%lld\n",ans); return 0; }
Narrow Components
题意
有一个 的 矩阵。 次询问,每次询问一个区间 ,求第 列到第 列有多少个只有 的四连通块。。
解法
考虑将两个矩阵拼在一起会减少多少连通块。
显然如果连接部分存在两边均为 的一行,则连通块个数应减少 。如果连接处的第一行和第三行的 均存在,连通块个数应减少 ,但是如果某一边的第一行和第三行的 本来就在一个连通块里,则连通块个数只需要减少 。
线段树维护上述信息即可。具体维护某个区间两边的 是否在一个连通块内可见代码。
p.s. 听说这个题有最多可以处理 行的使用线段树套并查集的解法。
代码
点此查看代码
#include <bits/stdc++.h> using namespace std; const int maxn=500010; int n,i,q,lp,rp,msk[maxn]; int l[maxn<<2],r[maxn<<2],m[maxn<<2]; struct seg{ int cnt,lm,rm; bool cl,cr,c2; inline seg operator +(const seg &a){ seg tmp; tmp.lm=lm;tmp.rm=a.rm; tmp.cnt=cnt+a.cnt; tmp.c2=c2&&a.c2; tmp.cl=cl||(c2&&a.cl); tmp.cr=a.cr||(a.c2&&cr); if(rm&a.lm){ --tmp.cnt; if((((rm==5)&&(!cr))&&(((a.lm==5)&&(!a.cl))||a.cl))|| (cr&&((a.lm==5)&&(!a.cl)))) --tmp.cnt; } return tmp; } }tr[maxn<<2],ans; #define ls(p) p<<1 #define rs(p) p<<1|1 void build(const int p,const int <,const int &rt){ l[p]=lt;r[p]=rt; m[p]=(lt+rt)>>1; if(lt==rt){ tr[p].lm=tr[p].rm=msk[lt]; if(!msk[lt]) tr[p].cnt=0; else if(msk[lt]==5){ tr[p].cnt=2; tr[p].c2=1; } else{ tr[p].cnt=1; if(msk[lt]==7) tr[p].cl=tr[p].cr=1; } return; } build(ls(p),lt,m[p]); build(rs(p),m[p]+1,rt); tr[p]=tr[ls(p)]+tr[rs(p)]; } void query(const int p,const int <,const int &rt){ if(lt<=l[p]&&rt>=r[p]){ if(!ans.cnt) ans=tr[p]; else ans=ans+tr[p]; return; } if(lt<=m[p]) query(ls(p),lt,rt); if(rt>m[p]) query(rs(p),lt,rt); } int main(){ scanf("%d",&n); getchar(); for(i=1;i<=n;++i){ q=getchar()^'0'; if(q) msk[i]|=1; } getchar(); for(i=1;i<=n;++i){ q=getchar()^'0'; if(q) msk[i]|=2; } getchar(); for(i=1;i<=n;++i){ q=getchar()^'0'; if(q) msk[i]|=4; } build(1,1,n); scanf("%d",&q); while(q--){ scanf("%d%d",&lp,&rp); ans={0,0,0,0,0,0}; query(1,lp,rp); printf("%d\n",ans.cnt); } }
Teleporters
题意
有一个有 个正整数的集合,第 个数为 ()。现在可以进行若干次操作,每次操作可以取出一个数 ,然后把 和 两个正整数放回集合。求使得集合内所有数的平方和不大于 的最小操作次数。。
解法
考虑将某个数 拆分 次对答案的最小贡献。
显然若其中拆出的数有 和 ,则有
故而最优方案中,最后拆出来的数一定不会有相差大于 的数,也就是拆出的数一定是 个 和 个 ,贡献即为
令 ,则在对应一段的斜率为 ,而之后的满足 的一段的斜率为 大于 。故而对于固定的 ,关于 的函数 是一段下凸函数。可以使用堆维护每个 目前的斜率,每次在 对应的斜率最小的 贪心进行操作即可。
注意: 中 ,不能单纯地按上述公式计算斜率,而需要在斜率交界处重新计算 。
代码
注意下面 Calc 函数的效率。
点此查看代码
#include <bits/stdc++.h> using namespace std; const int maxn=200010; const int INF=2147483647; #define ll long long int n,i,c,l,r,d,cnt; int a[maxn]; ll m,v,ans; inline ll Calc(const int &x,const int &y){ const ll p=x/(y+1); return p*p*(y+1)+(x-(y+1)*p)*(2*p+1); } inline int got(const int &x,const int &y){ if(x<=y) return INF; return x/(x/(y+1)); } struct node{ int len,use;ll val,vax; inline bool operator <(const node &a)const{ return vax<a.vax; } }tmp; priority_queue<node> q; int main(){ scanf("%d",&n); for(i=1;i<=n;++i) scanf("%d",a+i); scanf("%lld",&m); for(i=n;i;--i){ a[i]-=a[i-1];ans+=1LL*a[i]*a[i]; q.push((node){a[i],0,1LL*a[i]*a[i],1LL*a[i]*a[i]-Calc(a[i],1)}); } while(ans>m){ tmp=q.top();q.pop(); c=got(tmp.len,tmp.use); if(c!=tmp.use+1) --c; v=Calc(tmp.len,c); if(ans-tmp.val+v>=m){ cnt+=c-tmp.use; tmp.use=c; ans-=tmp.val-v; tmp.val=v; tmp.vax=v-Calc(tmp.len,c+1); q.push(tmp); } else{ l=tmp.use;r=c; while(l<r){ d=(l+r)>>1; if(ans-tmp.val+Calc(tmp.len,d)<=m) r=d; else l=d+1; } cnt+=l-tmp.use; break; } } printf("%d",cnt); return 0; }
Rotate and Play Game
题意
有一个长为 的序列 。两个人轮流进行若干次操作:先手可以取走 中任意元素,然后后手取走 中下标最小的元素,直到把序列取空。操作前可以将 由 变为 ,求先手能取到的数的和的最大值和对应的任意一个合法 。 且 为偶数。
解法
猜想一定存在一种方案使得先手能够取到前 大的数。
证明:考虑把前 大的数看成是 ,其他数看成是 ,则序列的前缀和均不小于 时先手能取遍前 大的数。此时先手可以取第一个 (令其所在的位置为第 个),此时肯定仍有序列的前缀和均不小于 ,也就是当前序列的第一个数为 。而后手取走这个 后,第 个数及以后的所有数均未发生改变;这之前的数(如果存在)肯定均为 ,且它们的和同样不变。
至于如何构造这样的序列,可以求出序列中最小的前缀和对应的前缀(令其为 且这个最小前缀和为 ),则 即为所求答案,把序列 变成 即满足上述性质。由于 ,且 ,则 。故而这样构造有其前缀和均不小于 。
代码
点此查看代码
#include <bits/stdc++.h> using namespace std; const int maxn=200010; int n,i,d,x; int a[maxn],p[maxn]; long long s; inline bool cmp(const int &y,const int &z){return a[y]<a[z];} int main(){ scanf("%d",&n); for(i=1;i<=n;++i){ scanf("%d",a+i); p[i]=i; } x=n; sort(p+1,p+n+1,cmp); n>>=1; for(i=1;i<=n;++i) a[p[i]]=1; n<<=1; while(i<=n){ s+=a[p[i]]; a[p[i]]=-1; ++i; } for(i=1;i<n;++i){ a[i]+=a[i-1]; if(a[i]<a[x]) x=i; } if(x==n) x=0; printf("%d %lld",x,s); return 0; }
P7949 左方之地 & ARC138D Differ by K Bits
题意
要求构造一个 的排列 ,满足相邻两个数的异或值在二进制的表示下刚好有且只有 个 。求任意一种构造方案,或判断其为无解。ARC138D:需要最后一个数和第一个数的异或值满足同样的性质。
P7949:。ARC138D:。
解法
首先 为偶数时(考虑奇偶性)或不小于 时显然无解。
考虑格雷码( 的情况)。长为 的格雷码 有 的构造方式:第 项为 。
证明:
如果 为偶数,则 只有一个 ,而 ,故而有 。
如果 为奇数,且 末尾有 个 (显然第 位为 ),令 ,则 ,;,。则 ,;从而 。
同时若 ,且 ,则 ,故而 。同时 ,故而 ,也就是 且 。从而这样只有 才有 。
综上, 满足 且 只有一个 。
所以我们有了对于任意的 构造 的解法。同时考虑构造 的方案。发现如果把奇数位的 值的所有位取反,则对于取反的 , 和 均有 个 。同时 有同样的性质。
考虑从 的合法解推到 的合法解。可以构造 ,此时 有 个 ,而 ,同样在二进制表达下有 个 。新的 同理。同时若 ,则 即 ,也就是需要 ,而此条件满足当且仅当 。综上,这样的构造方法可以得到 的合法解。
综上,可以先求 的合法解,然后逐级递推,即可求得答案。
代码
点此查看代码
//P7949 #include <bits/stdc++.h> using namespace std; int n,k,s,i,j,l,q; int b[1048600]; int main(){ scanf("%d%d",&n,&k); q=k; if(n==1&&k==1){ printf("1\n0 1"); return 0; } if(k>=n||(!(k&1))){ printf("0\n"); return 0; } printf("1\n"); s=(1<<(k+1))-1; for(i=0;i<=s;++i){ b[i]=i^(i>>1); if(i&1) b[i]^=s; } l=s;s=(1<<(k-1))-1;++k; while(k<n){ j=l+1; for(i=l;i>=0;--i) b[j++]=(b[i]+(1<<k))^s; l=l<<1|1;++k; } for(i=0;i<=l;++i) printf("%d ",b[i]); return 0; }
下述内容待补
Decreasing Subsequence
题意
求最长下降子序列长度不小于 的长为 的序列 的个数,满足 且 。。
解法
代码
点此查看代码
Beautiful Subsequences
题意
给出一个 的排列 ,求 中有多少个区间 满足 。。
解法
代码
点此查看代码
Edge Elimination
题意
给出一棵大小为 的无根树,可以进行若干次操作,每次操作删除一条与偶数条边相邻的边。求一种删除所有边的合法次序,或判断其为无解。。
解法
代码
点此查看代码
AND-MEX Walk
题意
给定一个 个节点 条边的无向简单连通图,边有边权。我们定义一条路径(即可以重复经过同一个节点或同一条边的路径)的权值如下:设该途径按顺序经过的边的权值为 ,则该途径的权值为 。给定 次询问,每次给定两个不同的整数 ,求所有从节点 开始到节点 结束的路径中,路径权值的最小值。。
解法
代码
点此查看代码
Checker for Array Shuffling
题意
给定一个长为 的数组 。若 的一个排列 可以通过 次将任意两个数交换位置得到 ,则 的价值为所有合法 的最小值。现在给出 的某个排列 ,判断它的价值是否为所有长为 的数组的最大值。若是则输出 AC
,否则输出 WA
。 组数据。.
解法
代码
点此查看代码
Cross Xor
题意
有一个 的全 矩阵 ,和一个 的 矩阵 。可以进行若干次操作,每次操作可以指定 和 ,然后将 异或上 。求 最终能变成的矩阵 中,有多少个满足 。。
解法
代码
点此查看代码
Zigu Zagu
题意
你有一个长度为 的 串 。现在给出 次询问,每次询问给出两个数 ,保证 。令 你可以对 做如下操作:
-
选择两个数 ,满足 。令子串 ,对于所有的 ,需要满足 。
-
删除 。
对于每一个询问,请求出最少需要多少个操作才能把 变成一个空串。。
解法
代码
点此查看代码
Xor Cards
题意
有 张卡片,第 张卡片上写有 。现在要选出若干卡片(记选了 )且保证 时,求 ,或判断不存在选出卡片的合法方案。。
解法
代码
点此查看代码
Antennas
题意
在 的数轴上有 座信号塔,每个整点上有一座。第 座信号塔和第 座信号塔可以传递信息当且仅当 。求从 向 传递信息经过的最少信号塔数(不包括 )。。
解法
代码
点此查看代码
本文来自博客园,作者:Fran-Cen,转载请注明原文链接:https://www.cnblogs.com/Fran-CENSORED-Cwoi/p/16753814.html
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 【杂谈】分布式事务——高大上的无用知识?