省选动态规划专题

消失之物

发现背包顺序无关,那么就支持O(n)撤销任何一个物品。时间复杂度O(nm)。当然也有唐氏的线段树分治和多项式解法,强行带个log

code
#include<bits/stdc++.h>
#include<bits/extc++.h>
// using namespace __gnu_pbds;
// using namespace __gnu_cxx;
using namespace std;
#define infile(x) freopen(x,"r",stdin)
#define outfile(x) freopen(x,"w",stdout)
#define errfile(x) freopen(x,"w",stderr)
#define rep(i,s,t,p) for(int i = s;i <= t; i += p)
#define drep(i,s,t,p) for(int i = s;i >= t; i -= p)
#ifdef LOCAL
FILE *InFile = infile("in.in"),*OutFile = outfile("out.out");
// FILE *ErrFile=errfile("err.err");
#else
FILE *Infile = stdin,*OutFile = stdout;
//FILE *ErrFile = stderr;
#endif
using ll=long long;using ull=unsigned long long;
using db = double;using ldb = long double;
const int N = 2e3 + 10;
int n,m,f[N],a[N],g[N];
signed main(){
cin.tie(nullptr)->sync_with_stdio(false);
cout.tie(nullptr)->sync_with_stdio(false);
cin>>n>>m;rep(i,1,n,1) cin>>a[i];
f[0] = 1;
rep(i,1,n,1) drep(j,m,a[i],1) f[j] = (f[j] + f[j - a[i]])%10;
rep(i,1,n,1){
g[0] = 1;
rep(j,1,m,1){
if(j - a[i] >= 0) g[j] = (f[j] - g[j - a[i]] + 10) % 10;
else g[j] = f[j] % 10;
cout<<g[j];
}
cout<<'\n';
}
}

[国家集训队] 墨墨的等式

同余最短路板子,见省选图论专题

[BalticOI 2022 Day1] Uplifting Excursion

首先,贪心地想,肯定是将所有的都选上,如果比l多,那么从大往小删,反之,从小往大删。那么,显然,最后剩下的和sum一定可以控制在[lm,l+m]间。

对于剩下的一点,考虑背包填,剩下的这个背包的值域是m2的,考虑ii不会同时出现,而每种物品最多只会有一个,所以最后的值域是m2的。

剩下的就是一个多重背包,二进制优化即可。时间复杂度O(m3logm)

code
#include<bits/stdc++.h>
using namespace std;
#define rep(i,s,t,p) for(int i = s;i <= t;i += p)
#define drep(i,s,t,p) for(int i = s;i >= t;i -= p)
#ifdef LOCAL
auto I = freopen("in.in","r",stdin),O = freopen("out.out","w",stdout);
#else
auto I = stdin,O = stdout;
#endif
using ll = long long;using ull = unsigned long long;
using db = long double;using ldb = long double;
#define int ll
int m,l;unordered_map<int,int> a,b;
int f[300010],siz;
void DP(int w,int v,int c){
if(w > 0){
for(int s = 1;c > 0;c -= s,s <<= 1){
int k = min(s,c);
drep(i,siz,k*w,1) f[i] = max(f[i],f[i-k*w]+k*v);
}
}
else{
for(int s = 1;c > 0;c -= s,s <<= 1){
int k = min(s,c);
rep(i,0,siz + k*w,1) f[i] = max(f[i],f[i-k*w]+k*v);
}
}
}
signed main(){
cin.tie(nullptr)->sync_with_stdio(false);
cin>>m>>l;int cbmn = 0,cbmx = 0,sum = 0;
rep(i,-m,m,1){
cin>>a[i],b[i] = a[i];sum += a[i]*i;
if(i < 0) cbmn += a[i]*i;else cbmx += a[i]*i;
}
if(l > cbmx || l < cbmn) return puts("impossible"),0;
if(sum > l){
drep(i,m,1,1){
if(sum <= l) break;
int res = sum - l,num = min(res/i,a[i]);
b[i] -= num;sum -= num*i;
}
}
else{
rep(i,-m,-1,1){
if(sum >= l) break;
int res = sum - l,num = min(res/i,a[i]);
b[i] -= num;sum -= num*i;
}
}
memset(f,-0x3f,sizeof f);
int res = m*m-l+sum;f[res] = 0;siz = m*m*2;
rep(i,-m,m,1) f[res] += b[i];
rep(i,0,2*m,1){
if(i == m) continue;
if(b[i-m]) DP(-(i-m),-1,min(b[i-m],siz));
if(a[i-m] - b[i-m]) DP(i-m,1,min(a[i-m]-b[i-m],siz));
}
if(f[m*m] < 0) cout<<"impossible\n";
else cout<<f[m*m]<<'\n';
}

[JSOI2016] 最佳团体

01分数规划+树上背包,简单说一下01分数规划吧,因为之前没怎么见过。

01分数规划

给定n个二元组(ai,bi),选出任意多个二元组,使得ab最大/最小。由于本质相同,所以下面都记为最大。

考虑二分,假设当前二分的答案为m,那么就要有abm,即am×b,移项得amb0,此时只需要求左边柿子最大值就行了。

对于本题

就是01分数规划套树上背包,注意你的背包可能写假了,写成O(n3)的了。

code
#include<bits/stdc++.h>
using namespace std;
#define rep(i,s,t,p) for(int i = s;i <= t;i += p)
#define drep(i,s,t,p) for(int i = s;i >= t;i -= p)
#ifdef LOCAL
auto I = freopen("in.in","r",stdin),O = freopen("out.out","w",stdout);
#else
auto I = stdin,O = stdout;
#endif
using ll = long long;using ull = unsigned long long;
using db = long double;using ldb = long double;
const int N = 2510;
#define eb emplace_back
int k,n,p[N],s[N],siz[N],dfn[N],rdfn[N],tim;
vector<int> e[N];
db a[N],f[N][N];
void dfs(int x){
siz[x] = 1;
if(x) rdfn[dfn[x] = ++tim] = x;
for(auto y:e[x]) dfs(y),siz[x] += siz[y];
}
bool check(db mid){
a[0] = 0;
rep(i,1,n,1) a[i] = p[i] - s[i]*mid;
rep(i,1,n+1,1) rep(j,1,k+1,1) f[i][j] = -1e10;
drep(i,n,1,1) rep(j,1,k,1)
f[i][j] = max(f[i+1][j-1] + a[rdfn[i]],f[i + siz[rdfn[i]]][j]);
return f[1][k] > 0;
}
signed main(){
cin.tie(nullptr)->sync_with_stdio(false);
cin>>k>>n;
rep(i,1,n,1){int f;cin>>s[i]>>p[i]>>f,e[f].eb(i);}
dfs(0);
db l = 0,r = 3,ans = 0;
while(l + 1e-4 <= r){
db mid = (l+r)/2;
if(check(mid)) ans = mid,l = mid;
else r = mid;
}
cout<<fixed<<setprecision(3);
cout<<ans<<'\n';
}

[POI2015] MYJ

容易证明,每个点的值为c中的一个数时,总和一定为最大的。发现只在乎相对大小,所以可以将c离散化。

n,m过小,考虑区间dp。具体的,设fi,j,k表示区间[i,j]中,最小值大于等于k时的价值总和,那么考虑枚举最小值的位置,有一个显然的转移方程fi,j,k=maxiljfi,l1,k+fl+1,j,k+sum×ck,其中sum表示在区间[i,j]中,值大于等于ck且跨过l的个数。

由于fi,j,k表示最小值大于等于k时最大的价值总和,所以还要和fi,j,k+1max

答案显然为f1,n,1,输出方案的话记录一下转移点就行了。

code
#include<bits/stdc++.h>
using namespace std;
#define rep(i,s,t,p) for(int i = s;i <= t;i += p)
#define drep(i,s,t,p) for(int i = s;i >= t;i -= p)
#ifdef LOCAL
auto I = freopen("in.in","r",stdin),O = freopen("out.out","w",stdout);
#else
auto I = stdin,O = stdout;
#endif
using ll = long long;using ull = unsigned long long;
using db = long double;using ldb = long double;
const int N = 60,M = 4010;
int n,m,a[M],b[M],c[M],sum[N][N];
int f[N][N][M],ans[N];pair<int,int> lst[N][N][M];
vector<int> num;
void dfs(int l,int r,int s){
if(l > r) return;
pair<int,int> it = lst[l][r][s];
ans[it.second] = num[it.first];
dfs(l,it.second-1,it.first);dfs(it.second+1,r,it.first);
}
signed main(){
cin.tie(nullptr)->sync_with_stdio(false);
num.emplace_back(-1);
cin>>n>>m;rep(i,1,m,1) cin>>a[i]>>b[i]>>c[i],num.emplace_back(c[i]);
sort(num.begin(),num.end());num.erase(unique(num.begin(),num.end()),num.end());
rep(i,1,m,1) c[i] = lower_bound(num.begin(),num.end(),c[i]) - num.begin();
drep(i,m,0,1){
rep(j,1,m,1) if(c[j] == i) rep(l,1,a[j],1) rep(r,b[j],n,1) sum[l][r]++;
rep(len,1,n,1) rep(l,1,n-len+1,1){
int r = l + len - 1;
f[l][r][i] = f[l][r][i + 1],lst[l][r][i] = lst[l][r][i+1];
rep(p,l,r,1){
int s = sum[l][r] - sum[l][p-1] - sum[p+1][r];
int v = (f[l][p-1][i]+f[p+1][r][i]+s*num[i]);
if(v > f[l][r][i]) f[l][r][i] = v,lst[l][r][i] = {i,p};
}
if(!lst[l][r][i].first) lst[l][r][i] = {i,l};
}
}
cout<<f[1][n][1]<<'\n';dfs(1,n,1);
rep(i,1,n,1) cout<<ans[i]<<' ';
}

[SBCOI2020] 一直在你身旁

n比较小,考虑区间dp。设fl,r表示当最后这根电线的长度x[l,r]时,最少需要花费多少代价确定x。那么有fl,r=minl<k<rmax(fl,k,fk+1,r)+ak。直接转移是O(n3)的,考虑优化。

打表发现这玩意没有决策单调性。这个max很讨厌,看看能不能把它拆开,直接分讨fl,k>fk+1,rfl,k<fk+1,r时候。但是还是不咋好做,考虑有啥优秀性质。容易发现,当fl,k随着k的增加而增加,fk+1,r随着k的增加而减少,那么就是找到一个分界点k,使得pk,q>kfl,pfp+1,r,fl,q<fq+1,r

那么就可以二分找这个分界点了?但是发现二分出这个分界点没有意义啊,最后转移还是O(n3)的。

发现当r固定时,那么fl,r的分界点在fl+1,r的左边或原地,这个可以通过打表找到规律。

fl,r的分界点为k,那么fl,r的转移就表示成

fl,r=min{fl,p+appkfp+1,r+app>k

对于pk,直接返回fl,k+ak即可,对于p>k,单调队列维护。

时间复杂度O(n2)

code
#include<bits/stdc++.h>
using namespace std;
#define rep(i,s,t,p) for(int i = s;i <= t;i += p)
#define drep(i,s,t,p) for(int i = s;i >= t;i -= p)
#ifdef LOCAL
auto I = freopen("in.in","r",stdin),O = freopen("out.out","w",stdout);
#else
auto I = stdin,O = stdout;
#endif
using ll = long long;using ull = unsigned long long;
using db = long double;using ldb = long double;
const int N = 7110;
int n,a[N];
ll f[N][N];
signed main(){
cin.tie(nullptr)->sync_with_stdio(false);
int T;cin>>T;
while(T--){
cin>>n;rep(i,1,n,1) cin>>a[i];
rep(i,1,n,1) rep(j,1,n,1) f[i][j] = 0;
rep(r,2,n,1){
int k = r;deque<int> q;q.push_back(r);
drep(l,r-1,1,1){
if(r - l == 1){f[l][r] = a[l];continue;}
while(k > l && f[l][k-1] >= f[k][r]) k--;
f[l][r] = f[l][k] + a[k];
while(q.size() && q.front() >= k) q.pop_front();
if(q.size()) f[l][r] = min(f[l][r],f[q.front()+1][r] + a[q.front()]);
while(q.size() && f[q.back() + 1][r] + a[q.back()]
>= f[l + 1][r] + a[l]) q.pop_back();
q.push_back(l);
}
}
cout<<f[1][n]<<'\n';
}
}

Yet Another Minimization Problem

决策单调性优化dp,但只有分治能做。

状态设计是显然的,设fk,i表示将前i个数分成k段的最小代价,显然有fk,i=min1j<ifk1,j1+w(j,i)。那么这就是跑k遍的1D/1Ddp,但是没有办法用数据结构优化,因为w没有很好的方法维护,斜率优化也做不了,考虑决策单调性。通过打表发现这玩意满足决策单调性,但由于w不好维护,直接上分治。

但如果暴力求w会寄,因为无法保证单次是O(nlogn),考虑用一个类似莫队的东西维护就好了。

总时间复杂度O(knlogn)

code
#include<bits/stdc++.h>
using namespace std;
#define rep(i,s,t,p) for(int i = s;i <= t;i += p)
#define drep(i,s,t,p) for(int i = s;i >= t;i -= p)
#ifdef LOCAL
auto I = freopen("in.in","r",stdin),O = freopen("out.out","w",stdout);
#else
auto I = stdin,O = stdout;
#endif
using ll = long long;using ull = unsigned long long;
using db = long double;using ldb = long double;
const int N = 1e5 + 10;
int n,k,a[N],now,ct[N],l,r;
ll f[21][N],sum;
ll calc(int L,int R){
while(l > L) sum += ct[a[--l]],ct[a[l]]++;
while(r < R) sum += ct[a[++r]],ct[a[r]]++;
while(l < L) ct[a[l]]--,sum -= ct[a[l++]];
while(r > R) ct[a[r]]--,sum -= ct[a[r--]];
return sum;
}
void work(int l,int r,int L,int R){
if(l > r) return;
int mid = (l + r) >> 1,pos = 0;
ll res = 0x3f3f3f3f3f3f3f3f,sum = 0;
drep(i,min(R,mid),L,1){
ll cjs = f[now][i-1];
cjs += calc(i,mid);
if(cjs < res) res = cjs,pos = i;
}
f[now+1][mid] = res;
work(l,mid-1,L,pos);work(mid+1,r,pos,R);
}
signed main(){
cin.tie(nullptr)->sync_with_stdio(false);
cin>>n>>k;rep(i,1,n,1) cin>>a[i];
memset(f,0x3f,sizeof f);f[0][0] = 0;
rep(j,1,k,1){
l = 1,r = 0,sum = 0;
memset(ct,0,sizeof ct);
now = j - 1;
work(1,n,1,n);
}
cout<<f[k][n]<<'\n';
}

[APIO2014] 序列分割

斜率优化板子,但要注意小的那一维放在前面,和内存访问连续有关。

code
#include<bits/stdc++.h>
using namespace std;
#define rep(i,s,t,p) for(int i = s;i <= t;i += p)
#define drep(i,s,t,p) for(int i = s;i >= t;i -= p)
#ifdef LOCAL
auto I = freopen("in.in","r",stdin),O = freopen("out.out","w",stdout);
#else
auto I = stdin,O = stdout;
#endif
using ll = long long;using ull = unsigned long long;
using db = long double;using ldb = long double;
namespace IO{
#define gc getchar_unlocked
#define pc putchar_unlocked
template<class T>
inline void read(T &x){
x = 0;char s = gc();
for(;s < '0' || s > '9';s = gc());
for(;'0' <= s && s <= '9';s = gc())
x = (x << 1) + (x << 3) + (s ^ 48);
}
template<class T,class... Args>
inline void read(T &x,Args&... argc){read(x);read(argc...);}
inline void write(char x){pc(x);}
template<class T>
inline void write(T x){
static int sta[30],top = 0;
do sta[++top] = x%10;while(x /= 10);
while(top) pc(sta[top--]+'0');
}
template<class T,class... Args>
inline void write(T x,Args... argc){write(x);write(argc...);}
}using IO::read;using IO::write;
const int N = 1e5 + 10,K = 210;
int n,k,a[N],q[N],l,r,pre[K][N];
ll f[K][N],s[N];
bool vis[N];int cjs;
ll pow(ll x){return x*x;}
ll get(int p,int k){return pow(s[p]) - f[k][p];}
db slope(int j,int k,int p){
if(s[j] == s[k]) return 1e12;
return 1.0*(get(j,p)-get(k,p))/(s[j] - s[k]);
}
void print(int k,int x){
if(!k) return;
print(k-1,pre[k][x]);
if(pre[k][x]) cjs++,write(pre[k][x],' '),vis[pre[k][x]] = true;
}
signed main(){
cin.tie(nullptr)->sync_with_stdio(false);
read(n,k);rep(i,1,n,1) read(a[i]),s[i] = s[i-1] + a[i];
rep(num,1,k,1){
l = 1,r = 0;q[++r] = 1;
rep(i,2,n,1){
while(l < r && slope(q[l+1],q[l],num-1) <= s[i]) l++;
int j = q[l];
f[num][i] = f[num-1][j] + s[i]*s[j] - pow(s[j]);
pre[num][i] = j;
while(l < r && slope(q[r],q[r-1],num-1) >= slope(i,q[r],num-1)) r--;
q[++r] = i;
}
}
write(f[k][n],'\n');
print(k,n);
rep(i,1,n,1){
if(cjs >= k) break;
if(vis[i]) continue;
write(i,' ');cjs++;
}
}

Sonya and Problem Wihtout a Legend

slope trick 维护dp,但是发现n很小,直接n2过。

code
#include<bits/stdc++.h>
#include<bits/extc++.h>
// using namespace __gnu_pbds;
// using namespace __gnu_cxx;
using namespace std;
#define infile(x) freopen(x,"r",stdin)
#define outfile(x) freopen(x,"w",stdout)
#define errfile(x) freopen(x,"w",stderr)
#ifdef LOCAL
FILE *InFile = infile("in.in"),*OutFile = outfile("out.out");
// FILE *ErrFile=errfile("err.err");
#else
FILE *Infile = stdin,*OutFile = stdout;
//FILE *ErrFile = stderr;
#endif
using ll=long long;using ull=unsigned long long;
using db = double;using ldb = long double;
const int N = 1e6 + 10;
int n,a[N],b[N];
inline void solve(){
cin>>n;
priority_queue<int> q;ll ans = 0;
for(int i = 1;i <= n; ++i){
cin>>a[i];b[i] = (a[i] -= i);
q.push(a[i]);
if(q.top() > a[i]){
ans += q.top() - a[i];
q.pop();q.push(a[i]);
}
a[i] = q.top();
}
cout<<ans<<'\n';
// for(int i = n - 1;i >= 1; --i) a[i] = min(a[i],a[i + 1]);
// for(int i = 1;i <= n; ++i) cout<<a[i]+i<<' ';
}
signed main(){
cin.tie(nullptr)->sync_with_stdio(false);
cout.tie(nullptr)->sync_with_stdio(false);
solve();
}

Yet Another Partiton Problem

神仙题,为了做这个特意去学的凸包。凸包启发式合并+斜率优化+可持久化李超线段树。

显然的dp方程fk,i=min1<jifk1,j1+(ij+1)×maxjpiap。容易想到斜率优化,记pjia最大值的位置,拆一下,变成斜率优化的形式,就变成了fk,i=i×apj×ap+ap+fk1,j1。按照y=kx+b的形式,此处的k=ap,x=i,b=j×ap+ap+fk1,j1。发现ap不单调,所以用李超线段树维护。但是ap还没有处理,考虑单调栈处理,那么每次弹栈就意味着将一条直线删除,换成另一条直线,所以考虑可持久化。但是b肯定取最小的,所以对于每个ap相同的b,用一个下凸包维护这些b所构成的凸包,然后这里的b也变成了一个斜率优化的dp,即gap=minj×ap+ap+fk1,j1,发现j单调但是fk1,j1不单调,所以维护一个凸包,对于每个ap二分求出最小值。弹栈时,相当于把两个凸包合并,直接合并显然会寄,考虑启发式合并即可。

时间复杂度O(knlogn),空间复杂度O(nlogn),时间复杂度在于李超线段树和凸包合并,空间复杂度在于可持久化李超树。

建议凸包用斜率判断,更好写,也不容易写错。

code
#include<bits/stdc++.h>
using namespace std;
#define rep(i,s,t,p) for(int i = s;i <= t;i += p)
#define drep(i,s,t,p) for(int i = s;i >= t;i -= p)
#ifdef LOCAL
auto I = freopen("in.in","r",stdin),O = freopen("out.out","w",stdout);
#else
auto I = stdin,O = stdout;
#endif
using ll = long long;using ull = unsigned long long;
using db = long double;using ldb = long double;
const int N = 2e4 + 10,K = 110;
int n,k,f[N],g[N],a[N],sta[N],top;
struct Line{
int k,b;
Line(){b = 2e9;k = 0;} Line(int K,int B):k(K),b(B){}
ll get(int x){return k*x+b;}
}s[N];
ll get(int p,int x){return s[p].get(x);}
struct LCST{
struct node{int ls,rs,v;}t[N*18];
#define ls(x) t[x].ls
#define rs(x) t[x].rs
#define v(x) t[x].v
int tot = 0,rt[N];
void upd(int pre,int &k,int l,int r,int p){
k = ++tot;t[k] = t[pre];
if(l == r){
if(get(p,l) < get(v(k),l)) v(k) = p;
return;
}
int mid = (l + r) >> 1;
if(get(p,mid) < get(v(k),mid)) swap(v(k),p);
if(get(p,l) < get(v(k),l)) upd(ls(pre),ls(k),l,mid,p);
if(get(p,r) < get(v(k),r)) upd(rs(pre),rs(k),mid+1,r,p);
}
int qry(int k,int l,int r,int x){
if(!k) return 2e9;
if(l == r) return get(v(k),x);
int mid = (l + r) >> 1,res = get(v(k),x);
return min(res,x <= mid?qry(ls(k),l,mid,x):qry(rs(k),mid+1,r,x));
}
#undef ls
#undef rs
#undef v
}sgt;
struct Point{
int x,y;
Point(){} Point(int X,int Y):x(X),y(Y){}
db slope(const Point &a){return 1.0*(y-a.y)/(x-a.x);}
};
struct Convex_Hull{
deque<Point> ch;
void Join(Convex_Hull &x){
int t = x.ch.size()-1;
// ch.front().x > x.ch.back().x
if(ch.size() <= x.ch.size()){//ch 并入 x.ch
for(auto p:ch){
while(t && x.ch[t-1].slope(x.ch[t]) >= x.ch[t-1].slope(p))
t--,x.ch.pop_back();
x.ch.emplace_back(p);t++;
}
ch.swap(x.ch);
}
else{
int sz = ch.size() - 1;
for(Point p;~t;--t){
p = x.ch[t];
while(sz && ch[1].slope(ch[0]) <= ch[1].slope(p))
ch.pop_front(),sz--;
ch.emplace_front(p);sz++;
}
}
}
int qry(int k){
int l = 1,r = ch.size() - 1,ans = 0;
while(l <= r){
int mid = (l + r) >> 1;
if(ch[mid].slope(ch[mid-1]) < k)
l = mid + 1,ans = mid;
else r = mid - 1;
}
return ch[ans].y - (ch[ans].x - 1)*k;
}
}ch[N];
signed main(){
cin.tie(nullptr)->sync_with_stdio(false);
cin>>n>>k;rep(i,1,n,1) cin>>a[i];
rep(i,1,n,1) f[i] = max(a[i],f[i-1]);
rep(i,1,n,1) f[i] *= i;
rep(j,2,k,1){
rep(i,1,n,1) swap(f[i],g[i]);
sgt.tot = 0;top = 0;
// cerr<<"\033[32m";cerr<<j<<":::\n";cerr<<"\033[0m";
rep(i,1,n,1){
// cerr<<i<<":\n";
ch[i].ch.resize(1);ch[i].ch[0] = Point(i,g[i-1]);
while(top && a[sta[top]] < a[i]) ch[i].Join(ch[sta[top--]]);
s[i].k = a[i];
s[i].b = ch[i].qry(a[i]);
sgt.upd(sgt.rt[sta[top]],sgt.rt[i],1,n,i);
f[i] = sgt.qry(sgt.rt[i],1,n,i);
// cerr<<"This is Line : ";
// cerr<<s[i].k<<' '<<s[i].b<<'\n';
// cerr<<"This is convex hull: ";
// for(auto q:ch[i].ch) cerr<<q.x<<' '<<q.y<<" | ";
// cerr<<'\n';
// cerr<<'\n';
sta[++top] = i;
}
// cerr<<'\n';
}
cout<<f[n]<<'\n';
}
posted @   CuFeO4  阅读(41)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
点击右上角即可分享
微信分享提示