把博客园图标替换成自己的图标
把博客园图标替换成自己的图标end

【洛谷7110】晚秋绝诗(有趣的思维题)

点此看题面

  • \(n\)座山,\(q\)个事件,分三种:
    • 某座山峰雾去/雾回。(初始都有雾)
    • 某座山峰旗升/旗落。(初始都无旗)
    • 询问能否知道某座山峰的高度。
  • 定义若第\(i\)座山峰是间峰,则它的高度是左右两座山峰的平均值。已知挂上旗帜的一定是间峰,但间峰不一定挂旗。
  • 你可以直接观测无雾的山的高度,或根据当前挂有旗帜的间峰信息推断,来判断一次询问。
  • \(n,m\le5\times10^5\)

有雾有旗位置的传递作用

这应该是我的做法的一个基础结论。

考虑一个位置如果它有雾有旗,那么它只能起到传递作用。换句话说,只要它不是询问的位置,那么即便把这个点删掉也毫无影响。

很难给出一个严谨而理性的证明,但这只要自己画个图举几个例子应该还是容易理解的。

有了这个结论,局面就简化了很多,也方便了我们接下来对询问点进行分类讨论。

至于删去某一个位置的具体实现,其实就是开一个\(set\)维护当前存在的位置就好了。

询问点:无雾

显然这时候可以直接观测,输出1就好了。

询问点:从某一侧直接推得

假设是从左侧直接推得,那么我们必然需要先找到询问位置左侧第一对两个连在一起的无雾的位置

为什么呢?

因为要利用一个旗使得右边位置的高度能被知道,就必须满足左边位置和当前位置的高度都已经知道,且显然尽量靠近询问位置一定是更优的。

然后就是要考虑能否询问位置从这对位置推过来,那么其实就是要满足它们之间所有位置都能从前一个位置推过来

一个位置能从前一个位置推过来,只需满足下面两个条件中的一个:本身无雾前一个位置有旗

因此,在具体实现的时候,只需要再开三个\(set\),分别维护每一对连在一起的无雾位置的位置、不能从前一个位置推过来的点、不能从后一个位置推过来的点。

判断的时候,仍以左边为例,就是看一下左侧第一对无雾位置是否在左侧第一个不能从前一个位置推过来的点右边就好了。

询问点:从两侧一起推得

能从两侧一起推得,一个必要条件就是询问位置有旗。

然后,就是左侧的位置要么本身无雾,要么能从左侧推得,右侧的位置同理。

判一个位置能否从某侧推得可以直接按上面的方法做,然后就做完了。

代码:\(O((n+q)logn)\)

#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 500000
using namespace std;
int n,a[N+5],b[N+5],l[N+5],r[N+5];set<int> S,P,L,R;
//S维护当前存在的位置,P维护一对连在一起的无雾位置,L,R分别维护不能从前一个/后一个位置推得的位置
class FastIO
{
	private:
		#define FS 100000
		#define tc() (A==B&&(B=(A=FI)+fread(FI,1,FS,stdin),A==B)?EOF:*A++)
		#define pc(c) (C==E&&(clear(),0),*C++=c)
		#define D isdigit(c=tc())
		int T;char c,*A,*B,*C,*E,FI[FS],FO[FS],S[FS];
	public:
		I FastIO() {A=B=FI,C=FO,E=FO+FS;}
		Tp I void read(Ty& x) {x=0;W(!D);W(x=(x<<3)+(x<<1)+(c&15),D);}
		Tp I void writeln(Ty x) {W(S[++T]=x%10+48,x/=10);W(T) pc(S[T--]);pc('\n');}
		I void clear() {fwrite(FO,1,C-FO,stdout),C=FO;}
}F;
#define Pre(s,x) (*--s.lower_bound(x))//求某个set中x的前驱
#define Nxt(s,x) (*s.upper_bound(x))//求某个set中x的后继
#define A(s,x) (s.insert(x),0)//往某个set中插入x
#define E(s,x) (s.erase(x),0)//从某个set中删去x
I void F5_P(CI x)//刷新P中x的状态
{
	if(!x||x>=n) return;if(E(P,x),!a[x]&&b[x]) return;a[x]&&a[Nxt(S,x)]&&A(P,x);//先无脑删去,再判断是否能加入
}
I void F5_L(CI x)//刷新L中x的状态
{
	if(!x||x>n) return;if(E(L,x),!a[x]&&b[x]) return;!(l[x]=a[x]|b[Pre(S,x)])&&A(L,x);
}
I void F5_R(CI x)//刷新R中x的状态
{
	if(!x||x>n) return;if(E(R,x),!a[x]&&b[x]) return;!(r[x]=a[x]|b[Nxt(S,x)])&&A(R,x);
}
int main()
{
	RI Qt,i,op,x,pre,nxt,p,q;for(F.read(n),i=0;i<=n+1;++i) S.insert(i),L.insert(i),R.insert(i);//初始化所有点存在,所有点不能从左右推得
	P.insert(0),P.insert(n),F.read(Qt);W(Qt--) switch(F.read(op),F.read(x),op)
	{
		case 1://一次操作直接一堆无脑更新
			b[x]&&(a[x]?(S.erase(x),0):(S.insert(x),0)),a[x]^=1,
			F5_P(Pre(S,x)),F5_P(x),F5_L(x),F5_R(x),F5_R(Pre(S,x)),F5_L(Nxt(S,x));break;
		case 2://同样一堆无脑更新
			!a[x]&&(b[x]?(S.insert(x),0):(S.erase(x),0)),b[x]^=1,
			F5_P(Pre(S,x)),F5_P(x),F5_L(x),F5_R(x),F5_R(Pre(S,x)),F5_L(Nxt(S,x));break;
		case 3:if(a[x]) {puts("1");break;}//如果本来就无雾
			#define CL(x) Pre(P,x)>Pre(L,(x)+1)//能否从左边推得
			#define CR(x) Nxt(P,(x)-1)<Nxt(R,(x)-1)//能否从右边推得
			if(!b[x]) {puts(CL(x)||CR(x)?"1":"0");break;}//如果无旗,只能考虑从某一侧直接推得
			if(p=CL(x-1),q=CR(x+1),(b[Pre(S,x)]&&p)||(b[Nxt(S,x)]&&q)) {puts("1");break;}//如果有旗,先考虑只从某一侧推得
			puts((a[Pre(S,x)]||p)&&(a[Nxt(S,x)]||q)?"1":"0");break;//还可以考虑从两侧一起推得
	}return 0;
}
posted @ 2020-12-01 08:53  TheLostWeak  阅读(193)  评论(0编辑  收藏  举报