Codeforces Round #831 (Div. 1 + Div. 2) 题解

本文网址:https://www.cnblogs.com/zsc985246/p/17086280.html ,转载请注明出处。

比赛题目非常有趣,推荐!

I 题暂时没有代码,后面可能会补。

2023/2/7update:更新 G 题题解和 I 题思路。

传送门

Codeforces Round #831 (Div. 1 + Div. 2)

A.Factorise N+M

题目大意

多组测试。每次输入一个质数 A,输出任意一个使 A+B 不是质数的 B

注意 B1

思路

直接输出 n

代码实现

#include<bits/stdc++.h>
#define ll long long
#define For(i,a,b) for(ll i=(a);i<=(b);++i)
#define Rep(i,a,b) for(ll i=(a);i>=(b);--i)
const ll N=1e6+10;
using namespace std;

ll n;

void mian(){
	scanf("%lld",&n);
	printf("%lld\n",n);
}

int main(){
	int T=1;
	scanf("%d",&T);
	while(T--)mian();
	return 0;
}

B.Jumbo Extra Cheese 2

题目大意

你有 n 个长方形,大小是 ai×bi,现在你需要把它们放到一起,可以旋转,求周长最小值。

思路

构造题。把所有的长方形的最短边作为底边,然后直接按高度排列,答案就是 2 × 每个长方形最短边之和 + 2 × 所有长方形中的最长边。

代码实现

#include<bits/stdc++.h>
#define ll long long
#define For(i,a,b) for(ll i=(a);i<=(b);++i)
#define Rep(i,a,b) for(ll i=(a);i>=(b);--i)
const ll N=1e6+10;
using namespace std;

ll n,m;
ll a[N],b[N];

void mian(){
	
	ll ans=0,maxx=0;
	scanf("%lld",&n);
	For(i,1,n){
		scanf("%lld",&a[i]);
		scanf("%lld",&b[i]);
		ans+=min(a[i],b[i])*2;
		maxx=max(maxx,max(a[i],b[i]));
	}
	
	ans+=maxx*2;
	
	printf("%lld\n",ans);
	
}

int main(){
	int T=1;
	scanf("%d",&T);
	while(T--)mian();
	return 0;
}

C.Bricks and Bags

题目大意

给定 n 个石头和三个空背包,将 n 个石头放入三个背包中。从三个背包中拿出三个石头,假设拿出的石头的质量分别是 a,b,c,分数就是 |ab|+|bc|,你需要使最终的分数的最小值最大。输出分数最小值的最大可能值。

思路

先把所有的石头排序。可以发现中间的背包摆放的石头必定是一段前缀或一段后缀。枚举断点计算极值即可。

代码实现

#include<bits/stdc++.h>
#define ll long long
#define For(i,a,b) for(ll i=(a);i<=(b);++i)
#define Rep(i,a,b) for(ll i=(a);i>=(b);--i)
const ll N=1e6+10;
using namespace std;

ll n,m;
ll a[N],b[N];

void mian(){
	
	ll ans=0;
	scanf("%lld",&n);
	For(i,1,n){
		scanf("%lld",&a[i]);
	}
	
	sort(a+1,a+n+1);
   
	//一段前缀
	For(i,1,n-2){
		ans=max(ans,a[i+1]-a[i]+a[n]-a[i]);//一边放最大值,一边放i+1
	}
	//一段后缀
	For(i,3,n){
		ans=max(ans,a[i]-a[1]+a[i]-a[i-1]);//一边放最小值,一边放i-1
	}
	
	printf("%lld\n",ans);
	
}

int main(){
	int T=1;
	scanf("%d",&T);
	while(T--)mian();
	return 0;
}

D.Knowledge Cards

题目大意

你有一个 n×m 的棋盘,在 (1,1) 处有 n×m 个棋子,从上到下第 i 个棋子标号为 ai,你的目标是将所有棋子按照 1n 的顺序移到 (n,m) 处。你可以将每个格子中顶端的棋子向任意方向移动一步。(1,1) 处只能移出棋子,而 (n,m) 处只能移入棋子。除了 (1,1)(n,m) 处,不能在一格中堆叠多个棋子

如果可以将所有棋子按顺序移到 (n,m),输出YA,否则输出TIDAK

思路

手动模拟放的过程。可以发现只要棋盘中除了 (1,1)(n,m) 有其它空位,就可以将任意一个不在 (1,1)(n,m) 的棋子移动到 (n,m)。所以只要场上同时存在不少于 n×m2 个棋子即无解。

代码实现

#include<bits/stdc++.h>
#define ll long long
#define For(i,a,b) for(ll i=(a);i<=(b);++i)
#define Rep(i,a,b) for(ll i=(a);i>=(b);--i)
const ll N=1e6+10;
using namespace std;

ll n,m,k;
ll a[N];
ll vis[N];

void mian(){
	
	ll ans=0;
	scanf("%lld",&n);
	scanf("%lld",&m);
	scanf("%lld",&k);
	For(i,1,k){
		scanf("%lld",&a[i]);
		vis[i]=0;
	}
	
	ll limit=n*m-3;
	ll pos=k;
	ll cnt=0;
	
	For(i,1,k){
		if(cnt<limit){
			if(a[i]==pos){
				pos--;
				while(vis[pos]){
					pos--;
					cnt--;
				}
			}else{
				vis[a[i]]=1;
				cnt++;
			}
		}else{
			printf("TIDAK\n");
			return;
		}
	}
	
	printf("YA\n");
	
}

int main(){
	int T=1;
	scanf("%d",&T);
	while(T--)mian();
	return 0;
}

E.Hanging Hearts

题目大意

给定一棵 n 个节点的树,每次操作可以选择一个叶子节点 x,删去 x,记录 x 的权值 wx。如果 wx 比它父亲 y 的权值 wy 小,wy=wx。一共需要进行 n 次操作。最后得到 wx 组成的序列。这个序列的价值是该序列的最长非下降子序列的长度。你需要对每个节点添加权值,使得价值最大。输出最大价值。

提示

  1. 深度越深,点权越小更优。

  2. 可以使用树上DP。

思路

一个节点深度越深,点权越小,这样删去它就会将父亲的权值变小,就会有更长的非下降子序列。

我们定义 dpi 表示 i 的子树产生的最长非下降子序列的长度。

可以发现 dpi 至少是子树的最大深度。因为我们可以先删掉其它点,只留下一条链。

所以就可以得到 dpi=max(dpj,maxdj),其中 ji 的儿子节点。

最后输出 max(dp1,maxd1) 即可。

代码实现

#include<bits/stdc++.h>
#define ll long long
#define For(i,a,b) for(ll i=(a);i<=(b);++i)
#define Rep(i,a,b) for(ll i=(a);i>=(b);--i)
#define pb push_back
const ll N=1e6+10;
using namespace std;

ll n,m,k;
ll a[N];
ll dp[N],d[N];
vector<ll>e[N];

void dfs(ll x){
	ll maxd=0;
	for(ll y:e[x]){
		dfs(y);
		maxd=max(maxd,d[y]);
		dp[x]+=max(dp[y],d[y]);
	}
	d[x]=maxd+1;
}

void mian(){
	
	ll ans=0;
	scanf("%lld",&n);
	For(i,2,n){
		ll x;
		scanf("%lld",&x);
		e[x].pb(i);
	}
	
	dfs(1);
	
	printf("%lld\n",max(dp[1],d[1]));
	
	For(i,1,n)e[i].clear();
	
}

int main(){
	int T=1;
//	scanf("%d",&T);
	while(T--)mian();
	return 0;
}

F.Conditional Mix

题目大意

给定 n 个一元集 ai , 每次可以合并两个交集为空的集合。合并时会建立一个新集合 A,元素为被合并的两个集合所有的元素。合并后原来的两个集合被删除,用新集合 A 替换。
可以经过任意次合并。设合并后每个集合的元素个数组成可重集 S。求不同 S 的数量,对 998244353 取模。

提示

  1. 考虑满足什么条件的集合 S 可以被构造出来。

  2. 计数DP。

思路

首先发现 S 内元素个数不满 n 个可以补 0

考虑满足什么条件的集合 S 可以被构造出来。

首先需要满足 ni=1Si=n。人话解释就是 S 内元素和为 n

如果我们令一个数 i 出现的次数为 cnti,那么其实 S 还需要满足对于 k[1,n],ki=1Sini=1min(cnti,k)。人话解释是 S 的前 k 个元素之和不能超过 A 中每个元素的出现次数与 k 的最小值之和。

为什么需要跟 kmin 呢?因为题目要求合并的两个集合不能有交集。也就是说合并后的集合不会有重复元素。

这个计数组合数显然不可行,自信计数DP。

将输入的 a 数组排序,从大到小选数。设 fi,j,k 表示确定了 S 中的前 i 个数,总和为 j,其中选择了 a 中的最小数为 k。转移方程可以参照代码。

根据上面推出的规律,k 的枚举范围可以缩小到 ni。空间上第一维可以滚动。

然后就做完了。

代码实现

#include<bits/stdc++.h>
#define ll long long
#define For(i,a,b) for(ll i=(a);i<=(b);++i)
#define Rep(i,a,b) for(ll i=(a);i>=(b);--i)
const ll N=2e3+10;
const ll p=998244353;
using namespace std;

ll n,m,k;
ll a[N];
ll dp[2][N][N];
ll t[N];
ll s[N];

void mian(){
	
	ll ans=0;
	scanf("%lld",&n);
	For(i,1,n){
		scanf("%lld",&a[i]);
		t[a[i]]++;
	}
	sort(t+1,t+n+1);
	
	For(i,1,n){
		For(j,1,n){
			s[i]+=min(i,t[j]);//前缀和 
		}
	}
	
	ll i0=0,i1=1;
	
	For(i,1,n)dp[i0][0][i]=1;
	For(i,1,n){
		ll limit=n/i;
		For(j,0,s[i]){
			dp[i1][j][limit+1]=0;
		}
		Rep(k,limit,0){
			For(j,0,s[i]){
				dp[i1][j][k]=dp[i1][j][k+1];//不选k 
				if(j>=k){
					dp[i1][j][k]+=dp[i0][j-k][k];//转移方程 
					if(dp[i1][j][k]>=p)dp[i1][j][k]-=p;//手动取模 
				}
			}
		}
		i0^=1,i1^=1;
	}
	
	printf("%lld\n",dp[i0][n][0]);
	
}

int main(){
	int T=1;
//	scanf("%d",&T);
	while(T--)mian();
	return 0;
}

G.Dangerous Laser Power

题目翻译

(不是题目大意的原因是不好概括)

有一个 n×m 的网格,上面每个位置都有一个激光发射器。每个发射器有四个面,分别编号为 0,1,2,3。如下图:

Codeforces的美丽图片

发射器有一个类型 ti,j=0/1 和一个强度 si,j。从编号为 x 的面进入、速度为 y 的激光会被发射器从编号为 x=(x+2+ti,j)mod4 的面发射出去,速度为 y=max{y,si,j},同时这个发射器消耗 yy 的能量。

现在每个发射器会向四个方向各发射一个速度为 1 的激光(不消耗能量)。一个激光超出网格或进入了 10100 个发射器之后就会消失。

如果一个发射器消耗的能量总和对 2 取模等于这个发射器的类型,则这个发射器是好的。

求一种类型分配方案,使得尽可能多的发射器是好的。

提示

  1. 按强度依次安排类型,可以让所有发射器都是好的。

  2. 维护信息快速计算发射器消耗的总能量。

思路

按强度大小依次安排类型。因为激光速度如果大于发射器的强度,发射器不消耗能量。

现在我们只需要快速计算发射器消耗的总能量。

idi,j,k 表示位置为 (i,j) 的发射器的第 k 个面的编号。

我们可以维护一个 sumi 表示 idi 的面接收的所有激光速度之和,ti 表示 idi 的面接收的激光个数。

那么位置为 (i,j) 的发射器消耗的总能量就是 ans=3k=0tpos×si,jsumpos,pos=idi,j,k

这样我们就可以快速计算类型了。

接下来只需要用并查集,在加边的时候顺便更新信息。

代码实现

#include<bits/stdc++.h>
#define ll long long
#define For(i,a,b) for(ll i=(a);i<=(b);++i)
#define Rep(i,a,b) for(ll i=(a);i>=(b);--i)
const ll N=1e3+10;
using namespace std;

struct portal{
	ll s;//强度 
	ll x,y;//坐标 
	bool operator<(const portal &a)const{
		return s<a.s;
	}
}a[N*N];//发射器 

ll n,m;
ll id[N][N][5];//发射器每个面的编号 
ll sum[4*N*N],t[4*N*N];//sum表示这个面进入的激光速度和,t表示进入这个面的激光个数 
ll nxt[2][4*N*N];//nxt[0]表示类型为0时的下一个发射器,nxt[1]同理 
ll ans[N][N];//答案 

//并查集 
ll fa[4*N*N];
ll find(ll x){
	if(fa[x]==x)return x;
	return fa[x]=find(fa[x]);
}
void add(ll x,ll y,ll z){
	y=find(y);
	if(!y||x==y)return;
	fa[x]=y;
	//更新sum和t 
	sum[y]=(sum[y]+t[x]*z)&1;
	t[y]+=t[x];
}

void mian(){
	
	ll cnt=0;//发射器编号 
	ll num=0;//发射器的面编号 
	scanf("%lld",&n);
	scanf("%lld",&m);
	For(i,1,n){
		For(j,1,m){
			scanf("%lld",&a[++cnt].s);
			a[cnt].x=i,a[cnt].y=j;//记录坐标 
			For(k,0,3){
				id[i][j][k]=++num;//编号 
				fa[num]=num;
				sum[num]=t[num]=1;//最开始发射的激光 
			}
		}
	}
	
	For(i,1,n){
		For(j,1,m){
			//暴力连边 
			nxt[0][id[i][j][0]]=id[i+1][j][0];
			nxt[0][id[i][j][1]]=id[i][j-1][1];
			nxt[0][id[i][j][2]]=id[i-1][j][2];
			nxt[0][id[i][j][3]]=id[i][j+1][3];
			nxt[1][id[i][j][0]]=id[i][j-1][1];
			nxt[1][id[i][j][1]]=id[i-1][j][2];
			nxt[1][id[i][j][2]]=id[i][j+1][3];
			nxt[1][id[i][j][3]]=id[i+1][j][0];
		}
	}
	
	sort(a+1,a+cnt+1);//按强度从小到大排序 
	
	For(i,1,cnt){
		ll x=a[i].x,y=a[i].y;
		//计算消耗的总能量 
		ll s=0;
		For(k,0,3){
			s+=a[i].s*t[id[x][y][k]]-sum[id[x][y][k]];
		}
		s&=1;
		//记录答案 
		ans[x][y]=s;
		//将激光发射出去 
		For(k,0,3){
			add(id[x][y][k],nxt[s][id[x][y][k]],a[i].s&1);
		}
	}
	
	For(i,1,n){
		For(j,1,m){
			printf("%lld",ans[i][j]);
		}
		printf("\n");
	}
	
}

int main(){
	int T=1;
//	scanf("%d",&T);
	while(T--)mian();
	return 0;
}

H.MEX Tree Manipulation

题目大意

有一棵有根树,根节点为 1。这棵树的叶子节点的权值为 0,非叶子节点的权值为其儿子节点(不是整棵子树)mex 值。

现在这棵树只有一个根节点 1,有 n 次操作,第 i 次操作在节点 x 下面接入一个编号为 i+1 的节点。对于每次操作,你需要输出操作结束后所有节点的权值之和。

提示

  1. 可以离线处理。

  2. 点的权值最大可能值比较小。

  3. 加点只影响这个点到根节点的一条链,考虑树链剖分。

  4. 加入一个值,mex 只有两种情况。

思路

首先考虑离线

我们发现题目强调了是儿子节点,考虑计算点的权值最大值

fi 表示让一个点权值为 i 至少需要的节点数。

可以推出 fi=i1j=1fj,整理得到 fi=2i

一个点的权值最大为 log n

因为加点只影响这个点到根节点的一条链,一般树上的链式修改有树上差分和树链剖分两种常用优化技巧。这里需要多次输出答案,所以使用树链剖分

具体来讲就是假设加入的是一个节点的重儿子,将当前的 ans 减去这条链的答案,然后更改之后再加回来。

考虑维护一个值 pi,j,表示i 的下面插入一个值为 j 的点后的 mex

树链剖分维护链上修改需要带上线段树,思考如何在线段树上维护这个值。

因为线段树的区间按照 dfs 序排列,所以在 i 下方插入值为 j 的点等价于dfni 这个点表示的区间最后方加入一个值为 j 的点

然而这样的时间复杂度为 O(n log3n)虽然你卡常到极致还是可以过。

因为加入一个数 x,当 mex=xmex 改变,否则不变,所以 j 的那一维只需要开 2

然后我们需要多维护一个数组 numi,0/1 表示点 i 的儿子集合中前两个没有出现的权值,这样可以快速确定 p 数组的第二维的下标。

这样,时间复杂度就是 O(n log2n)

如果没有理解上面的文字,可以结合代码和注释理解!

代码实现

#include<bits/stdc++.h>
#define ll long long
#define For(i,a,b) for(ll i=(a);i<=(b);++i)
#define Rep(i,a,b) for(ll i=(a);i>=(b);--i)
#define lson rt<<1
#define rson rt<<1|1
const ll N=3e5+10;
using namespace std;

ll n;
ll ans;

ll cnt,fir[N],nxt[N],v[N];

void add(ll x,ll y){
	v[++cnt]=y;
	nxt[cnt]=fir[x];
	fir[x]=cnt;
}

ll fa[N],dep[N],siz[N],son[N];
ll dfn_x,dfn[N],top[N];

void dfs(ll x,ll t){
	top[x]=t;
	dfn[x]=++dfn_x;
	if(!son[x])return;
	dfs(son[x],t);
	for(ll i=fir[x];i;i=nxt[i]){
		ll y=v[i];
		if(y==son[x])continue;
		dfs(y,y);
	}
}

ll b[N];//标记链顶位置 
ll val[N];//标记这个节点的mex值 
ll vis[N][20];//标记当前节点儿子节点权值的出现情况 

ll s[N<<2][2];//总和 
ll p[N<<2][2];//加入一个值之后的mex 
ll num[N<<2][2];//加入的值 

void change(ll rt,ll l,ll r,ll x,ll z1,ll z2){
	if(l==r){
		//直接更改 
		s[rt][0]=p[rt][0]=num[rt][0]=z1;
		s[rt][1]=p[rt][1]=num[rt][1]=z2;
		return;
	}
	ll mid=l+r>>1;
	if(x<=mid)change(lson,l,mid,x,z1,z2);
	else change(rson,mid+1,r,x,z1,z2);
	//本质是dfs序! 
	For(i,0,1){
		ll t=p[rson][i];//假设加入p[rson][i] 
		p[rt][i]=p[lson][t==num[lson][0]];//加入之后继承原来的mex值 
		s[rt][i]=s[lson][t==num[lson][0]]+s[rson][i];//求和 
	}
	num[rt][0]=num[rson][0];//num[lson][0]已经用过 
}

ll query(ll rt,ll l,ll r,ll x,ll y,ll &z){//z用取址是因为右边的查询对左边有影响 
	if(x<=l&&r<=y){
		ll ans=s[rt][z==num[rt][0]];//记录总和 
		z=p[rt][z==num[rt][0]];//记录当前mex 
		return ans;
	}
	ll ans=0;
	ll mid=l+r>>1;
	//这里不能写反了!一定是先走右边! 
	if(y>mid)ans+=query(rson,mid+1,r,x,y,z);//先查右边的mex 
	if(x<=mid)ans+=query(lson,l,mid,x,y,z);//用右边的mex查左边的mex 
	return ans;
}

void get_mex(ll x,ll &z1,ll &z2){//找前两个不为0的位置 
	z1=z2=19;
	For(i,0,19){
		if(!vis[x][i]){
			if(z1==19){
				z1=i;
			}else{
				z2=i;
				break;
			}
		}
	}
}

void insert(ll x){
	ll f=fa[x];
	ll z;
	while(f){
		z=19;//因为查询中每次都会更改z的值,所以需要初始化 
		ans-=query(1,1,n,dfn[top[f]],dfn[b[top[f]]],z);//减去之前贡献 
		f=fa[top[f]];
	}
	val[x]=19;
	b[top[x]]=x;//动态修改标号(假设加入的是重儿子) 
	while(x){
		ll z1,z2;
		get_mex(x,z1,z2);//找到加入的值 
		change(1,1,n,dfn[x],z1,z2);//加入这个值 
		z=19;
		ans+=query(1,1,n,dfn[top[x]],dfn[b[top[x]]],z);//加上现在贡献 
		x=top[x];
		vis[fa[x]][val[x]]--;//减去原来的值 
		val[x]=z;//更改 
		vis[fa[x]][val[x]]++;//加回来 
		x=fa[x];
	}
}

void mian(){
	
	scanf("%lld",&n);
	n++;
	//由于点集输入有顺序,所以这里可以直接使用循环处理fa,dep,siz和son 
	dep[1]=1;
	For(i,2,n){
		scanf("%lld",&fa[i]);
		dep[i]=dep[fa[i]]+1;
		add(fa[i],i);//单向边即可 
	}
	Rep(x,n,1){
		siz[x]=1;
		for(ll i=fir[x];i;i=nxt[i]){
			ll y=v[i];
			siz[x]+=siz[y];
			if(siz[y]>siz[son[x]]){
				son[x]=y;
			}
		}
	}
	//树链剖分的第二次dfs 
	dfs(1,1);
	
	b[1]=1;
	
	change(1,1,n,1,0,1);//先算出总体答案 
	For(i,2,n){
		insert(i);//加入一个点 
		printf("%lld\n",ans);
	}
	
}

int main(){
	int T=1;
//	scanf("%d",&T);
	while(T--)mian();
	return 0;
}

I.Arranging Crystal Balls

(代码没有写,有需要可以阅读下面的链接)

巨佬Kubic的题解

题目大意

给定一个 n 个点组成的环,每个点的取值范围是 [0,m1]。每次操作可以将环上一段长度为 k 的区间 +11。点权超出取值范围会溢出。求将环变为 0 的最小操作数。

提示

  1. 定长区间修改可以使用差分。

  2. 将点分成多个独立组。

  3. 枚举端点变化量,断环为链。

  4. 设计独立组的权值。

  5. 背包+单调队列。

思路

设差分数组 bi=(aia(i1)modn)modm

发现 a 全部为 0 等价于 a0=0b 全部为 0

一次操作就等价于 bib(i+k)modn 分别 +1,1

这提示我们可以将 ii+k 分到一组,形成多个相互独立的组。

可以发现组的个数是 cnt=gcd(n,k),组的大小是 l=ncnt

一次操作就变为选择一个组内的相邻两个数(可以选择首尾)分别 +1,1

因为修改的区间长度为 k,所以对 a0 有影响的修改操作总个数为 k。所以每个组中对 a0 有影响的操作个数为 kcnt

套路:一个数列需要进行相邻两个元素分别 +1,1 操作,并需要知道操作最小数量时,前缀和可以将操作简化为前缀和数组上单点 +1,1。但是如果数列不为负,需要保证前缀和数组单调递增。

设组内元素为 ci

因为组互相独立,不妨根据套路,设前缀和数组 si=si1+ci。现在我们需要断环为链。

我们可以先做 c0cl1 之间的操作。

假设我们让 c0 变为了 (c0+t)modmcl1 变为了 (cl1t)modm

那么新的前缀和数组就变为了 si=si+t,特别的,sl1=sl1

现在我们成功地将操作转化成了单点的 +1,1 操作。

那么我们想让差分数组 b 全为 0 就转化成了让 s 数组的每个数都是 m 的倍数。

那么最小的操作次数就是 l2i=0min(simodm,msimodm)

我们设当 t=0 时,ai 变为了 ai+Δ,因为每个组中对 a0 有影响的操作个数为 kcnt,当我们整体加上 t 时,这些操作都会对 ai 产生 t 的影响。

也就是说,当 t0 时,ai 实际上变为了 ai+Δktcnt

发现如果一个位置对 a0 有影响,那 a0 的变化量跟这个位置的 s 的变化量是相等的。

所以我们可以提前计算出 Δ 的值,加到 a0 上。设此时的 a0 变为了 a0

回到最初的问题。还记得另一个条件吗?我们需要使最后 a0=0

如果我们的 a0 正好为 kcnt×t,我们就可以正好使 a0=0

对于每个环,我们设每个 t 的贡献为 wt=min(t,mt)+l2i=0min(simodm,msimodm),即将 a0 变为 0 的最小操作数加上将 s 数组的每个数都是 m 的倍数的最小操作数。

dpi 表示 tmodm=i 的最小操作数,每次算出 wt 之后用背包计入答案即可。

但是直接这样做复杂度是 O(cnt×m2),需要优化。

实际上,min(t,mt)l2i=0min(simodm,msimodm) 都是只有两段的分段函数,所以 wt 可以被分成 O(l) 个等差数列。

也就是说可以使用单调队列优化。

最终复杂度为 O(cnt×l×m)=O(n×m)

代码实现

404 Not Found

尾声

如果有什么问题,可以直接评论!

都看到这里了,不妨点个赞吧!

posted @   zsc985246  阅读(222)  评论(1编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示