线段树分治略解杂题解析

news/2024/10/11 21:26:25

可能做到好题之后会再更新吧。

总叙

线段树与离线询问结合技巧又被称为线段树分治。

使用线段树分治维护的信息通常会在某一个时间段内出现,要求在离线的前提下回答某一个时刻的信息并,则可以考虑使用线段树分治的技巧。

以下是线段树分治的基本模板:

change 将信息按时间段覆盖在线段树上,query 通过不断合并线段树上节点维护的信息达到在叶子结点满足信息不重不漏。

struct type_name{};
struct del_tmp{};
struct node{};
struct segment_tree{type_name name;stack<del_tmp>st;struct segment_tree_node{vector<node>v;}t[N<<2];inline int ls(int x){return x<<1;};inline int rs(int x){return x<<1|1;};
#define mid ((l+r)>>1)void change(int p,int l,int r,int re_l,int re_r,node val){if(re_l<=l&&r<=re_r)	return t[p].v.push_back(val),void();else{if(re_l<=mid)	change(ls(p),l,mid,re_l,re_r,val);if(mid<re_r)	change(rs(p),mid+1,r,re_l,re_r,val);}}void query(int p,int l,int r){int tp=st.tp;for(auto it:t[p].v){//do sonething to merge information}if(l==r)	//do something to count answerelse	query(ls(p),l,mid),query(rs(p),mid+1,r);while(st.tp>tp){del_tmp tmp=st.top();st.pop();//do something to delete}}
}T;

时间复杂度分析

设总操作数为 $n$,总时间为 $m$,所使用的数据结构满足合并信息的复杂度为 $O(M(n))$,使用栈撤销的时间复杂度为 $O(T(n))$,在叶子结点统计答案的复杂度为 $O(C(n))$。

则每一个操作都在线段树上被分割为 $O(\log{m})$ 段,总共有 $O(n\log{m})$ 段,每一段需要合并一次,删除一次,总复杂度为 $O(n\log{m}(T(n)+M(n)))$。

加上统计答案则为 $O(n\log{m}(T(n)+M(n))+mC(n))$,通常,在题目中,我们限定 $n,m$ 同级。

二分图 /【模板】线段树分治

tips:扩展域并查集判二分图属于线段树分治以外的知识点,请自行了解。

用线段树分治解决问题需要思考以下问题:

  1. 线段树维护什么?

在这里线段树维护的是边,通过边出现的时间将其加入线段树。对应模板中的 node 结构体。

  1. 使用什么数据结构?

这里是按秩合并的并查集,对应模板中的 type_name 结构体。

tips:为什么不能使用路径压缩?

因为路径压缩是均摊复杂度,即 $T(n)$ 可以达到 $O(n)$。会导致撤销的复杂度炸掉。

template<int N>struct DSU{int fa[N],siz[N];void clear(int n){for(int i=1;i<=n;i++)	fa[i]=i,siz[i]=1;}int find(int x){return (fa[x]==x?x:find(fa[x]));}del_tmp merge(int x,int y){int X=find(x),Y=find(y);if(siz[X]>siz[Y])	swap(X,Y);del_tmp ret={X,siz[X]};siz[Y]+=siz[X],fa[X]=Y;return ret;}bool same(int x,int y){return find(x)==find(y);}
};

3.怎么撤销操作?

通常是使用栈,撤销用模板中的 del_tmp 实现。

while(st.tp>tp){del_tmp tmp=st.top();st.pop();dsu.siz[dsu.fa[tmp.num]]-=tmp.siz;dsu.fa[tmp.num]=tmp.num;
}

4.如何合并信息?

这道题就是使用并查集的 merge 函数完成,在 merge 过程中判断是否合法。

最后在叶子结点根据是否合法输出答案就行了。

code:

struct del_tmp{int num,siz;};
template<int N>struct DSU{int fa[N],siz[N];void clear(int n){for(int i=1;i<=n;i++)	fa[i]=i,siz[i]=1;}int find(int x){return (fa[x]==x?x:find(fa[x]));}del_tmp merge(int x,int y){int X=find(x),Y=find(y);if(siz[X]>siz[Y])	swap(X,Y);del_tmp ret={X,siz[X]};siz[Y]+=siz[X],fa[X]=Y;return ret;}bool same(int x,int y){return find(x)==find(y);}
};
struct edge{int u,v;};
struct segment_tree{DSU<N<<1> dsu;my_stack<del_tmp,N<<1>st;segment_tree(){dsu.clear((N<<1)-1);}struct segment_tree_node{vector<edge>v;}t[N<<2];inline int ls(int x){return x<<1;};inline int rs(int x){return x<<1|1;};
#define mid ((l+r)>>1)void change(int p,int l,int r,int re_l,int re_r,edge val){if(re_l<=l&&r<=re_r)	return t[p].v.push_back(val),void();else{if(re_l<=mid)	change(ls(p),l,mid,re_l,re_r,val);if(mid<re_r)	change(rs(p),mid+1,r,re_l,re_r,val);}}void query(int p,int l,int r){bool ok=1;int tp=st.tp;for(auto it:t[p].v){if(dsu.same(it.u,it.v)){ok=0;for(int i=l;i<=r;i++)	cout<<"No\n";break;}//小优化,如果在这时已经不合法了,那么到了叶子结点只会增加边,仍然不合法。可以节约递归和删除成本st.push(dsu.merge(it.u+N,it.v));st.push(dsu.merge(it.v+N,it.u));}if(ok){if(l==r)	cout<<"Yes\n";else	query(ls(p),l,mid),query(rs(p),mid+1,r);//一定不能写反}while(st.tp>tp){del_tmp tmp=st.top();st.pop();dsu.siz[dsu.fa[tmp.num]]-=tmp.siz;dsu.fa[tmp.num]=tmp.num;}}
}T;
int main(){ios::sync_with_stdio(false);cin.tie(nullptr);cout.tie(nullptr);cin>>n>>m>>k;for(int i=1,u,v,l,r;i<=m;i++){cin>>u>>v>>l>>r;if(l!=r)	T.change(1,1,k,l+1,r,{u,v});//按时间加边}T.query(1,1,k);
}

在本题中,$T(n)=O(1)$,$M(n)=O(\log{n})$,$C(n)=O(1)$,所以总的时间复杂度为 $O(m\log{k}(T(n)+M(n))+kC(n))=O(m\log{k}\log{n})$。

tips:本题的第 $i$ 个时间段代表的是开区间 $(i-1,i)$。

Fairy

*2900。

这道题也提示了线段树分治的常见思想:控制边的出现时间。

题目中要求去掉某一条边,这个好办,第 $i$ 条边在第 $i$ 时刻不出现就可以了。

加边:

cin>>n>>m;
for(int i=1,u,v;i<=m;i++){cin>>u>>v;if(i!=1)	T.change(1,1,m,1,i-1,{u,v});if(i!=m)	T.change(1,1,m,i+1,m,{u,v});	
}

其余的部分也就大同小异了。

最小mex生成树

要让 $\operatorname{mex}=k$ 只要没有权值为 $k$ 的边不就行了。

要求去掉某一堆边,这个好办,让权值为 $i$ 的边在第 $i$ 时刻不出现就可以了。

在叶子结点判断剩余的边是否能构成原图的生成树即可。

query:

void query(int p,int l,int r){int tp=st.tp;for(auto it:t[p].v)	st.push(dsu.merge(it.u,it.v));if(dsu.siz[dsu.find(1)]==n)	ans=min(ans,l);else if(l!=r)	query(ls(p),l,mid),query(rs(p),mid+1,r);while(st.tp>tp){del_tmp tmp=st.top();st.pop();if(!tmp.num)	continue;dsu.siz[dsu.fa[tmp.num]]-=tmp.siz;dsu.d[dsu.fa[tmp.num]]-=tmp.d;dsu.fa[tmp.num]=tmp.num;}
}

注意边权可能为 $0$。

Communication Towers

*2700

看见一个点只在某段时间出现,果断线段树分治。

通过可撤销并查集维护连通性。

这里涉及到一个新问题,也是线段树分治的一个trick:标记维护。

注意到需要给 $1$ 所在的树上的每个结点打上标记,这并不好办。

考虑只给树的根结点增加标记。在撤销操作(其实也是分裂子树)时下传标记。

但这样会出现一种情况,一个子树后来连接上带有标记的根(但这个根现在不与 $1$ 相连),则会导致新的子树也被统计进入答案。

考虑一个解决方法,在连接时减去根的标记,撤销时加上。

这样,只要根结点的标记在途中没有变化,就不会多下传。

//merge
del_tmp merge(int x,int y){int X=find(x),Y=find(y);if(X==Y)	return{0,0};if(d[X]>d[Y])	swap(X,Y);del_tmp ret={X,d[X]==d[Y]};d[Y]+=(d[X]==d[Y]),tag[X]-=tag[Y],fa[X]=Y;return ret;
}
//delete
while(st.tp>tp){del_tmp tmp=st.top();st.pop();if(!tmp.num)	continue;dsu.tag[tmp.num]+=dsu.tag[dsu.fa[tmp.num]];dsu.d[dsu.fa[tmp.num]]-=tmp.d;dsu.fa[tmp.num]=tmp.num;
}

下传标记的问题至此完美解决,剩的就是普通线段树分治的操作了。

code

#include<bits/stdc++.h>
using namespace std;
const int N=2e5+5;
int n,m,mx,l[N],r[N],ans=0x3f3f3f3f;
template<typename T,int siz> struct my_stack{T st[siz];int tp=0;void push(T x){st[++tp]=x;}void pop(){if(!tp)	cerr<<"THE STACK IS EMPTY!\n";else	tp--;}T top(){if(!tp)	cerr<<"THE STACK IS EMPTY!\n";else	return st[tp];}void clear(){tp=0;}bool empty(){return !tp;}
};
struct del_tmp{int num,d;};
template<int N>struct DSU{int fa[N],tag[N],d[N];void clear(int n){for(int i=1;i<=n;i++)	fa[i]=i,tag[i]=0,d[i]=1;}int find(int x){return (fa[x]==x?x:find(fa[x]));}del_tmp merge(int x,int y){int X=find(x),Y=find(y);if(X==Y)	return{0,0};if(d[X]>d[Y])	swap(X,Y);del_tmp ret={X,d[X]==d[Y]};d[Y]+=(d[X]==d[Y]),tag[X]-=tag[Y],fa[X]=Y;return ret;}bool same(int x,int y){return find(x)==find(y);}
};
struct edge{int u,v;};
struct segment_tree{DSU<N> dsu;my_stack<del_tmp,N<<1>st;segment_tree(){dsu.clear(N-1);}struct segment_tree_node{vector<edge>v;}t[N<<2];inline int ls(int x){return x<<1;};inline int rs(int x){return x<<1|1;};
#define mid ((l+r)>>1)void change(int p,int l,int r,int re_l,int re_r,edge val){if(re_l<=l&&r<=re_r)	return t[p].v.push_back(val),void();else{if(re_l<=mid)	change(ls(p),l,mid,re_l,re_r,val);if(mid<re_r)	change(rs(p),mid+1,r,re_l,re_r,val);}}void query(int p,int l,int r){//		cerr<<p<<" "<<l<<" "<<r<<"\n";int tp=st.tp;for(auto it:t[p].v)	st.push(dsu.merge(it.u,it.v));if(l==r)	dsu.tag[dsu.find(1)]++;else	query(ls(p),l,mid),query(rs(p),mid+1,r);while(st.tp>tp){del_tmp tmp=st.top();st.pop();if(!tmp.num)	continue;dsu.tag[tmp.num]+=dsu.tag[dsu.fa[tmp.num]];dsu.d[dsu.fa[tmp.num]]-=tmp.d;dsu.fa[tmp.num]=tmp.num;}}
}T;
int main(){ios::sync_with_stdio(false);cin.tie(nullptr);cout.tie(nullptr);cin>>n>>m;for(int i=1;i<=n;i++)	cin>>l[i]>>r[i],mx=max(mx,r[i]);for(int i=1,u,v;i<=m;i++){cin>>u>>v;if(max(l[u],l[v])<=min(r[u],r[v]))	T.change(1,1,mx,max(l[u],l[v]),min(r[u],r[v]),{u,v});}T.query(1,1,mx);for(int i=1;i<=n;i++){if(T.dsu.tag[i])	cout<<i<<" ";}
}

时间复杂度 $O(m\log{n}\log{R_{imax}})$。

Unique Occurrences

*2300

将同颜色的边去掉后,每条此颜色的边两边的连通块大小之积即为这条边的贡献。

线段树分治+可撤销并查集即可。

洞穴勘测

将操作与查询一起放进线段树里分治。递归到叶子结点时如果是询问就回答,不是就不管。

离线统计边的存在时间,每条边依次插入线段树即可。

query:

void query(int p,int l,int r){int tp=st.tp;for(auto it:t[p].v)	st.push(dsu.merge(it.u,it.v));if(l==r){if(q[l].u)	cout<<(dsu.same(q[l].u,q[l].v)?"Yes\n":"No\n");//有改变的地方}else	query(ls(p),l,mid),query(rs(p),mid+1,r);while(st.tp>tp){del_tmp tmp=st.top();st.pop();if(!tmp.num)	continue;dsu.d[dsu.fa[tmp.num]]-=tmp.d;dsu.fa[tmp.num]=tmp.num;}
}

add:

cin>>n>>m;
for(int i=1,u,v;i<=m;i++){cin>>s>>u>>v;if(u>v)	swap(u,v);if(s[0]=='C'){int tmp=ma[{u,v}];if(tmp)	la[tmp]=i;else	ma[{u,v}]=++cnt,e[cnt]={u,v},la[cnt]=i;}else if(s[0]=='D'){int tmp=ma[{u,v}];T.change(1,1,m,la[tmp],i-1,e[tmp]);la[tmp]=0;}else	q[i]={u,v};
}
for(int i=1;i<=cnt;i++)	if(la[i])	T.change(1,1,m,la[i],m,e[i]);

捉迷藏

问题变为了维护白点的直径,将每个点是白点的时间段插入线段树进行分治。当然你也可以用点分树

直径的更新:
对于一个集合 $S$ 和只有一个点的集合 ${P}$。若集合 $S$ 的直径为 $(U,V)$。则点集 $S∩{P}$ 的直径只可能为 $(U,V)$,$(U,P)$ 或 $(V,P)$。

然后记录直径的两端点做到撤销,就可以线段树分治了。

A Museum Robbery

*2800

一件展品出现有时间限制,很明显的线段树分治。

看 $s(m)$ 的计算方式,大概就是线段树分治套背包了。

令 $dp_i$ 为总重量为 $i$ 时的最大价值,问题就转化为一个经典的 01 背包问题了。统计答案时做一个前缀和就可以了。

背包撤销时可以用退背包,也可以 $O(n)$ 记录修改之前的背包状态。我这里用的是后者。

code

#include<bits/stdc++.h>
using namespace std;
const long long N=1.5e4+5,M=3e4+5,W=1e3+5,p=1e7+19,mod=1e9+7;
int n,m,k,cnt,la[N];
long long power[W];
bool q[M];
struct exhabit{int c,w;}a[N];
struct segment_tree{int dp[W];struct segment_tree_node{vector<exhabit>v;}t[M<<2];inline int ls(int x){return x<<1;};inline int rs(int x){return x<<1|1;};
#define mid ((l+r)>>1)void change(int p,int l,int r,int re_l,int re_r,exhabit val){if(re_l<=l&&r<=re_r)	return t[p].v.push_back(val),void();else{if(re_l<=mid)	change(ls(p),l,mid,re_l,re_r,val);if(mid<re_r)	change(rs(p),mid+1,r,re_l,re_r,val);}}void query(int p,int l,int r){vector<int> pre(k+1,0);for(int i=1;i<=k;i++)	pre[i]=dp[i];for(auto it:t[p].v){for(int i=k;i>=it.w;i--)	dp[i]=max(dp[i],dp[i-it.w]+it.c);}if(l==r){if(q[l]){long long ma=-1,ans=0;for(int i=1;i<=k;i++){ma=max(ma,1ll*dp[i]);ans=(ans+ma*power[i-1]%mod)%mod;}cout<<ans<<"\n";}}else	query(ls(p),l,mid),query(rs(p),mid+1,r);for(int i=1;i<=k;i++)	dp[i]=pre[i];}
}T;
int main(){ios::sync_with_stdio(false);cin.tie(nullptr);cout.tie(nullptr);cin>>n>>k;cnt=n;power[0]=1;for(int i=1;i<=k;i++)	power[i]=power[i-1]*p%mod;for(int i=1;i<=n;i++)	cin>>a[i].c>>a[i].w,la[i]=1;cin>>m;for(int i=1,opt,num;i<=m;i++){cin>>opt;if(opt==1){cnt++;cin>>a[cnt].c>>a[cnt].w;la[cnt]=i;}else if(opt==2){cin>>num;T.change(1,1,m,la[num],i,a[num]);la[num]=0;}else	q[i]=1;}for(int i=1;i<=cnt;i++){if(la[i])	T.change(1,1,m,la[i],m,a[i]);}T.query(1,1,m);
}

时间复杂度 $O((n+m)k\log{m}+km)$。

Painting Edges

*3300

真史登场

发现 $k\le50$ 然而空间不足以开 $50$ 棵线段树,那就只能是一个线段树里面 $50$ 个并查集了。

先离线处理每条边颜色可能会更改的时间点(因为操作不合法时,颜色不会更改,所以不能直接插入),

然后线段树分治,在分治时分别添加各个颜色的边,如果有一个并查集不满足要求,整个子树都不合法。

回答完一个询问时,根据这个询问的信息获得边的颜色信息(即是否会更改),然后根据之前记录下的时间区间现场加到线段树后面去。

for(auto it:t[p].v){if(dsu[it.c].same(it.u,it.v)){ok=0;for(int i=l;i<=r;i++){cout<<"NO\n";if(e[Q[i].num].c&&i+1<nxt[i])	change(1,1,q,i+1,nxt[i]-1,e[Q[i].num]);}break;}st.push(dsu[it.c].merge(it.u+N,it.v));st.st[st.tp].c=it.c;st.push(dsu[it.c].merge(it.v+N,it.u));st.st[st.tp].c=it.c;
}
if(ok){if(l==r){cout<<"YES\n";e[Q[l].num].c=Q[l].c;if(l+1<nxt[l])	change(1,1,q,l+1,nxt[l]-1,e[Q[l].num]);}else	query(ls(p),l,mid),query(rs(p),mid+1,r);
}

其中 nxt 是下一次询问这条边的时间(如果没有下一次询问就是 $q+1$)。

需要注意的是,无论修改后是否合法,属于这个询问的叶子结点上加入的这条边一定是修改过后的颜色,因为你需要用修改过后的颜色去判断它合不合法。

for(int i=1;i<=q;i++){cin>>Q[i].num>>Q[i].c;nxt[last[Q[i].num]]=i,last[Q[i].num]=i;//辅助记录修改时间区间T.change(1,1,q,i,i,{e[Q[i].num].u,e[Q[i].num].v,Q[i].c});//强制加入修改后的颜色
}

代码实现部分完毕,但是这道题还卡空间。

tips1:如果你的做法里含有 STL 的 queue 并且数量为 $O(n)$ 级别。

请使用合理的方式或使用 queue<int,list<int>> 更换掉,否则定义 queue 的额外内存会让你MLE。

tips2: 请计算好需要使用的空间,尽量不要多开任何无意义的空间。

code

#include<bits/stdc++.h>
using namespace std;
const int N=500005;
int n,m,q,k;
template<typename T,int siz> struct my_stack{T st[siz];int tp=0;void push(T x){st[++tp]=x;}void pop(){if(!tp)	cerr<<"THE STACK IS EMPTY!\n";else	tp--;}T top(){if(!tp)	cerr<<"THE STACK IS EMPTY!\n";else	return st[tp];}void clear(){tp=0;}bool empty(){return !tp;}
};
struct del_tmp{int num,d;short int c;};
template<int N>struct DSU{int fa[N],d[N];void clear(int n){for(int i=1;i<=n;i++)	fa[i]=i,d[i]=1;}int find(int x){return (fa[x]==x?x:find(fa[x]));}del_tmp merge(int x,int y){int X=find(x),Y=find(y);if(d[X]>d[Y])	swap(X,Y);del_tmp ret={X,d[X]==d[Y],0};d[Y]+=(d[X]==d[Y]),fa[X]=Y;return ret;}bool same(int x,int y){return find(x)==find(y);}
};
struct edge{int u,v;short int c;}e[N];
struct query{int num;short int c;}Q[N];
int nxt[N],last[N];
struct segment_tree{DSU<N<<1> dsu[51];my_stack<del_tmp,N<<1>st;void clear(int k){for(int i=1;i<=k;i++)	dsu[i].clear((N<<1)-1);}struct segment_tree_node{vector<edge>v;}t[N<<2];inline int ls(int x){return x<<1;};inline int rs(int x){return x<<1|1;};
#define mid ((l+r)>>1)void change(int p,int l,int r,int re_l,int re_r,edge val){if(re_l<=l&&r<=re_r)	return t[p].v.push_back(val),void();else{if(re_l<=mid)	change(ls(p),l,mid,re_l,re_r,val);if(mid<re_r)	change(rs(p),mid+1,r,re_l,re_r,val);}}void query(int p,int l,int r){bool ok=1;int tp=st.tp;for(auto it:t[p].v){if(dsu[it.c].same(it.u,it.v)){ok=0;for(int i=l;i<=r;i++){cout<<"NO\n";if(e[Q[i].num].c&&i+1<nxt[i])	change(1,1,q,i+1,nxt[i]-1,e[Q[i].num]);}break;}st.push(dsu[it.c].merge(it.u+N,it.v));st.st[st.tp].c=it.c;st.push(dsu[it.c].merge(it.v+N,it.u));st.st[st.tp].c=it.c;}if(ok){if(l==r){cout<<"YES\n";e[Q[l].num].c=Q[l].c;if(l+1<nxt[l])	change(1,1,q,l+1,nxt[l]-1,e[Q[l].num]);}else	query(ls(p),l,mid),query(rs(p),mid+1,r);}while(st.tp>tp){del_tmp tmp=st.top();st.pop();dsu[tmp.c].d[dsu[tmp.c].fa[tmp.num]]-=tmp.d;dsu[tmp.c].fa[tmp.num]=tmp.num;}}
}T;
int main(){ios::sync_with_stdio(false);cin.tie(nullptr);cout.tie(nullptr);cin>>n>>m>>k>>q;for(int i=1;i<=m;i++)	cin>>e[i].u>>e[i].v;for(int i=1;i<=q;i++)	cin>>Q[i].num>>Q[i].c,nxt[last[Q[i].num]]=i,last[Q[i].num]=i,T.change(1,1,q,i,i,{e[Q[i].num].u,e[Q[i].num].v,Q[i].c});for(int i=1;i<=m;i++)	nxt[last[i]]=q+1;T.clear(k);T.query(1,1,q);
}

时间复杂度 $O(q\log{q}\log{n})$。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.ryyt.cn/news/70399.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈,一经查实,立即删除!

相关文章

多校A层冲刺NOIP2024模拟赛05

A. 好数(number) 很容易想到 \(n^3\) 枚举两个,看第三个是否出现,扩展一下,枚举一个,看剩下需要的和是否出现过,提前处理出两两的和 和最早能合出这个数的位置,复杂的 \(O(n^2)\)点击查看代码 #include<bits/stdc++.h> const int maxn=5000+10; using namespace …

二分图全面学习笔记

二分图全面学习笔记 Part1:二分图的定义与判定方法 首先,我们要知道二分图的定义是什么。 二分图的定义 ​ 如果一张无向图的 \(n\) 个节点可以分成 \(A,B\) 两个不相交的非空集合,并且同一个集合之中的两个点之间没有边相连接,那么称该无向图为二分图 (Bipartite Graph) …

解密prompt系列40. LLM推理scaling Law

OpenAI的O-1出现前,其实就有大佬开始分析后面OpenAI的技术路线,其中一个方向就是从Pretrain-scaling,Post-Train-scaling向Inference Scaling的转变,这一章我们挑3篇inference-scaling相关的论文来聊聊,前两篇分别从聚合策略和搜索策略来优化广度推理,最后一篇全面的分析…

浅谈一类动态开点线段树优化 - DEST树

前言 线段树,是一种优秀的数据结构,其应用极为广泛。其中,动态开点值域线段树,配合上线段树合并,甚至能替代或超越平衡树。但是,这种线段树的树高与值域相关,很容易产生四五倍常数。无论考虑时间或空间复杂度,这样的树都不算优。那么,我们是否能想办法优化它呢? 优化…

『模拟赛』多校A层冲刺NOIP2024模拟赛05

『模拟赛记录』多校A层冲刺NOIP2024模拟赛05Rank 烂。A. 好数(number) 签,唐,没签上。 考虑之前几次类似的题方法都是选 \(k-1\) 的方案存起来以使总复杂度除以一个 \(n\),故考虑记共 \(n^2\) 个两两组合之和,枚举当前点 \(i\) 前面的点,找是否有值为它们的差的组合,复…

vscode 搭建 python 开发环境

1、安装 python 插件 2、按 Ctrl + Shift + P,在打开的输入框中输入 Python: Select Interpreter 搜索,选择 Python 解析器3、运行:ctrl + F5,调试:F5 4、查看安装包列表pip list5、安装外部库pip install xxx

idea - properties文件unicode中文显示

💖简介 idea中properties文件中文默认展示为unicode码unicode 中文展示为 \u开头的ASCII🌟调整中文显示 idea -> settings -> Editor -> File EncodingsGlobal Encoding 选择 UTF-8 Project Encoding 选择 UTF-8 Default encoding for properties files 选择 UTF-…

基于MPPT的太阳能光伏电池simulink性能仿真,对比扰动观察法,增量电导法,恒定电压法

1.课题概述在simulink中,实现基于MPPT的太阳能光伏电池,对比对比扰动观察法,增量电导法,恒定电压法三种MPPT方法。2.系统仿真结果局部放大可以看到三个算法的对比结果如下:3.核心程序与模型 版本:MATLAB2022a 4.系统原理简介太阳能光伏(PV)系统是一种将太阳能转换为电能的…