【CF套题】Codeforces Round #539 (Div. 1) (Round1109)

【前言】
数据结构写得很爽啊。

【题目】
原题地址

A.Sasha and a Bit of Relax

给定一个数列 a i a_i ai,问有多少个长度为偶数的区间满足前一半数和后一半数字异或和相等。
n ≤ 3 × 1 0 5 , a i ≤ 2 20 n\leq 3\times 10^5,a_i\leq 2^{20} n3×105,ai220

【解题思路】
卡了一会woc

s i s_i si表示前缀异或和,假设一个区间 [ l , r ] [l,r] [l,r]是合法的,其充要条件就是 s l − 1 = s r , l & 1 ≠ r & 1 s_{l-1}=s_r,l\&1\neq r\& 1 sl1=sr,l&1̸=r&1
f i , 0 / 1 f_{i,0/1} fi,0/1表示前缀异或值为 i i i时端点奇偶性为 0 / 1 0/1 0/1的前缀个数进行 DP \text{DP} DP即可。
复杂度 O ( n ) O(n) O(n)

#include<bits/stdc++.h>
using namespace std;

int n,f[(1<<20)+10][2];
long long ans;

int read()
{
	int ret=0;char c=getchar();
	while(!isdigit(c)) c=getchar();
	while(isdigit(c)) ret=ret*10+(c^48),c=getchar();
	return ret;
}

int main()
{
#ifdef Durant_Lee
	freopen("CF1109A.in","r",stdin);
	freopen("CF1109A.out","w",stdout);
#endif
	n=read();f[0][0]=1;
	for(int i=1,s=0;i<=n;++i)
	{
		int x=read();s^=x;
		ans+=f[s][i&1];++f[s][i&1];
	}
	printf("%lld\n",ans);
	return 0;
}

B.Sasha and One More Name

给定一个长度为 n n n的字符串 s s s,问最少将它分成多少段重新拼接以后能形成一个新的回文串。
n ≤ 5000 n\leq 5000 n5000

【解题思路】
不难发现答案一定是 1 , 2 1,2 1,2或无解,随便判判吧, O ( n ) O(n) O(n)判一组都可以了。
复杂度 O ( n 2 ) O(n^2) O(n2)或更优(只需要写个哈希就 O ( n ) O(n) O(n)了吧)。

#include<bits/stdc++.h>
using namespace std;

const int N=5005;
int n,ans;
char s[N],t[N];

bool check(char *s,int x)
{
	for(int i=1;i<=x;++i) if(s[i]!=s[x-i+1]) return 1;
	return 0;
}
bool same(char *s,char *t)
{
	for(int i=1;i<=n;++i) if(s[i]^t[i]) return 0;
	return 1;
}

int main()
{
#ifdef Durant_Lee
	freopen("CF1109B.in","r",stdin);
	freopen("CF1109B.out","w",stdout);
#endif
	scanf("%s",s+1);n=strlen(s+1);
	for(int i=1;i<=n/2;++i) if(check(s,i)) ans=2;
	if(!(n&1)) if(check(s,n/2)) ans=1;
	for(int i=1;i<=n;++i) 
	{
		for(int j=1;j<=i;++j) t[n-(i-j)]=s[j];
		for(int j=i+1;j<=n;++j) t[j-i]=s[j];
		if(!check(t,n) && !same(s,t)) ans=1;
	}
	if(!ans) puts("Impossible"); else printf("%d\n",ans);
	return 0;
}
C.Sasha and a Patient Friend

有一个碗,每个时刻水量会增加一个数 x x x(可以为负),有 3 3 3种操作

  • 1   t   s : 1\ t\ s: 1 t s:从第 t t t秒开始直到下一个修改操作点水量增加速率变为 s s s
  • 2   t : 2\ t: 2 t:删除第 t t t秒这个修改操作水量对增加速率的改变(然后这个时刻的该变量会被前面的修改影响)
  • 3   l   r   v : 3\ l\ r\ v: 3 l r v:假设初始水量为 v v v,考虑第 l l l到第 r r r秒水量的变化,问什么时候水量为 0 0 0

q ≤ 1 0 5 , l , r , t , s ≤ 1 0 9 q\leq 10^5,l,r,t,s\leq 10^9 q105,l,r,t,s109

【解题思路】
这个题目看一年。

前两个操作相当于区间赋值,第三个操作显然可以二分,我们只需要用线段树维护区间最小前缀和,然后在线段树上二分即可。再拿个 set \text{set} set之类的维护一下操作位置就好了。

离散化后复杂度 O ( q log ⁡ q ) O(q\log q) O(qlogq),写起来是真的麻烦。

#include<bits/stdc++.h>
#define mkp make_pair
#define fi first
#define se second
using namespace std;

typedef long long ll;
typedef double db;
typedef pair<ll,int> pii;
const int N=2e5+10,M=N<<2,inf=0x3f3f3f3f;
int num[N];

int read()
{
	int ret=0,f=0;char c=getchar();
	while(!isdigit(c)) f|=(c=='-'),c=getchar();
	while(isdigit(c)) ret=ret*10+(c^48),c=getchar();
	return f?-ret:ret;
}

namespace Data_Structure
{
	struct Segment
	{
		#define ls (x<<1)
		#define rs (x<<1|1)
		ll sum[M],si[M],tar[M];//sum of interval,mini prefix sum,interval set 
		void pushdown(int x,int l,int r)//wrong because use wrong num
		{
			if(tar[x]==inf) return;
			int mid=(l+r)>>1;ll t=tar[x];tar[x]=inf;
			sum[ls]=t*(num[mid+1]-num[l]);si[ls]=min(0ll,sum[ls]);
			sum[rs]=t*(num[r+1]-num[mid+1]);si[rs]=min(0ll,sum[rs]);
			tar[ls]=tar[rs]=t;
		}
		void pushup(int x)
		{
			sum[x]=sum[ls]+sum[rs];si[x]=min(si[ls],sum[ls]+si[rs]);
		}
		void update(int x,int l,int r,int L,int R,ll v)
		{
			if(L<=l && r<=R){sum[x]=v*(num[r+1]-num[l]);si[x]=min(0ll,sum[x]);tar[x]=v;return;}
			pushdown(x,l,r);
			int mid=(l+r)>>1;
			if(L<=mid) update(ls,l,mid,L,R,v);
			if(R>mid) update(rs,mid+1,r,L,R,v);
			pushup(x);
		}
		pii query(int x,int l,int r,ll v)
		{
			if(l==r)return mkp(v,l);
			pushdown(x,l,r);
			int mid=(l+r)>>1;
			if(v+si[ls]<=0) return query(ls,l,mid,v);
			else return query(rs,mid+1,r,v+sum[ls]);
		}
		pii query1(int x,int l,int r,int L,int R,ll v)
		{
			if(L<=l && r<=R){return (v+si[x]<=0)?query(x,l,r,v):mkp(v+sum[x],-1);}
			pushdown(x,l,r);
			int mid=(l+r)>>1;pii res=mkp(v,-1);
			if(L<=mid) res=query1(ls,l,mid,L,R,v);
			if(R>mid && !~res.se) res=query1(rs,mid+1,r,L,R,res.fi);
			return res;
		}
		ll query2(int x,int l,int r,int p)
		{
			if(l==r) return tar[x];
			pushdown(x,l,r);
			int mid=(l+r)>>1;
			if(p<=mid) return query2(ls,l,mid,p);
			else return query2(rs,mid+1,r,p);
		}
		#undef ls
		#undef rs
	}tr;
}
using namespace Data_Structure;

namespace DreamLolita
{
	int Q,n;
	set<int>st;
	struct Tquery
	{
		int op,l,r,v;
		Tquery(int _o=0,int _l=0,int _r=0,int _v=0):op(_o),l(_l),r(_r),v(_v){}
	}q[N];
	void solution()
	{
		Q=read();
		for(int i=1;i<=Q;++i)
		{
			int op=read(),x,y,z;
			if(op==1)
			{
				x=read();y=read();
				q[i]=Tquery(1,x,0,y);num[++n]=x;
			}
			else if(op==2)
			{
				x=read();
				q[i]=Tquery(2,x,x,0);num[++n]=x;
			}
			else 
			{
				x=read();y=read();z=read();
				q[i]=Tquery(3,x,y,z);num[++n]=x;num[++n]=y;
			}
		}
		num[++n]=0;num[++n]=1e9+1;sort(num+1,num+n+1);n=unique(num+1,num+n+1)-num-1;
		st.insert(1);st.insert(n);
		for(int i=1;i<=Q;++i)
		{
			if(q[i].op==1)
			{
				int x=lower_bound(num+1,num+n+1,q[i].l)-num;st.insert(x);
				set<int>::iterator it=st.lower_bound(x);++it;
				int y=*it;tr.update(1,1,n,x,y-1,q[i].v);
			}
			else if(q[i].op==2)
			{
				int x=lower_bound(num+1,num+n+1,q[i].l)-num;
				set<int>::iterator it=st.lower_bound(x),it2=it;--it;++it2;
				int y=*it,z=*it2;st.erase(x);tr.update(1,1,n,x,z-1,tr.query2(1,1,n,y));
			}
			else 
			{
				if(!q[i].v){printf("%d\n",q[i].l);continue;}
				int x=lower_bound(num+1,num+n+1,q[i].l)-num,y=lower_bound(num+1,num+n+1,q[i].r)-num-1;
				set<int>::iterator it=st.lower_bound(x);x=*it;
				if(x>y){puts("-1");continue;}
				pii res=tr.query1(1,1,n,x,y,q[i].v);
				if(~res.se)
				{
					db v=tr.query2(1,1,n,res.se);
					printf("%.10lf\n",num[res.se]-(db)res.fi/v);
				}
				else puts("-1");
			}
		}
	}
}

int main()
{
#ifdef Durant_Lee
	freopen("CF1109C.in","r",stdin);
	freopen("CF1109C.out","w",stdout);
#endif
	DreamLolita::solution();
	return 0;
}
D.Sasha and Interesting Fact from Graph Theory

称一棵带标号树是妙的,当且仅当两个关键点 a , b a,b a,b之间的距离恰好为 m m m
给定 n , a , b , m n,a,b,m n,a,b,m,问有多少本质不同的树是好的,且每条边的边权为正且不超过 m m m
n , m ≤ 1 0 6 n,m\leq 10^6 n,m106

首先我们并不在乎这两个点是什么,然后就是一个计数题。
一个比较显然的思路就是枚举两点之间的节点数 k k k,那么这里有 ( n − 2 k ) {n-2 \choose k} (kn2)的选法,还有一个 k ! k! k!的顺序。

接下来就是给 k + 1 k+1 k+1条边分配边权和为 m m m,用隔板法可以得到是 ( m − 1 k ) {m-1\choose k} (km1)
然后再给剩下 n − k − 2 n-k-2 nk2条边随意分别边权,就是 m n − k − 2 m^{n-k-2} mnk2

下面的问题相当于求一个 n n n个点 k ( + 2 ) k(+2) k(+2)个连通块的带标号无根森林,我们考虑用 prufer \text{prufer} prufer序列来做。最后答案是 k ⋅ n n − k − 1 k\cdot n^{n-k-1} knnk1

但是怎么证明啊?

这个博主给了个归纳法,但是我化不出来啊。blog
这个好像看懂了。blog
大概就是现在相当于固定一条大小为 k + 2 k+2 k+2的链连通块,还有 n − k − 2 n-k-2 nk2个大小为 1 1 1的点连通块,用这些连通块要连成一棵树。
结论是 n m ∏ i = 1 m s z i n^m\prod_{i=1}^msz_i nmi=1mszi,其中 m m m是连通块个数, s z i sz_i szi是第 i i i个连通块大小。证明的话可以考虑 prufer \text{prufer} prufer序上每个出现数字会对答案产生的贡献,然后乘法分配律一下就可以了。

不管怎样,反正最后答案就是
∑ k = 0 n − 2 ( n − 2 k ) ⋅ k ! ⋅ ( m − 1 k ) ⋅ m n − k − 2 ⋅ ( k + 2 ) ⋅ n n − k − 3 \sum_{k=0}^{n-2} {n-2\choose k} \cdot k! \cdot {m-1\choose k}\cdot m^{n-k-2} \cdot (k+2)\cdot n^{n-k-3} k=0n2(kn2)k!(km1)mnk2(k+2)nnk3
复杂度 O ( n ) O(n) O(n)

#include<bits/stdc++.h>
using namespace std;

typedef long long ll;
const int N=1e6+10,mod=1e9+7;
int n,m,A,B,ans;
int fac[N],ifac[N],pwn[N],pwm[N];

void up(int &x,int y){x+=y;if(x>=mod)x-=mod;}
int mul(int x,int y){return 1ll*x*y%mod;}
int qpow(int x,int y){int res=1;for(;y;y>>=1,x=mul(x,x))if(y&1)res=mul(res,x);return res;}
void init()
{
	scanf("%d%d%d%d",&n,&m,&A,&B);
	fac[0]=1;for(int i=1;i<N;++i) fac[i]=mul(fac[i-1],i);
	pwn[0]=qpow(n,mod-2);for(int i=1;i<N;++i) pwn[i]=mul(pwn[i-1],n);
	pwm[0]=1;for(int i=1;i<N;++i) pwm[i]=mul(pwm[i-1],m);
	ifac[N-1]=qpow(fac[N-1],mod-2);for(int i=N-2;~i;--i) ifac[i]=mul(ifac[i+1],i+1);
}
int C(int x,int y){if(x<y)return 0;return 1ll*fac[x]*ifac[y]%mod*ifac[x-y]%mod;}

void solve()
{
	for(int k=0;k<=n-2;++k)
	{
		int t1=mul(mul(C(n-2,k),fac[k]),pwm[n-k-2]),t2=mul(C(m-1,k),mul(k+2,pwn[n-k-3+1]));
		up(ans,mul(t1,t2));
	}
	printf("%d\n",ans);
}

int main()
{
#ifdef Durant_Lee
	freopen("CF1109D.in","r",stdin);
	freopen("CF1109D.out","w",stdout);
#endif
	init();solve();
	return 0;
}

E.Sasha and a Very Easy Test

给定一个长度为 n n n的序列 a i a_i ai以及一个模数 m m m,有 q q q次三种操作

  • 区间乘一个数
  • 单点除以一个数,保证可以整除
  • 区间和对 m m m取模的值
    n , q ≤ 1 0 5 , m ≤ 1 0 9 + 9 n,q\leq 10^5,m\leq 10^9+9 n,q105,m109+9

【解题思路】
1 , 3 1,3 1,3个操作显然是可以用线段树维护的。这个单点除有点麻烦,根本原因是因为 m m m不一定是素数,可能没有逆元。

于是不妨设 m = p 1 m 1 p 2 m 2 ⋅ p k m k m=p_1^{m_1}p_2^{m_2}\cdot p_k^{m_k} m=p1m1p2m2pkmk。这里 k = O ( log ⁡ m ) k=O(\log m) k=O(logm),实际上最多是 9 9 9
那么其实所有 x x x也可以表示为一个数 x 0 x_0 x0乘上 p i p_i pi的若干次幂,其中 gcd ⁡ ( x 0 , m ) = 1 \gcd (x_0,m)=1 gcd(x0,m)=1。这个就表示成一个向量吧。

那么第二个操作就是一个乘逆元和一个向量减了。第一个操作就是乘法和一个向量加。第三个直接加。

复杂度 O ( ( q + n ) log ⁡ n log ⁡ m ) O((q+n)\log n \log m) O((q+n)lognlogm)

#include<bits/stdc++.h>
using namespace std;

const int N=1e5+10;
int n,mod,phi,Q,pnum;
int a[N],P[11],C[11];

namespace IO
{
	int read()
	{
		int ret=0;char c=getchar();
		while(!isdigit(c)) c=getchar();
		while(isdigit(c)) ret=ret*10+(c^48),c=getchar();
		return ret;
	}
	void write(int x){if(x>9)write(x/10);putchar(x%10^48);}
	void writeln(int x){write(x);putchar('\n');}
}
using namespace IO;

namespace Math
{
	int upm(int x){return x>=mod?x-mod:(x<0?x+mod:x);}
	void up(int &x,int y){x=upm(x+y);}
	int mul(int x,int y){return 1ll*x*y%mod;}
	int getphi(int n)
	{
		int res=n;
		for(int i=2;1ll*i*i<=n;++i)
		{
			if(!(n%i)) res=res/i*(i-1);
			while(!(n%i)) n/=i;
		}
		if(n>1) res=res/n*(n-1);
		return res;
	}
	int qpow(int x,int y)
	{
		int res=1;
		for(;y;y>>=1,x=mul(x,x))if(y&1)res=mul(res,x);
		return res;
	}
	int inv(int x){return qpow(x,phi-1);}
	void divide(int x)
	{
		int t=x;
		for(int i=2;t>1 && 1ll*i*i<=x;++i)
		{
			if(!(t%i)) P[++pnum]=i;
			while(!(t%i)) t/=i,++C[pnum];
		}
		if(t>1) P[++pnum]=t;
		C[pnum]=1;
	}
	void calc(int x,int &res,int *p)
	{
		for(int i=1;i<=pnum;++i) while(!(x%P[i])) x/=P[i],p[i]++;
		res=x%mod;
	}
	int calcres(int x,int *p)
	{
		for(int i=1;i<=pnum;++i) x=mul(x,qpow(P[i],p[i]));
		return x;
	}
}
using namespace Math;

namespace Data_Structure
{
	struct node
	{
		int tar,itar,val,p[12];
	};
	struct Seg
	{
		#define ls (x<<1)
		#define rs (x<<1|1)
		node t[N<<2];
		void pushdown(int x)
		{
			for(int i=1;i<=pnum;++i){t[ls].p[i]+=t[x].p[i];t[rs].p[i]+=t[x].p[i];t[x].p[i]=0;}
			t[ls].val=mul(t[ls].val,t[x].tar);t[rs].val=mul(t[rs].val,t[x].tar);
			t[ls].tar=mul(t[ls].tar,t[x].tar);t[rs].tar=mul(t[rs].tar,t[x].tar);
			t[ls].itar=mul(t[ls].itar,t[x].itar);t[rs].itar=mul(t[rs].itar,t[x].itar);
			t[x].tar=t[x].itar=1;
		}
		void pushup(int x){t[x].val=upm(t[ls].val+t[rs].val);}
		void build(int x,int l,int r)
		{
			t[x].tar=t[x].itar=1;memset(t[x].p,0,sizeof(t[x].p));	
			if(l==r){t[x].val=a[l]%mod;calc(a[l],t[x].itar,t[x].p);return;}
			int mid=(l+r)>>1;
			build(ls,l,mid);build(rs,mid+1,r);
			pushup(x);
		}
		void update1(int x,int l,int r,int L,int R,int v,int iv,int *p)
		{
			if(L<=l && r<=R)
			{
				for(int i=1;i<=pnum;++i) t[x].p[i]+=p[i];
				t[x].val=mul(t[x].val,v);t[x].tar=mul(t[x].tar,v);t[x].itar=mul(t[x].itar,iv);
				return;
			}
			pushdown(x);
			int mid=(l+r)>>1;
			if(L<=mid) update1(ls,l,mid,L,R,v,iv,p);
			if(R>mid) update1(rs,mid+1,r,L,R,v,iv,p);
			pushup(x);
		}
		void update2(int x,int l,int r,int pos,int v,int *p)
		{
			if(l==r)
			{
				for(int i=1;i<=pnum;++i) t[x].p[i]-=p[i];
				t[x].itar=mul(t[x].itar,inv(v));
				t[x].val=calcres(t[x].itar,t[x].p);
				return;
			}
			pushdown(x);
			int mid=(l+r)>>1;
			if(pos<=mid) update2(ls,l,mid,pos,v,p);
			else update2(rs,mid+1,r,pos,v,p);
			pushup(x);
		}
		int query(int x,int l,int r,int L,int R)
		{
			if(L<=l && r<=R) return t[x].val;
			pushdown(x);
			int mid=(l+r)>>1,res=0;
			if(L<=mid) up(res,query(ls,l,mid,L,R));
			if(R>mid) up(res,query(rs,mid+1,r,L,R));
			return res;
		}
		#undef ls 
		#undef rs
	}tr;
}
using namespace Data_Structure;

namespace DreamLolita
{
	void solution()
	{
		n=read();mod=read();phi=getphi(mod);divide(mod);
		for(int i=1;i<=n;++i) a[i]=read();
		tr.build(1,1,n);

		Q=read();
		while(Q--)
		{
			static int ps[11];
			int op=read(),a=read(),b=read(),x,v;
			memset(ps,0,sizeof(ps));
			if(op==1) x=read(),calc(x,v,ps),tr.update1(1,1,n,a,b,x,v,ps);
			else if(op==2) calc(b,v,ps),tr.update2(1,1,n,a,v,ps);
			else writeln(tr.query(1,1,n,a,b));
		}
	}
}

int main()
{
#ifdef Durant_Lee
	freopen("CF1109E.in","r",stdin);
	freopen("CF1109E.out","w",stdout);
#endif
	DreamLolita::solution();
	return 0;
}

F.Sasha and Algorithm of Silence’s Sounds

给定一个 n × m n\times m n×m的矩阵,每个格子里有一个不重复的 1 ∼ n × m 1\sim n\times m 1n×m的数字。问有多少个区间 [ l , r ] [l,r] [l,r]满足 [ l , r ] [l,r] [l,r]中所有数字在矩阵中构成一棵树(两点之间有且只有一条路径)。
n , m ≤ 1000 , n m ≤ 2 × 1 0 5 n,m\leq 1000,nm\leq 2\times 10^5 n,m1000,nm2×105

【解题思路】
连通性问题可以考虑一下 LCT \text{LCT} LCT的啊。现在如果它是一个连通块的话很不好处理,不妨先强制它为一个森林,从小到大枚举每个 r r r,处理出最小的 L r L_r Lr满足 [ L r , r ] [L_r,r] [Lr,r]构成一个森林,显然这个 L r L_r Lr是单调的。

现在要处理出 L r ≤ l ≤ r L_r\leq l\leq r Lrlr中满足 [ l , r ] [l,r] [l,r]是一棵树的个数。直接求也很不好求,一个很妙的东西是森林中树的个数等于点数减去边数。于是我们维护一棵线段树,对于每个叶子 i i i,维护后缀森林中树的数量,这样就是一个区间最小值和最小值个数了。加入 r r r这个点相当于区间 + 1 +1 +1,加入与 r r r相连的边相当于后缀区间 − 1 -1 1

复杂度 O ( n log ⁡ n ) O(n\log n) O(nlogn)

#include<bits/stdc++.h>
using namespace std;

typedef long long ll;
const int M=1005,N=2e5+10;
int dx[]={0,1,0,-1},dy[]={1,0,-1,0};

int read()
{
	int ret=0;char c=getchar();
	while(!isdigit(c)) c=getchar();
	while(isdigit(c)) ret=ret*10+(c^48),c=getchar();
	return ret;
}

namespace Data_Structure
{
	struct Segment
	{
		#define ls (x<<1)
		#define rs (x<<1|1)
		int n,tar[N<<2];
		struct node
		{
			int v,c;node(int _v=0,int _c=0):v(_v),c(_c){}
			node operator +(const node&rhs)const
			{
				if(v==rhs.v) return node(v,c+rhs.c);
				return v<rhs.v?node(v,c):node(rhs.v,rhs.c);
			}
		}t[N<<2];
		void pushup(int x){t[x]=t[ls]+t[rs];}
		void pushdown(int x)
		{
			int v=tar[x];tar[x]=0;
			t[ls].v+=v;tar[ls]+=v;t[rs].v+=v;tar[rs]+=v;
		}
		void build(int x,int l,int r)
		{
			if(l==r){t[x]=node(1,1);return;}
			int mid=(l+r)>>1;
			build(ls,l,mid);build(rs,mid+1,r);
			pushup(x);
		}
		void update(int x,int l,int r,int L,int R,int v)
		{
			if(L<=l && r<=R){t[x].v+=v;tar[x]+=v;return;}
			pushdown(x);
			int mid=(l+r)>>1;
			if(L<=mid) update(ls,l,mid,L,R,v);
			if(R>mid) update(rs,mid+1,r,L,R,v);
			pushup(x);
		}
		node query(int x,int l,int r,int L,int R)
		{
			if(L<=l && r<=R) return t[x];
			pushdown(x);
			int mid=(l+r)>>1;node res=node(N,0);
			if(L<=mid) res=res+query(ls,l,mid,L,R);
			if(R>mid) res=res+query(rs,mid+1,r,L,R);
			return res;
		}
		int calc(int l,int r){node res=query(1,1,n,l,r);return res.v==1?res.c:0;}
		#undef ls
		#undef rs	
	}tr;
	int top,st[N];
	struct LCT
	{
		#define ls ch[x][0]
		#define rs ch[x][1]
		int rev[N],fa[N],ch[N][2];
		bool isroot(int x){return ch[fa[x]][0]!=x && ch[fa[x]][1]!=x;}
		int get(int x){return ch[fa[x]][1]==x;}
		void pushdown(int x){if(rev[x])rev[ls]^=1,rev[rs]^=1,rev[x]^=1,swap(ls,rs);}
		void rotate(int x)
		{
			int y=fa[x],z=fa[y],k=get(x);
			if(!isroot(y)) ch[z][get(y)]=x;
			fa[ch[x][!k]]=y;fa[y]=x;fa[x]=z;
			ch[y][k]=ch[x][!k];ch[x][!k]=y;
		}
		void splay(int x)
		{
			st[top=1]=x;for(int t=x;!isroot(t);t=fa[t])st[++top]=fa[t];
			while(top) pushdown(st[top--]);
			while(!isroot(x))
			{
				int y=fa[x];
				if(!isroot(y)) rotate(get(x)==get(y)?y:x);
				rotate(x);
			}
		}
		void access(int x){for(int t=0;x;t=x,x=fa[x]) splay(x),rs=t;}
		void makeroot(int x){access(x);splay(x);rev[x]^=1;}
		void link(int x,int y){makeroot(x);fa[x]=y;}
		void cut(int x,int y){makeroot(x);access(y);splay(y);ch[y][0]=fa[x]=0;}
		int findroot(int x){access(x);splay(x);while(ls)x=ls;return x;}
		#undef ls
		#undef rs
	}T;
}
using namespace Data_Structure;

namespace DreamLolita
{
	int n,m;
	int a[M][M],fx[N],fy[N];
	ll ans;
	bool inmp(int x,int y){return 1<=x && x<=n && 1<=y && y<=m;}
	bool checkforest(int l,int r)
	{
		for(int i=0;i<4;++i)
		{
			int xl=fx[r]+dx[i],yl=fy[r]+dy[i];
			if(!inmp(xl,yl) || !(l<=a[xl][yl] && a[xl][yl]<=r)) continue;
			for(int j=i+1;j<4;++j)
			{
				int xr=fx[r]+dx[j],yr=fy[r]+dy[j];
				if(!inmp(xr,yr) || !(l<=a[xr][yr] && a[xr][yr]<=r)) continue;
				if(T.findroot(a[xl][yl])==T.findroot(a[xr][yr])) return 0;
			}
		}
		return 1;
	}
	void modify(int l,int r,int p,int op)
	{
		for(int i=0;i<4;++i)
		{
			int x=fx[p]+dx[i],y=fy[p]+dy[i];
			if(!inmp(x,y) || !(l<=a[x][y] && a[x][y]<=r)) continue;
			if(~op) T.link(p,a[x][y]),tr.update(1,1,tr.n,1,a[x][y],-1);
			else T.cut(p,a[x][y]);
		}
	}
	void solution()
	{
		n=read();m=read();tr.n=n*m;tr.build(1,1,tr.n);
		for(int i=1;i<=n;++i) for(int j=1;j<=m;++j) a[i][j]=read(),fx[a[i][j]]=i,fy[a[i][j]]=j;
		for(int r=1,l=1;r<=n*m;++r)
		{
			for(;!checkforest(l,r);++l) modify(l+1,r-1,l,-1);
			modify(l,r-1,r,1);ans+=tr.calc(l,r);tr.update(1,1,tr.n,l,r,1);
		}
		printf("%lld\n",ans);
	}
}

int main()
{
#ifdef Durant_Lee
	freopen("CF1109F.in","r",stdin);
	freopen("CF1109F.out","w",stdout);
#endif
	DreamLolita::solution();
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值