『做题记录』一锅大乱炖
不出意外的话,这就是我最后的波纹了吧。
当然以后还会继续的。
减半警报器
这个 trick 能将
P7603 [THUPC2021] 鬼街
Description
鬼街上经常有灵异事件,每个灵异事件会导致编号为
每次输入的
Solution
考虑到每次的质因数个数函数
考虑优化上面的过程,我们发现警报器进行 “拿出来查询,然后再放回去无事发生” 这个过程的次数会非常多,这启发我们要减少这个过程的次数,而优化用到了一个性质:根据鸽巢原理,如果触发警报,那么所有小警报器计数的最大值一定大于
还是继续考虑 “拿出来查询,然后再放回去无事发生” 这一过程,我们刚刚对 “拿出来” 的条件进行了优化,而 “放回去” 还没有被我们优化。很自然地,我们会考虑放回去的时候通过 更新分阈值 的方法减少次数:即把旧的阈值减去这一趟所有小警报器的计数和,然后将这一新阈值用同样的方法设置分阈值丢回去。因为每一次最坏的情况是只有达到分阈值的那个小警报器计数为分阈值,其他小警报器计数为
这样一来我们执行 “拿出来查询,然后再放回去无事发生” 这一过程的次数只会有以
这个 trick 的理论分析就是这些,本质在于 设置阈值和更新阈值 所带来的复杂度优化。具体如何维护这一过程可以看代码。
Code
点击查看代码
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
#define mp make_pair
#define vi vector<int>
#define eb emplace_back
#define pli pair<LL, int>
#define fi first
#define se second
#define rep(rp,a,b) for(int rp=a;rp<=b;++rp)
#define per(bl,a,b) for(int bl=a;bl>=b;--bl)
#define segc int mid = L+R>>1, lc = now<<1, rc = lc|1
const int N = 1e5+5, MOD = 998244353, INF = 1e9;
inline LL read() {
LL x = 0, f = 1; char ch = 0;
while (!isdigit(ch)) {ch = getchar(); if (ch == '-') f = -1;}
while (isdigit(ch)) x = (x<<3)+(x<<1)+(ch^48), ch = getchar();
return f*x;
}
struct BLCK{
int id, opid; LL lim;//监视器编号、阈值编号与阈值
bool operator<(const BLCK &x) const {
return lim > x.lim;
}
};
int n, m, vis[N], cntn, nop[N];//nop i标记监视器i最新阈值编号
LL cnt[N];//从开始到现在闹鬼的次数
vi pri[N], ans;
priority_queue<BLCK> q[N];
pair<int, LL> mon[N];//每个监视器的监视目标和阈值
void sieve() {
rep (i, 2, n) {
if (!pri[i].empty()) continue ;
rep (j, 1, n/i) pri[i*j].eb(i);
}
}
LL getval(int x) {
LL ret = 0;
for (int j:pri[mon[x].fi]) ret += cnt[j];
return ret;
}
int main() {
n = read(), m = read();
sieve();
LL lans = 0;
while (m --) {
int opt = read(), x = read();
LL y = read()^lans;
if (!opt) {
for (int i:pri[x]) cnt[i] += y;
for (int i:pri[x]) {
while (!q[i].empty() && q[i].top().lim <= cnt[i]) {
BLCK x = q[i].top(); q[i].pop();
if (vis[x.id]) continue ;
if (nop[x.id] != x.opid) continue ;//类似dij的懒删除堆,忽略编号不为最新的阈值
LL newval = getval(x.id);
if (newval >= mon[x.id].se) vis[x.id] = 1, ans.eb(x.id);
else {
LL newlim = (mon[x.id].se-newval-1)/pri[mon[x.id].fi].size()+1;
++nop[x.id];
for (int j:pri[mon[x.id].fi])
q[j].push((BLCK){x.id, nop[x.id], newlim+cnt[j]});
}
}
}
sort(ans.begin(), ans.end());
printf("%d", (int)ans.size());
for (int i:ans) printf(" %d", i); puts("");
lans = ans.size();
ans.clear();
} else {
++cntn;
if (!y) {
ans.eb(cntn);
continue ;
}
LL lim = (y-1)/pri[x].size()+1, stp = 0;
++nop[cntn];
for (int i:pri[x]) {
q[i].push((BLCK){cntn, nop[cntn],lim+cnt[i]});
stp += cnt[i];
} mon[cntn] = mp(x, y+stp);
}
}
return 0;
}
Summary
很经典的 trick (according to Cust10),但是优化幅度不算大,所以对应的数据范围也是比较好看出来的。
猫树分治
我们理应认为它是一种 trick 。
正好最近模拟赛有一档猫树的部分分,于是就打算写这了,但是因为找不到原所以就随便贴道板题上来了。猫树分治可以对于静态的具有可并性的区间问题做到
GSS5 - Can you answer these queries V
Description
给定一个数列,每次询问求左端点在
Solution
非常基础的猫树应用,考虑维护若干个具有可并性的信息来回答询问,这里相比 GSS1 并没有新的信息需要维护,还是跨区间中点最大子段和,以及区间内最大子段和。
现在来考虑如何回答询问。首先是询问区间不交的情况,这样中间的所有数都是必取的,用前缀和维护不难。然后我们想要知道一个区间的最大后缀和和最大前缀和,而这用猫树维护也是非常轻松的。对于区间有交的情况,交的部分处理同 GSS1 ,然后后面就可以拆成两个区间后缀最大值加上区间前缀最大值,最后将这三个值取 max 即可。
代码实现也并不复杂。
Code
点击查看代码
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
#define mp make_pair
#define vi vector<int>
#define eb emplace_back
#define pli pair<LL, int>
#define fi first
#define se second
#define rep(rp,a,b) for(int rp=a;rp<=b;++rp)
#define per(bl,a,b) for(int bl=a;bl>=b;--bl)
#define segc int mid = L+R>>1, lc = now<<1, rc = lc|1
const int N = 1e5+5, MOD = 998244353, INF = 1e9;
inline int read() {
int x = 0, f = 1; char ch = 0;
while (!isdigit(ch)) {ch = getchar(); if (ch == '-') f = -1;}
while (isdigit(ch)) x = (x<<3)+(x<<1)+(ch^48), ch = getchar();
return f*x;
}
int lg[N<<2], pos[N], p[16][N], s[16][N], a[N], sum1[16][N], sum2[16][N];
void build(int now, int L, int R, int dep) {
if (L == R) return (void)(pos[L] = now);
int prep, sm;
segc;
p[dep][mid] = s[dep][mid] = prep = sm = sum1[dep][mid] = sum2[dep][mid] = a[mid], sm = max(sm, 0);
per (i, mid-1, L) {
prep += a[i], sm += a[i];
sum1[dep][i] = prep, sum2[dep][i] = sm;
s[dep][i] = max(s[dep][i+1], prep);
p[dep][i] = max(p[dep][i+1], sm);
sm = max(sm, 0);
}
p[dep][mid+1] = s[dep][mid+1] = prep = sm = sum1[dep][mid+1] = sum2[dep][mid+1] = a[mid+1], sm = max(sm, 0);
rep (i, mid+2, R) {
prep += a[i], sm += a[i];
sum1[dep][i] = prep, sum2[dep][i] = sm;
s[dep][i] = max(s[dep][i-1], prep);
p[dep][i] = max(p[dep][i-1], sm);
sm = max(sm, 0);
}
build(lc, L, mid, dep+1), build(rc, mid+1, R, dep+1);
}
inline int squery(int L, int R) {
if (L > R) return 0;
if (L == R) return a[L];
int dep = lg[pos[L]]-lg[pos[L]^pos[R]];
return sum1[dep][L]+sum1[dep][R];
}
inline int fquery(int L, int R) {
if (L == R) return a[L];
int dep = lg[pos[L]]-lg[pos[L]^pos[R]];
return max({p[dep][L], p[dep][R], s[dep][L]+s[dep][R]});
}
inline int prequery(int L, int R) {
if (L == R) return a[L];
int dep = lg[pos[L]]-lg[pos[L]^pos[R]];
return max(sum2[dep][L], sum1[dep][L]+s[dep][R]);
}
inline int sufquery(int L, int R) {
if (L == R) return a[L];
int dep = lg[pos[L]]-lg[pos[L]^pos[R]];
return max(sum2[dep][R], s[dep][L]+sum1[dep][R]);
}
inline int query(int L1, int R1, int L2, int R2) {
if (R1 < L2) return squery(R1+1, L2-1)+sufquery(L1, R1)+prequery(L2, R2);
else {
int ret = fquery(L2, R1);
if (L1 != L2) ret = max(ret, sufquery(L1, L2-1)+prequery(L2, R2));
if (R1 != R2) ret = max(ret, sufquery(L1, R1)+prequery(R1+1, R2));
return ret;
}
}
void solve() {
int n = read(), len = 2;
while (len < n) len <<= 1;
rep (i, 1, n) a[i] = read();
rep (i, 2, (len<<1)) lg[i] = lg[i>>1]+1;
build(1, 1, len, 1);
int q = read();
while (q --) {
int L1 = read(), R1 = read(), L2 = read(), R2 = read();
printf("%d\n", query(L1, R1, L2, R2));
}
}
int main() {
int T = read(); while (T --) solve();
return 0;
}
Summary
对于静态区间问题,只要维护若干区间可并信息,猫树就可以非常快速处理询问。特别是对于一些具有 dp 结构的问题,只要不带修上猫树就能得到一个复杂度不错的解。(只不过往前合并可能要重推一遍式子)
DP of DP
我们知道, dp 过程本身就是一个 DFA ,那么当题目形如 “求……值为……的方案数” 时,并且这一值需要通过 dp 求取时,那么我们就可以在这一 dp 的 DFA 之上进行 dp ,这就是 dp 套 dp 。
[CF924F]Minimal Subset Difference
Description
定义
Solution
首先,求取
不难发现,
从
对于多次询问,不妨对每次询问先差分,那么现在要解决的就是在
更多细节看代码就懂了。
Code
点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define pii pair<int, int>
#define int long long
#define mp make_pair
#define vi vector<int>
#define eb emplace_back
#define fi first
#define se second
#define rep(rp,a,b) for(int rp=a;rp<=b;++rp)
#define per(bl,a,b) for(int bl=a;bl>=b;--bl)
#define segc int mid = L+R>>1, lc = now<<1, rc = lc|1
const int N = 3e5+5, S = 2e4+5, MOD = 998244353, INF = 1e9;
inline int read() {
int x = 0, f = 1; char ch = 0;
while (!isdigit(ch)) {ch = getchar(); if (ch == '-') f = -1;}
while (isdigit(ch)) x = (x<<3)+(x<<1)+(ch^48), ch = getchar();
return f*x;
}
__int128 sta[S];
map<__int128, int> id;
int cnts, nxtp[S][10], dp[20][S][10];
__int128 getnxt(__int128 x, int tmp) {
__int128 ret = 0;
rep (i, 0, 81) if ((x>>(__int128)i)&((__int128)1)) {
if (i+tmp <= 81) ret |= ((__int128)1)<<(i+tmp);
ret |= ((__int128)1)<<abs(i-tmp);
} return ret;
}
void init() {//建立DFA
queue<pii> q;
q.push(mp(18, 1));
sta[++cnts] = 1;
id[1] = cnts;
while (!q.empty()) {
int dep = q.front().fi, xid = q.front().se; q.pop();
__int128 now = sta[xid];
if (!dep) continue ;
rep (i, 0, 9) {
__int128 nxt = getnxt(now, i);
if (!id[nxt]) {
sta[++cnts] = nxt;
id[nxt] = cnts;
q.push(mp(dep-1, cnts));
}
nxtp[xid][i] = id[nxt];
}
}
rep (i, 1, cnts) {
int mn1 = INF;
rep (j, 0, 81) if ((sta[i]>>(__int128)j)&((__int128)1)) {
mn1 = j;
break;
}
rep (j, mn1, 9) dp[0][i][j] = 1;
}
rep (i, 1, 17) {
rep (j, 1, cnts) {
rep (k, 0, 9) rep (l, 0, 9)
dp[i][j][k] += dp[i-1][nxtp[j][l]][k];
}
}
}
int num[20];
void getnum(int x) {rep (i, 1, 18) num[19-i] = x%10, x /= 10;}
int calc(int x, int lim) {
int ret = 0, st = 1;
if (x == 1000000000000000000ll) --x, ret += (lim>=1);
getnum(x);
rep (i, 1, 18) {
rep (j, 0, num[i]-1) ret += dp[18-i][nxtp[st][j]][lim];
st = nxtp[st][num[i]];
} ret += dp[0][st][lim];
return ret-1;//减去0
}
void solve() {
int L = read(), R = read(), lim = read();
printf("%lld\n", calc(R, lim)-calc(L-1, lim));
}
signed main() {
init();
int T = read(); while (T --) solve();
return 0;
}
Summary
题目形如 “求……值为……的方案数” 时,并且这一值需要通过 dp 求取时,那么我们就可以在这一 dp 的 DFA 之上进行 dp ,这就是 dp 套 dp 。(没什么好说的直接ctrl-c-v)
树上路径贡献转枚举边计算
虽然是很常见的 trick ,但蠢蠢的笔者总是忘记,于是就记下来了。(虽然放在这些强大的trick里有点不合适?)这是一种典的不能再典的计算路径和方法。
[CF1770E]Koxia and Tree
Description
有一棵
有
- 给每条边随机定向。
- 按照边的编号顺序,对于每一条边
,如果结点 上有蝴蝶而 没有,那么 上的蝴蝶就会飞到 上。 - 随机选取两只不同的蝴蝶,计算它们在无向树上的距离。
你需要给出第三步中距离的期望值。对
Solution
首先,令初始有蝴蝶的点集为
然后就是在计算完对答案的贡献后计算新的
化简后可以得到
代码把式子抄进去即可。
Code
点击查看代码
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
#define mp make_pair
#define vi vector<int>
#define eb emplace_back
#define pli pair<LL, int>
#define fi first
#define se second
#define rep(rp,a,b) for(int rp=a;rp<=b;++rp)
#define per(bl,a,b) for(int bl=a;bl>=b;--bl)
#define segc int mid = L+R>>1, lc = now<<1, rc = lc|1
const int N = 3e5+5, MOD = 998244353, INF = 1e9, I2 = 499122177;
inline void chkmin(int &x,int y) {x = y>x ? x : y;}
inline void chkmax(int &x,int y) {x = y>x ? y : x;}
inline int read() {
register int x = 0, f = 1;
register char ch = 0;
while(ch < 48 || ch > 57) {
ch = getchar();
if (ch == '-') f = -1;
}
while(ch >= 48 && ch <= 57) x = x*10+(ch^48), ch = getchar();
return f*x;
}
struct EDGE{
int u, v;
}e[N];
int c[N], p[N], dep[N];
vi G[N];
void dfs(int x, int par) {
dep[x] = dep[par]+1;
for (int y:G[x]) if (y != par) {
dfs(y, x);
c[x] += c[y];
}
}
int powM(int x, int y = MOD-2) {
int ret = 1;
while (y) {
if (y&1) ret = 1ll*ret*x%MOD;
x = 1ll*x*x%MOD, y >>= 1;
} return ret;
}
void solve() {
int n = read(), k = read();
rep (i, 1, k) {
int x = read();
c[x] = p[x] = 1;
}
rep (i, 1, n-1) {
e[i].u = read(), e[i].v = read();
G[e[i].u].eb(e[i].v), G[e[i].v].eb(e[i].u);
}
dfs(1, 0);
int ans = 0;
rep (i, 1, n-1) {
int u = e[i].u, v = e[i].v;
if (dep[u] > dep[v]) swap(u, v);
(ans += 1ll*p[u]*p[v]%MOD*c[v]%MOD*(k-c[v])%MOD) %= MOD;
(ans += 1ll*(1ll-p[u]+MOD)*(1ll-p[v]+MOD)%MOD*c[v]%MOD*(k-c[v])%MOD) %= MOD;
(ans += 1ll*p[u]*(1ll-p[v]+MOD)%MOD*(1ll*c[v]*(k-c[v])%MOD+1ll*(c[v]+1)*(k-c[v]-1)%MOD)%MOD*I2%MOD%MOD) %= MOD;
(ans += 1ll*(1ll-p[u]+MOD)*p[v]%MOD*(1ll*c[v]*(k-c[v])%MOD+1ll*(c[v]-1)*(k-c[v]+1)%MOD)%MOD*I2%MOD%MOD) %= MOD;
p[u] = p[v] = 1ll*(p[u]+p[v])*I2%MOD;
} printf("%d\n", 1ll*ans*powM(1ll*k*(k-1)/2%MOD)%MOD);
}
int main() {
int T = 1; while (T --) solve();
return 0;
}
Summary
碰到路径和,无脑拆成边。
极小 区间
区间
[THUPC 2024 初赛] 套娃
当时太菜了,场上甚至没有开这道题(批评我的两个隐身队友 Custlo 和 MuelsyeU)
Description
我们定义一个集合的
给定一个序列
- 对于
的所有长为 的子区间,求出这个子区间构成的数集的 。 - 对于求出的所有
,求出这个数集自己的 ,记为 。
求出序列
Solution
为了便于书写,我们这里用
首先我们刚需“极小
那么现在我们对这一结论进行证明:
引理: 对于极小
证明: 考虑反证,假设
我们先钦定一个极小
综上,我们就证明了极小
在有了这一性质后,我们就可以考虑去找到这些极小
考虑对于一个
找到极小
Code
点击查看代码
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
#define mp make_pair
#define vi vector<int>
#define eb emplace_back
#define pii pair<int, int>
#define fi first
#define se second
#define rep(rp,a,b) for(int rp=a;rp<=b;++rp)
#define per(bl,a,b) for(int bl=a;bl>=b;--bl)
#define segc int mid = L+R>>1, lc = now<<1, rc = lc|1
const int N = 1e5+5, MOD = 998244353, INF = 1e9;
template <typename T> inline void chkmin(T &x,T y) {x = y>x ? x : y;}
template <typename T> inline void chkmax(T &x,T y) {x = y>x ? y : x;}
inline int read() {
register int x = 0, f = 1;
register char ch = 0;
while(ch < 48 || ch > 57) {
ch = getchar();
if (ch == '-') f = -1;
}
while(ch >= 48 && ch <= 57) x = x*10+(ch^48), ch = getchar();
return f*x;
}
struct BLCK{
int ls, rs, val;
}tr[25*N];
int a[N], cntn, rt[N];
vi pos[N];
vector<pii> mex[N];
void build(int &now, int L, int R) {
tr[now=++cntn] = {0, 0, 0};
if (L == R) return ;
int mid = (L+R)>>1;
build(tr[now].ls, L, mid);
build(tr[now].rs, mid+1, R);
}
void update(int old, int &now, int L, int R, int pos, int val) {
tr[now=++cntn] = tr[old];
if (L == R) return tr[now].val = val, void();
int mid = (L+R)>>1;
pos <= mid ? update(tr[old].ls, tr[now].ls, L, mid, pos, val) : update(tr[old].rs, tr[now].rs, mid+1, R, pos, val);
tr[now].val = min(tr[tr[now].ls].val, tr[tr[now].rs].val);
}
int query(int now, int L, int R, int x) {
if (L == R) return L;
int mid = (L+R)>>1;
return tr[tr[now].ls].val < x ? query(tr[now].ls, L, mid, x) : query(tr[now].rs, mid+1, R, x);
}
int buc[N];
vi ad[N], dl[N];
void add(int ql, int L, int R, int qr, int x) {ad[R-L+1].eb(x); dl[qr-ql+2].eb(x);}
int main() {
int n = read(), cnta = n+3;
build(rt[0], 1, cnta);
rep (i, 0, cnta) pos[i].eb(0);
rep (i, 1, n) a[i] = read(), pos[a[i]].eb(i), update(rt[i-1], rt[i], 0, cnta, a[i], i);
// rep (i, 1, cntn) printf("%d %d %d %d\n", i, tr[i].ls, tr[i].rs, tr[i].val);
// rep (i, 1, n) rep (j, i, n) printf("%d %d %d\n", i, j, query(rt[j], 0, cnta, i));
rep (i, 0, cnta) pos[i].eb(n+1);
rep (i, 1, n) a[i] ? mex[0].eb(mp(i, i)) : mex[1].eb(mp(i, i));
rep (i, 1, cnta) {
for (int j = 0; j < mex[i-1].size(); ++j) {
int L = mex[i-1][j].fi, R = mex[i-1][j].se;
int ql = *(--lower_bound(pos[i-1].begin(), pos[i-1].end(), L)), qr = *(upper_bound(pos[i-1].begin(), pos[i-1].end(), R));
if (ql) mex[query(rt[R], 0, cnta, ql)].eb(mp(ql, R));
if (qr <= n) mex[query(rt[qr], 0, cnta, L)].eb(mp(L, qr));
}
sort(mex[i].begin(), mex[i].end(),[&](pii x,pii y){return x.fi==y.fi?x.se<y.se:x.fi>y.fi;});
vector<pii>G;
int las = 2e9;
for (int j = 0; j < mex[i].size(); ++j) {
int L = mex[i][j].fi, R = mex[i][j].se;
if (las > R) G.push_back(mp(L, R)), las = R;
} swap(G, mex[i]);
}
rep (i, 0, cnta) for (int j = 0; j < mex[i].size(); ++j) {
int L = mex[i][j].fi, R = mex[i][j].se;
// printf("%d %d %d\n", i, L, R);
add(*(--lower_bound(pos[i].begin(), pos[i].end(), L))+1, L, R, *upper_bound(pos[i].begin(), pos[i].end(), R)-1, i);
}
set<int>s;
rep (i, 0, n) buc[i] = 0, s.insert(i);
rep (k, 1, n) {
for(int j:ad[k]) if(!buc[j]++) s.erase(j);
for(int j:dl[k]) if(!--buc[j]) s.insert(j);
printf("%d ", *s.begin());
}
return 0;
}
莫队
作为人丁稀薄的根号算法的一员,有必要写进来,但实在是不想放什么题来讲了。(懒
普通莫队
莫队一般用来处理 区间询问、询问可离线、能通过移动区间界更新答案 的题目。
对于一个长度为
- 那么下一次询问与当次询问的
在同一块内,那么 移动距离最多只有 ,总共的移动次数是 的。 的块间移动最多有块数 次,每次距离最多为 ,这影响不到复杂度。- 每一块的询问,
都会最多走 的距离,总共是 的
综合以上,以这种顺序处理询问的复杂度是
回滚莫队
当仅有扩大区间大小更新答案或减小区间大小更新答案比较容易时,就需要考虑避免做另一种较难实现的操作,而处理这类问题就需要使用回滚莫队。之所以称作回滚莫队,是因为指针的移动并不是连续的,每次询问左端点的状态都将回滚到所在块的一端,复原答案,这样就只需要往一个方向移动指针
相对于普通莫队复杂度上并无不同,至于实现,也许看代码段更为直观:
只扩大区间莫队
rep (blk, 0, n-1) {
int L = (blk+1)*B, R = (blk+1)*B+1;
getans(L, R); //计算ans及相关维护信息
while (que[p].l <= (blk+1)*B) {
int ql = que[p].l, qr = que[p].r; ++p;
while (R < qr) add(R++);
int tmp = ans;
while (L > ql) add(L--);
que[p].ans = ans;
ans = tmp; //复原答案
roll(L); //复原L及其维护信息至L=(blk+1)*B的状态
}
}
只减小区间莫队
rep (blk, 0, n-1) {
int L = blk*B+1, R = n;
getans(L, R); //计算ans及相关维护信息
while (que[p].l <= (blk+1)*B) {
int ql = que[p].l, qr = que[p].r; ++p;
while (R > qr) del(R--);
int tmp = ans;
while (L < ql) del(L++);
que[p].ans = ans;
ans = tmp; //复原答案
roll(L); //复原L及其维护信息至L=blk*B+1的状态
}
}
特别地,为了防止出现
带修莫队
带修就是加入时间维,那么我们还是考虑分块,不过这次我们对
- 那么下一次询问与当次询问的
在同一块内,那么 移动距离最多只有 ,总共的移动次数是 的。 的块间移动最多有块数 次,每次距离最多为 ,这还是影响不到复杂度。- 每一块的询问,时间
都会最多走 的距离,总共是 的.
那么复杂度是 比暴力快就是赢)
树上莫队
子树
拍成
链
有些细节但不多,先钦定
上面这部分确定了链所在的
Summary
这么多应该够了吧……挺久没考的了,说不定就考了呢(
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】