Logo zrl123456 的博客

博客

标签
暂无

T427435 魔法 讲解

本文章由 WyOJ Shojo 从洛谷专栏拉取,原发布时间为 2024-02-22 13:20:26

题目
考察:数学,递推打表

20~60 分做法:

$n,m$ 两数均暴力枚举,时间复杂度为 $O(n^2)$,无法通过 $k\le 10^8$ 的数据。

100 分做法 1:

做法 $1$ 需要一些数学知识,题中给的式子:
$$\lvert n^2-mn-m^2\rvert=1$$ 拆开绝对值符号,得: $$\begin{cases}n^2-mn-m^2=1\n^2-mn-m^2=-1\end{cases}$$ 移项,得:
$$\begin{cases}-m^2-nm+(n^2-1)=0\-m^2-nm+(n^2+1)=0\end{cases}$$ 这样我们就得到两个方程,我们可以枚举 $n$,也可以枚举 $m$。
根据一元二次方程求根公式,得: $$\begin{cases}a=-1,b=-n,c=n^2-1\=-1,b=-n,c=n^2+1\end{cases}$$ 那么(注:$\Delta=b^2-4ac$):
$$\begin{cases}\Delta=(-n)^2-4\times(-1)\times(n^2-1)=3n^2-4\\Delta=(-n)^2-4\times(-1)\times(n^2+1)=3n^2+4\end{cases}$$ 那么我们得到四个可能的根: $$\begin{cases}m_1=\frac{(-b)+\sqrt\Delta}{2a}=-\frac{\sqrt{3n^2-4}+n}{2}\\m_2=\frac{(-b)-\sqrt\Delta}{2a}=\frac{\sqrt{3n^2-4}-n}{2}\\m_3=\frac{(-b)+\sqrt\Delta}{2a}=-\frac{\sqrt{3n^2+4}+n}{2}\\m_4=\frac{(-b)-\sqrt\Delta}{2a}=\frac{\sqrt{3n^2+4}-n}{2}\end{cases}$$ 但要成为一个真正的根,还要满足以下条件:

  • $\Delta\ge0$
  • $m\in\{1,2,\dots,k\}$

最后在这些根里面找到使 $n^2+m^2$ 最大的数即可。
代码:

#include<bits\/stdc++.h>
#define int long long
#define inl inline
#define reg register
#define INF 214748364721474836
using namespace std;
inl int read(){
	int f=1,x=0;
	char ch=getchar();
	while(ch<'0'||ch>'9'){
		if(ch=='-') f=-1;
		ch=getchar();
	}
	while(ch>='0'&&ch<='9'){
		x=(x<<1)+(x<<3)+(ch^48);
		ch=getchar();
	}
	return f*x;
}
inl void write(int x){
	if(x<0){
		x=-x;
		putchar('-');
	}
	if(x>=10) write(x\/10);
	putchar(x%10^48);
}
int k,ans,mxn,mxm;
signed main(){
	k=read();
	for(reg int n=1;n<=k;++n){
		reg int a=-1,b=-n,c=-1+n*n,c2=1+n*n;
		reg int delta=b*b-4*a*c,delta2=b*b-4*a*c2;
		reg double sq=sqrt(delta),sq2=sqrt(delta2);
		reg double num1=(-b+sq)\/(2*a),num2=(-b-sq)\/(2*a);
		reg double num3=(-b+sq2)\/(2*a),num4=(-b-sq2)\/(2*a);
		if(num1==(int)num1&&ans<n*n+num1*num1&&num1>0&&num1<=k){
			ans=n*n+num1*num1;
			mxn=n;
			mxm=num1;
		}
		if(num2==(int)num2&&ans<n*n+num2*num2&&num2>0&&num2<=k){
			ans=n*n+num2*num2;
			mxn=n;
			mxm=num2;
		}
		if(num3==(int)num3&&ans<n*n+num3*num3&&num3>0&&num3<=k){
			ans=n*n+num3*num3;
			mxn=n;
			mxm=num3;
		}
		if(num4==(int)num4&&ans<n*n+num4*num4&&num4>0&&num4<=k){
			ans=n*n+num4*num4;
			mxn=n;
			mxm=num4;
		}
	}
	printf("m=%d\nn=%d",mxm,mxn);
	return 0;
}

100 分做法 2:

我们发现,这些数可能是一个有规律的数列。
那么我们可以通过打表寻找规律。
打表代码:

#include<bits\/stdc++.h>
#define int long long
#define inl inline
#define reg register
#define INF 214748364721474836
using namespace std;
inl int read(){
	int f=1,x=0;
	char ch=getchar();
	while(ch<'0'||ch>'9'){
		if(ch=='-') f=-1;
		ch=getchar();
	}
	while(ch>='0'&&ch<='9'){
		x=(x<<1)+(x<<3)+(ch^48);
		ch=getchar();
	}
	return f*x;
}
inl void write(int x){
	if(x<0){
		x=-x;
		putchar('-');
	}
	if(x>=10) write(x\/10);
	putchar(x%10^48);
}
int k,ans,mxn,mxm;
inl void f(int i){
	for(reg int y=i;y>=1;--y)
		for(reg int x=i;x>=1;--x)
			if(abs(x*x-x*y-y*y)==1){
				printf("i=%d x=%d y=%d\n",i,y,x);
				return;
			}
}
signed main(){
	k=read();
	for(reg int i=1;i<=k;++i) f(i);
	return 0;
}

结果:

可以看到,最后的结果酷似斐波那契数列,那我们就可以写代码了!
代码:

#include<bits\/stdc++.h>
#define int long long
#define inl inline
#define reg register
#define INF 214748364721474836
using namespace std;
inl int read(){
	int f=1,x=0;
	char ch=getchar();
	while(ch<'0'||ch>'9'){
		if(ch=='-') f=-1;
		ch=getchar();
	}
	while(ch>='0'&&ch<='9'){
		x=(x<<1)+(x<<3)+(ch^48);
		ch=getchar();
	}
	return f*x;
}
inl void write(int x){
	if(x<0){
		x=-x;
		putchar('-');
	}
	if(x>=10) write(x\/10);
	putchar(x%10^48);
}
#define K 114514
int k,a[K],i;
signed main(){
	k=read();
	a[1]=a[2]=1;
	for(i=3;a[i-1]<=k;++i) a[i]=a[i-1]+a[i-2];
	printf("m=%d\nn=%d",a[i-3],a[i-2]);
	return 0;
}

P2822 [NOIP2016 提高组] 组合数问题 讲解

本文章由 WyOJ Shojo 从洛谷专栏拉取,原发布时间为 2024-03-01 22:02:02

题目
先在这放两个组合数公式:
$$C_n^m=\frac{n!}{m!(n-m)!}$$
$$C_n^m=C^m_{n-1}+C_{n-1}^{m-1}$$
第一个公式大家都知道,第二个公式其实就是 dp。对于每个物品,要么选($C_{n-1}^{m-1}$),要么不选($C_{n-1}^m$)。
那么,运用第二个式子,提前计算出所有 $n,m\le 2\times 10^3$ 的 $C_n^m$。
那么组合数都有了,但 $O(Tnm)$ 的时间复杂度仍然会被卡,我们得想办法让每次询问都变成 $O(1)$,就得使用前缀和优化,即:
$$sum_{i,j}=sum_{i-1,j}+sum_{i,j-1}-sum_{i-1,j-1}+[(C_{i,j}\bmod k=0)\lor(C_{i,j}\ne0)]$$
总体复杂度:$O(nm+T)$,可以通过。
代码:

#include<bits\/stdc++.h>
#define int long long
#define inl inline
#define reg register
#define INF 2147483647
using namespace std;
inl int read(){
	int f=1,x=0;
	char ch=getchar();
	while(ch<'0'||ch>'9'){
		if(ch=='-') f=-1;
		ch=getchar();
	}
	while(ch>='0'&&ch<='9'){
		x=(x<<1)+(x<<3)+(ch^48);
		ch=getchar();
	}
	return f*x;
}
inl void write(int x){
	if(x<0){
		x=-x;
		putchar('-');
	}
	if(x>=10) write(x\/10);
	putchar(x%10^48);
}
#define N 2005
int t,k,f[N][N],sum[N][N],n,m;
inl void init(){
	f[1][1]=1;
	for(reg int i=2;i<=N-4;++i)
		for(reg int j=1;j<=i;++j)
			f[i][j]=(f[i-1][j]+f[i-1][j-1])%k;
	for(reg int i=1;i<=N-4;++i)
		for(reg int j=1;j<=N-4;++j)
			sum[i][j]=sum[i-1][j]+sum[i][j-1]-sum[i-1][j-1]+(!f[i][j]&&j<=i);
	return;
}
inl void solve(){
	n=read();
	m=read();
	write(sum[n+1][m+1]);
	puts("");
}
signed main(){
	t=read();
	k=read();
	init();
	while(t--) solve();
	return 0;
}

[ABC343F] Second Largest Query 讲解

本文章由 WyOJ Shojo 从洛谷专栏拉取,原发布时间为 2024-03-04 21:56:43

考察:线段树(这道题不同于以前的线段树,我们需要解决三个问题)。
题目

维护值:

看到单点修改,区间查询,我们便想到了线段树。
但是这道题问的问题非常刁钻,问的是在 $[l,r]$ 区间内严格次小值的数量,那么我们需要维护四个值:严格最小值,严格最小值数量,严格次小值,严格次小值数量(我们分别用 $x_1,ans_1,x_2,ans_2$ 表示)。

状态转移:

我们知道了左孩子和右孩子的四个值,怎样得出父节点的四个值也是一个难点(设其左孩子为 $u$,右孩子为 $v$,父节点为 $z$)。
这得分九种情况讨论 (你没听错,九种情况),所以说举个例子就行:
$x_{u,1}>x_{v,1}\lor x_{u,2}>x_{v,1}$($x_{u,1}>x_{v,1}>x_{v,2}$,则 $x_{v,2}$ 不可能为次大值):
$x_{z,1}=x_{u,1}$,$ans_{z,1}=ans_{u,1}$,$x_{z,2}=x_{u,2}$,$ans_{z,2}=ans_{u,2}$。
其余的同上,主要看代码:

t[x].x1=max(t[x<<1].x1,t[x<<1|1].x1);
if(t[x<<1].x1>t[x<<1|1].x1){
	t[x].ans1=t[x<<1].ans1;
	t[x].x2=max(t[x<<1].x2,t[x<<1|1].x1);
	if(t[x<<1].x2>t[x<<1|1].x1) t[x].ans2=t[x<<1].ans2;
	else if(t[x<<1].x2==t[x<<1|1].x1) t[x].ans2=t[x<<1].ans2+t[x<<1|1].ans1;
	else t[x].ans2=t[x<<1|1].ans1;
}else if(t[x<<1].x1==t[x<<1|1].x1){
	t[x].ans1=t[x<<1].ans1+t[x<<1|1].ans1;
	t[x].x2=max(t[x<<1].x2,t[x<<1|1].x2);
	if(t[x<<1].x2>t[x<<1|1].x2) t[x].ans2=t[x<<1].ans2;
	else if(t[x<<1].x2==t[x<<1|1].x2) t[x].ans2=t[x<<1].ans2+t[x<<1|1].ans2;
	else t[x].ans2=t[x<<1|1].ans2;
}else{
	t[x].ans1=t[x<<1|1].ans1;
	t[x].x2=max(t[x<<1].x1,t[x<<1|1].x2);
	if(t[x<<1].x1>t[x<<1|1].x2) t[x].ans2=t[x<<1].ans1;
	else if(t[x<<1].x1==t[x<<1|1].x2) t[x].ans2=t[x<<1].ans1+t[x<<1|1].ans2;
	else t[x].ans2=t[x<<1|1].ans2;
}

统计答案:

答案同样要四个值,用 $ansx_{1,1},ansx_{1,2},ansx_{2,1},ansx_{2,2}$ 表示,还得分六种情况,那再举个例子(设当前访问的节点为 $u$):
$x_{u,1}>ansx_{1,1}$:
$ansx_{2,1}=ansx_{1,1}$(注意得先往下传),$ansx_{2,2}=ansx_{1,2}$,$ansx_{1,1}=x_{u,1}$,$ansx_{1,2}=ans_{u,1}$。
具体看代码:

if(t[x].x1>ans11){
	ans21=ans11;
	ans22=ans12;
	ans11=t[x].x1;
	ans12=t[x].ans1;
}else if(t[x].x1==ans11) ans12+=t[x].ans1;
else if(t[x].x1>ans21){
	ans21=t[x].x1;
	ans22=t[x].ans1;
}else if(t[x].x1==ans21) ans22+=t[x].ans1;
if(t[x].x2>ans21){\/\/注意没有 else
	ans21=t[x].x2;
	ans22=t[x].ans2;
}else if(t[x].x2==ans21) ans22+=t[x].ans2;

好了,三个问题都解决完了,剩余的按线段树板子码就行。
代码:(赛时代码,$120$ 多行):

#include<bits\/stdc++.h>
#define int long long
#define inl inline
#define reg register
#define INF 2147483647
using namespace std;
inl int read(){
	int f=1,x=0;
	char ch=getchar();
	while(ch<'0'||ch>'9'){
		if(ch=='-') f=-1;
		ch=getchar();
	}
	while(ch>='0'&&ch<='9'){
		x=(x<<1)+(x<<3)+(ch^48);
		ch=getchar();
	}
	return f*x;
}
inl void write(int x){
	if(x<0){
		x=-x;
		putchar('-');
	}
	if(x>=10) write(x\/10);
	putchar(x%10^48);
}
const int N=2e5+5;
int n,q,a[N],op,l,r,p,x,ans11,ans12,ans21,ans22;
struct trees{
	int l,r,x1,ans1,x2,ans2;
}t[N<<2];
inl void pushup(int x){
	t[x].x1=max(t[x<<1].x1,t[x<<1|1].x1);
	if(t[x<<1].x1>t[x<<1|1].x1){
		t[x].ans1=t[x<<1].ans1;
		t[x].x2=max(t[x<<1].x2,t[x<<1|1].x1);
		if(t[x<<1].x2>t[x<<1|1].x1) t[x].ans2=t[x<<1].ans2;
		else if(t[x<<1].x2==t[x<<1|1].x1) t[x].ans2=t[x<<1].ans2+t[x<<1|1].ans1;
		else t[x].ans2=t[x<<1|1].ans1;
	}else if(t[x<<1].x1==t[x<<1|1].x1){
		t[x].ans1=t[x<<1].ans1+t[x<<1|1].ans1;
		t[x].x2=max(t[x<<1].x2,t[x<<1|1].x2);
		if(t[x<<1].x2>t[x<<1|1].x2) t[x].ans2=t[x<<1].ans2;
		else if(t[x<<1].x2==t[x<<1|1].x2) t[x].ans2=t[x<<1].ans2+t[x<<1|1].ans2;
		else t[x].ans2=t[x<<1|1].ans2;
	}else{
		t[x].ans1=t[x<<1|1].ans1;
		t[x].x2=max(t[x<<1].x1,t[x<<1|1].x2);
		if(t[x<<1].x1>t[x<<1|1].x2) t[x].ans2=t[x<<1].ans1;
		else if(t[x<<1].x1==t[x<<1|1].x2) t[x].ans2=t[x<<1].ans1+t[x<<1|1].ans2;
		else t[x].ans2=t[x<<1|1].ans2;
	}
}
inl void build(int l,int r,int x){
	t[x].l=l;
	t[x].r=r;
	if(l==r){
		t[x].x1=a[l];
		t[x].ans1=1;
		return;
	}
	reg int mid=(l+r)>>1;
	build(l,mid,x<<1);
	build(mid+1,r,x<<1|1);
	pushup(x);
}
inl void add(int p,int num,int x){
	if(t[x].l==p&&t[x].r==p){
		t[x].x1=num;
		return;
	}
	reg int mid=(t[x].l+t[x].r)>>1;
	if(p<=mid) add(p,num,x<<1);
	else add(p,num,x<<1|1);
	pushup(x);
}
inl void getans(int l,int r,int x){
	if(l<=t[x].l&&t[x].r<=r){
		if(t[x].x1>ans11){
			ans21=ans11;
			ans22=ans12;
			ans11=t[x].x1;
			ans12=t[x].ans1;
		}else if(t[x].x1==ans11) ans12+=t[x].ans1;
		else if(t[x].x1>ans21){
			ans21=t[x].x1;
			ans22=t[x].ans1;
		}else if(t[x].x1==ans21) ans22+=t[x].ans1;
		if(t[x].x2>ans21){
			ans21=t[x].x2;
			ans22=t[x].ans2;
		}else if(t[x].x2==ans21) ans22+=t[x].ans2;
		return;
	}
	reg int mid=(t[x].l+t[x].r)>>1;
	if(l<=mid) getans(l,r,x<<1);
	if(r>mid) getans(l,r,x<<1|1);
}
signed main(){
	n=read();
	q=read();
	for(reg int i=1;i<=n;++i) a[i]=read();
	build(1,n,1);
	for(reg int i=1;i<=q;++i){
		op=read();
		if(op==1){
			p=read();
			x=read();
			add(p,x,1);
		}else{
			l=read();
			r=read();
			ans11=ans12=ans21=ans22=0;
			getans(l,r,1);
			write(ans22);
			puts("");
		}
	}
	return 0;
}

[ABC217F] Make Pair 讲解

本文章由 WyOJ Shojo 从洛谷专栏拉取,原发布时间为 2024-03-05 21:11:23

题目
考察:区间 dp,组合数。

状态与边界条件:

我们设 $[l,r]$ 区间的方案数用 $f_{l,r}$ 表示($1\le l,r\le 2n$),那么边界条件就是一个空序列的方案数(即 $f_{i+1,i}$)的方案数便是 $1$。

转移方程:

对于每一个 $f_{l,r}$,若 $l$ 与 $r$ 为朋友关系(我们记作 $vis_{l,r}=1$),那我们就需要先加上 $f_{l+1,r-1}$。
接下来我们需要找到一个点(同学)$k$,把区间 $[l,r]$ 分成两部分,即区间 $[l,k-1]$ 和 $[k,r]$,然后再将区间 $[k,r]$ 分成 $k$ 点,$[k+1,r-1]$ 区间和 $r$ 点,若 $vis_{k,r}=1$,则我们需要加上一个数。
就是下面这张图(paint 画图有点丑):

若只是将 $f_{l,k-1}$ 与 $f_{k,r}$ 相乘,则无法满足题目中对于两种选人的方案,即使同桌关系相同,只要离开队列的顺序不同,也算是不同的方案的条件,那么我们来分析一下:
对于整个 $[l,r]$ 区间需要选 $\frac{r-l+1}{2}$ 次,$[k,r]$ 区间需要选 $\frac{r-k+1}{2}$,那么我们需要在 $\frac{r-l+1}{2}$ 次中选 $\frac{r-k+1}{2}$ 次,这就是组合数,也就是说这一次的答案是 $f_{l,k-1}f_{k+1,r-1}C_{\frac{r-k+1}{2}}^{\frac{r-l+1}{2}}$,我们需要提前维护好任意的 $C_{i,j}$($0\le j\le i\le 2n$)。
那么我们得到最后的转移方程:
$$f_{l,r}=vis_{l,r}f_{l+1,r-1}+\sum_{k=l+2}^{r-1}vis_{k,r}f_{l,k-1}f_{k+1,r-1}C_{\frac{r-k+1}{2}}^{\frac{r-l+1}{2}}$$ 当然,我们得保证 $r-l+1$,$r-k+1$,$k-l$ 均为偶数。
代码:

#include<bits\/stdc++.h>
#define int long long
#define inl inline
#define reg register
#define INF 214748364721474836
using namespace std;
inl int read(){
	int f=1,x=0;
	char ch=getchar();
	while(ch<'0'||ch>'9'){
		if(ch=='-') f=-1;
		ch=getchar();
	}
	while(ch>='0'&&ch<='9'){
		x=(x<<1)+(x<<3)+(ch^48);
		ch=getchar();
	}
	return f*x;
}
inl void write(int x){
	if(x<0){
		x=-x;
		putchar('-');
	}
	if(x>=10) write(x\/10);
	putchar(x%10^48);
}
#define MOD 998244353
#define N 405
int n,m,u,v,f[N][N],c[N][N];
bool vis[N][N];
signed main(){
	n=read()<<1;
	m=read();
	for(int i=1;i<=m;++i){
		u=read();
		v=read();
		if((u^v)&1) vis[u][v]=vis[v][u]=true;
	}
	for(int i=0;i<=n;++i) f[i+1][i]=1;
	for(int i=0;i<=n;++i)
		for(int j=0;j<=i;++j) 
			if(j==0) c[i][j]=1;
			else c[i][j]=(c[i-1][j]+c[i-1][j-1])%MOD;
	for(int len=2;len<=n;len+=2)
		for(int l=1;l+len-1<=n;++l){
			int r=l+len-1;
			if(vis[l][r]) f[l][r]=f[l+1][r-1];
			for(int k=l+2;k<r;k+=2)
				if(vis[k][r]) f[l][r]=(f[l][r]+(f[l][k-1]*f[k+1][r-1]%MOD)*c[len>>1][(r-k+1)>>1]%MOD)%MOD;
		}
	write(f[1][n]%MOD);
	return 0;
}

[ABC341E] Alternating String 讲解

本文章由 WyOJ Shojo 从洛谷专栏拉取,原发布时间为 2024-03-11 22:35:25

题目
考察:线段树,树状数组,set,差分。

方法一:

这道题与 P3870 有点相似,只不过它是区间求和,而这道题是判断是不是好字符串。
我们不难发现,好的字符串有以下特征:

  1. 好的字符串取反后必为好的字符串(不好的字符串取反后必为不好的字符串)。
  2. 好的字符串的子串必为好的字符串(不好的字符串的母串必为不好的字符串)。

然后我们来看个图:

所以说我们要存三个值:首端点值,尾端点值和判断变量,我们分别用 $begin$,$end$,$vis$ 表示(当然懒标记也是需要的,我们用 $lan$ 表示)。
我们得到状态转移方程:
$$vis_x=[vis_u\lor vis_v\lor (end_u\ne begin_v)]$$ $$begin_x=begin_u$$ $$end_x=end_v$$ 边界条件为($l=r$):
$$vis_x=1$$ $$begin_x=end_x=a_l$$ 修改时:
$$lan_x=1-lan_x$$ pushdown 函数:
$$begin_u=1-begin_u$$ $$end_u=1-end_u$$ $$begin_v=1-begin_v$$ $$end_v=1-end_v$$ $$lan_u=lan_v=1$$ $$lan_x=0$$ 易得代码(时间:$427ms$,空间:$47.53MB$,代码长度:$2.64KB$,是三种做法之中最劣的一种做法):

#include<bits\/stdc++.h>
#define int long long
#define inl inline
#define INF 214748364721474836
using namespace std;
inl int read(){
	int f=1,x=0;
	char ch=getchar();
	while(ch<'0'||ch>'9'){
		if(ch=='-') f=-1;
		ch=getchar();
	}
	while(ch>='0'&&ch<='9'){
		x=(x<<1)+(x<<3)+(ch^48);
		ch=getchar();
	}
	return f*x;
}
inl void write(int x){
	if(x<0){
		x=-x;
		putchar('-');
	}
	if(x>=10) write(x\/10);
	putchar(x%10^48);
}
const int N=5e5+5;
struct trees{
	int l,r,begin,end;
	bool vis,lan;
}t[N<<2];
string s;
int n,q,a[N],op,l,r;
inl void build(int x,int l,int r){
	t[x].l=l;
	t[x].r=r;
	if(l==r){
		t[x].vis=1;
		t[x].begin=a[l];
		t[x].end=a[r];
\/\/		cout<<x<<' '<<t[x].l<<' '<<t[x].r<<' '<<t[x].begin<<' '<<t[x].end<<' '<<t[x].vis<<endl;
		return;
	}
	int mid=(l+r)>>1;
	build(x<<1,l,mid);
	build(x<<1|1,mid+1,r);
	t[x].vis=(t[x<<1].vis&&t[x<<1|1].vis&&t[x<<1].end!=t[x<<1|1].begin);
	t[x].begin=t[x<<1].begin;
	t[x].end=t[x<<1|1].end;
\/\/	cout<<x<<' '<<t[x].l<<' '<<t[x].r<<' '<<t[x].begin<<' '<<t[x].end<<' '<<t[x].vis<<endl;
}
inl void pushdown(int x){
	if(!t[x].lan) return;
	t[x<<1].begin^=1;
	t[x<<1].end^=1;
	t[x<<1|1].begin^=1;
	t[x<<1|1].end^=1;
	t[x<<1].lan^=1;
	t[x<<1|1].lan^=1;
\/\/	cout<<x<<' '<<t[x].l<<' '<<t[x].r<<' '<<t[x].begin<<' '<<t[x].end<<' '<<t[x].vis<<endl;
\/\/	cout<<(x<<1)<<' '<<t[x<<1].l<<' '<<t[x<<1].r<<' '<<t[x<<1].begin<<' '<<t[x<<1].end<<' '<<t[x<<1].vis<<endl;
\/\/	cout<<(x<<1|1)<<' '<<t[x<<1|1].l<<' '<<t[x<<1|1].r<<' '<<t[x<<1|1].begin<<' '<<t[x<<1|1].end<<' '<<t[x<<1|1].vis<<endl;
	t[x].lan=0;
}
inl void add(int x,int l,int r){
	if(l<=t[x].l&&t[x].r<=r){
		t[x].lan^=1;
		t[x].begin^=1;
		t[x].end^=1;
\/\/		cout<<x<<' '<<t[x].l<<' '<<t[x].r<<' '<<t[x].begin<<' '<<t[x].end<<' '<<t[x].vis<<endl;
		return;
	}
	pushdown(x);
	int mid=(t[x].l+t[x].r)>>1;
	if(l<=mid) add(x<<1,l,r);
	if(mid<r) add(x<<1|1,l,r);
	t[x].vis=(t[x<<1].vis&&t[x<<1|1].vis&&t[x<<1].end!=t[x<<1|1].begin);
	t[x].begin=t[x<<1].begin;
	t[x].end=t[x<<1|1].end;
\/\/	cout<<x<<' '<<t[x].l<<' '<<t[x].r<<' '<<t[x].begin<<' '<<t[x].end<<' '<<t[x].vis<<endl;
}
inl bool getans(int x,int l,int r){
	if(l<=t[x].l&&t[x].r<=r) return t[x].vis;
	pushdown(x);
	int mid=(t[x].l+t[x].r)>>1;
	bool vis=1;
	if(l<=mid) vis&=getans(x<<1,l,r);
	if(r>mid) vis&=getans(x<<1|1,l,r);
	if(l<=mid&&r>mid) vis&=(t[x<<1].end!=t[x<<1|1].begin);
	return vis;
}
signed main(){
	n=read();
	q=read();
	cin>>s;
	s=' '+s;
	for(int i=1;i<=n;++i) a[i]=s[i]^48;
	build(1,1,n);
	for(int i=1;i<=q;++i){
		op=read();
		l=read();
		r=read();
		if(op==1) add(1,l,r);
		else if(getans(1,l,r)) puts("Yes");
		else puts("No");
	}
	return 0;
} 

做法 2:

看到相邻两个数不同,我们便想到差分,样例中的数列差分后就是下面这个样子:
$$c=\{1,1,1,1,0\}$$ 恍然大悟,不就是转化成了单点修改(修改左右两个端点),区间求和(若区间内的和正好等于长度,也就是每个元素都为 $1$ 时,就为好的字符串)嘛。
代码没什么好讲的,但需要注意两个点:

  1. 第 $1$ 个点上的差分数组始终为 $1$。
  2. 第 $l$ 个点上存的是第 $l-1$ 个点与它的差分,不能算入查询区间里。

代码(时间:$223ms$,空间:$31.55MB$,代码长度:$1.48KB$,就时间来看,它是三种做法的最优解):

#include<bits\/stdc++.h>
#define int long long
#define inl inline
#define INF 214748364721474836
using namespace std;
inl int read(){
	int f=1,x=0;
	char ch=getchar();
	while(ch<'0'||ch>'9'){
		if(ch=='-') f=-1;
		ch=getchar();
	}
	while(ch>='0'&&ch<='9'){
		x=(x<<1)+(x<<3)+(ch^48);
		ch=getchar();
	}
	return f*x;
}
inl void write(int x){
	if(x<0){
		x=-x;
		putchar('-');
	}
	if(x>=10) write(x\/10);
	putchar(x%10^48);
}
const int N=5e5+5;
struct trees{
	int l,r,sum;
}t[N<<2];
string s;
int n,q,a[N],op,l,r;
inl void build(int x,int l,int r){
	t[x].l=l;
	t[x].r=r;
	if(l==r){
		t[x].sum=a[l];
		return;
	}
	int mid=(l+r)>>1;
	build(x<<1,l,mid);
	build(x<<1|1,mid+1,r);
	t[x].sum=t[x<<1].sum+t[x<<1|1].sum;
}
inl void add(int x,int vis){
	if(t[x].l==vis&&vis==t[x].r){
		t[x].sum^=1;
		return;
	}
	int mid=(t[x].l+t[x].r)>>1;
	if(vis<=mid) add(x<<1,vis);
	else add(x<<1|1,vis);
	t[x].sum=t[x<<1].sum+t[x<<1|1].sum;
}
inl int getans(int x,int l,int r){
	if(l<=t[x].l&&t[x].r<=r) return t[x].sum;
	int mid=(t[x].l+t[x].r)>>1,sum=0;
	if(l<=mid) sum+=getans(x<<1,l,r);
	if(r>mid) sum+=getans(x<<1|1,l,r);
	return sum;
}
signed main(){
	n=read();
	q=read();
	cin>>s;
	s=' '+s;
	a[1]=1;
	for(int i=2;i<=n;++i) a[i]=s[i-1]^s[i];
	build(1,1,n);
	for(int i=1;i<=q;++i){
		op=read();
		l=read();
		r=read();
		if(op==1){
			if(l!=1) add(1,l);
			if(r!=n) add(1,r+1);
		}else if(getans(1,l+1,r)==r-l) puts("Yes");
		else puts("No");
	}
	return 0;
} 

做法 3:

做法 $3$ 很巧妙,同样做了差分,但把相同的放在了 set 里,修改的时候左右端点 set 里有就删除,没有就插入,最后直接查询。
要注意的还是上面那两个,不多说直接放代码(时间:$705ms$,空间:$26.59MB$,代码长度:$982B$,虽说跑的最慢,但代码空间大大减少):

#include<bits\/stdc++.h>
#define int long long
#define inl inline
#define INF 214748364721474836
using namespace std;
inl int read(){
	int f=1,x=0;
	char ch=getchar();
	while(ch<'0'||ch>'9'){
		if(ch=='-') f=-1;
		ch=getchar();
	}
	while(ch>='0'&&ch<='9'){
		x=(x<<1)+(x<<3)+(ch^48);
		ch=getchar();
	}
	return f*x;
}
inl void write(int x){
	if(x<0){
		x=-x;
		putchar('-');
	}
	if(x>=10) write(x\/10);
	putchar(x%10^48);
}
#define T set<int>::iterator
string s;
int n,q,op,l,r;
set<int>st;
signed main(){
	n=read();
	q=read();
	cin>>s;
	s=' '+s;
	for(int i=1;i<=n;++i)
		if(s[i]==s[i-1]) st.insert(i);
	for(int i=1;i<=q;++i){
		op=read();
		l=read();
		r=read();
		if(op==1){
			if(st.find(l)==st.end()&&l!=1) st.insert(l);
			else st.erase(l);
			if(st.find(r+1)==st.end()) st.insert(r+1);
			else st.erase(r+1);
		}else{
			T it=st.upper_bound(l);
			if(it==st.end()||*it>r) puts("Yes");
			else puts("No");
		}
	}
	return 0;
} 

(真的想吐槽一下专栏区)

AT_abc334_f讲解

本文章由 WyOJ Shojo 从洛谷专栏拉取,原发布时间为 2023-12-29 18:28:19

这道题大部分都是使用 dp 和单调队列去做的, 中间也需要前缀和来维护, 题解中也有一些讲解。
只是做的时候要注意是按 $1$ 到 $N$ 的顺序送礼物就可以了。

非正解

这里我们设圣诞老人家为 $0$ 号房间, $sum_i$ 为圣诞老人从 $0$ 号房间到 $1$ 号房间, 再到 $2$ 号, $3$ 号...... 直到 $i$ 号且中途不回自己家的距离,$m(i,j)$ 表示 $i$ 号房间到 $j$ 号房间的欧几里得距离, 下文公式中的 $i$ 表示圣诞老人所在的房间, $j$ 表示圣诞老人在送完 $i-j$ 号房间后回到自己家中。
易得公式:
$$f_i=\min(f_{i-j}+m(i-j+1,0)+sum_i-sum_{i-j+1}+m(i,0))(1\le j\le k)$$ 将与 $j$ 不相关的项移出函数, 得:
$$f_i=\min(f_{i-j}+m(i-j+1,0)-sum_{i-j+1})+sum_i+m(i,0)(1\le j\le k)$$ 所以我们只需枚举出 $f_{i-j}+m(i-j+1,0)-sum_{i-j+1}$ 的最小值即可。
时间复杂度为 $O(n^2)$, 明显超时。

正解

分析代码, 发现明显可以使用单调队列优化。
那么存入单调队列中的下标固然为 $i$, 值应将 $j$ 移出, 得 $f_i+m(i+1,0)-sum_{i+1}$, 我们用 $ans(i)$ 表示 。
那么我们设队首下标为 $l$, 则公式得:
$$f_i=ans(l)+sum_i+m(i,0)$$ 那如何处理队列为空的情况呢?
很简单, 在队列中放入一个 $0$ 即可。
时间复杂度为 $O(n)$。
代码:

#include<bits\/stdc++.h>
#define m(i,j) sqrt((x[i]-x[j])*(x[i]-x[j])+(y[i]-y[j])*(y[i]-y[j]))
#define ans(i) f[i]+m(i+1,0)-sum[i+1]
using namespace std;
deque<int>d;
const int MAX=2*1e5+5;
long long n,k,x[MAX],y[MAX];
double sum[MAX],f[MAX];
void que(int l){
	while(!d.empty()&&d.front()+k<=l) d.pop_front();
	while(!d.empty()&&ans(d.back())>ans(l)) d.pop_back();
	d.push_back(l);
	return; 
} 
int main(){
	cin>>n>>k;
	for(int i=0;i<=n;i++) cin>>x[i]>>y[i];
	for(int i=1;i<=n;i++) sum[i]=sum[i-1]+m(i,i-1);
	d.push_back(0);
	for(int i=1;i<=n;i++){
		f[i]=ans(d.front())+m(i,0)+sum[i];
		que(i);
	}
	printf("%.15lf",f[n]);
	return 0;
}	

CF1916D Mathematical Problem 讲解

本文章由 WyOJ Shojo 从洛谷专栏拉取,原发布时间为 2024-01-05 18:49:32

(事先声明:这个方法是班里的大佬告诉我的)
看到这道题, 你是不是有点懵?

---------------------------------------------------------切入正题-------------------------------------------------------------

首先你要知道若 $x$ 是一个能被 $a^2$ 得到, 且 $a$ 为整数的, 它就是一个平方数。
且, 若 $x$ 为一个平方数, 那么 $100x$ 也为一个平方数。
为什么呢?
因为:
$$100x=a\times a\times 100$$ $$100x=a\times a\times (10\times 10)$$ $$100x=10a\times 10a$$ $$100x=(10a)^2$$ 这样做, 我们就可以得到 $n-2$ 个平方数。
接下来, 我们考虑把 $0$ 塞到中间去。
因为:
$$(a+b)^2=(a+b)(a+b)$$ $$(a+b)^2=a(a+b)+b(a+b)$$ $$(a+b)^2=a^2+ab+b^2+ab$$ $$(a+b)^2=a^2+2ab+b^2$$ 接下来, 我们拿 $169$ 试一试。
我们设 $a$ 为 $10$,$b$ 为 $3$:
$$(10+3)^2=10^2+2\times 10\times 3+3^2$$ $$(10+3)^2=100+60+9=169$$ 然后, 我们在 $1$ 后面塞一个 $0$,$9$ 前面塞一个 $0$。
这样,$a$ 就变成了 $100$,$b$ 不变。
得到:
$$(100+3)^2=100^2+2\times 100\times 3+3^2$$ $$(100+3)^2=10000+600+9=10609$$ 恰好等于 $10609$。
$961$ 同上,以此类推。
这样,我们得到了五位平方数:$16900$,$19600$,$96100$,$10609$,$90601$。
是不是很震惊?
代码如下:

#include<bits\/stdc++.h>
using namespace std;
string s[100][100],ss[50];
int t,n;
void workk(){\/\/预处理1 
	for(int i=1;i<=48;i++){
		ss[i]=ss[i-1]+"0";
	}
	return;
}
void work(){\/\/预处理2 
	s[1][1]="1";
	s[3][1]="169";
	s[3][2]="961";
	s[3][3]="196";
	for(int i=5;i<=99;i+=2){
		for(int j=1;j<=i-2;j++){
			s[i][j]=s[i-2][j]+"00";
		}
		s[i][i-1]="1"+ss[(i-3)>>1]+"6"+ss[(i-3)>>1]+"9";
		s[i][i]="9"+ss[(i-3)>>1]+"6"+ss[(i-3)>>1]+"1";
	}
}
int main(){
	cin>>t;
	workk();
	work();
	for(int i=1;i<=t;i++){
		cin>>n;
		for(int j=1;j<=n;j++){
			cout<<s[n][j]<<endl;\/\/直接输出 
		}
	}
	return 0;
}
共 127 篇博客