小游戏又AK了!

220 篇文章 2 订阅
11 篇文章 0 订阅

题目

题目背景(与题目无关)
D D ( X Y X ) \sf DD(XYX) DD(XYX) 又双叒叕 A K AK AK 了!

对于他本人而言,这不过是一件轻而易举的小事。然而我因此而对他非常仰慕。人与人的体质,真是不能一概而论啊!

题目描述
小游戏 A K AK AK n n n 场比赛(他一共也只打过 n + 1 n+1 n+1 场比赛,其中 1 1 1 场忘了交代码),他的 A K AK AK 用时被记录了下来,分别是 t 1 , t 2 , t 3 , … , t n t_1,t_2,t_3,\dots,t_n t1,t2,t3,,tn

为了装弱,他想挑出一个严格上升子序列(谎称自己越来越菜)。于是他就像大多数人会做的那样,用一支笔在两个被选择的元素之间画线。结果他慢慢迷上了这个自娱自乐的活动——他开始不停地在两个元素之间画线,最终笔恰好回到了最初的位置,而且此时任意两个可以同时被选入上升子序列的不同元素之间都有恰好一条线。当然啦,笔可以是往前也可以是往后画,但是绝对不能从纸上抬起来;也并非所有的 t t t 序列都可以进行这个活动,因为这个玩法挺严苛的。

当然这个问题非常简单,对于 D D ( X Y X ) \sf DD(XYX) DD(XYX) 来说,即使是把每一个原序列的子段 ⟨ t l , t l + 1 , … , t r ⟩ \langle t_l,t_{l+1},\dots,t_r\rangle tl,tl+1,,tr 当成真正需要处理的序列(即,在原序列中删去一个前缀、删去一个后缀,然后进行这个活动),他也会做。不过他今天的思维能力退化到了一个前所未有的境地,并且 H a n d I n D e v i l \sf HandInDevil HandInDevil 准备趁机击垮他,所以 D D ( X Y X ) \sf DD(XYX) DD(XYX) 把这个任务交给了他的崇拜者——请问有多少个子段使得这个活动能够完美结束?

两个子段不同,当且仅当有序数对 ⟨ l , r ⟩ \langle l,r\rangle l,r 不同。

数据范围与提示
n ≤ 8 × 1 0 3 n\le 8\times 10^3 n8×103 。请注意空间限制仅有 512 M B 512\tt MB 512MB

提示:有一种正解是使用哈希的

思路

问题等价于找欧拉回路。那么我们只需要判断度数为偶数,以及图是连通的。不过要注意,单独的一个点是可以被忽略的。

判断图连通,还挺简单的。因为 i < j i<j i<j t i < t j t_i<t_j ti<tj 时,任意 i < k < j i<k<j i<k<j 都会与 i , j i,j i,j 中至少一个连边。所以图不连通,一定是某个前缀的最小值 ≥ \ge 这个后缀的最大值。

考虑到 n 2 n^2 n2 可接受,直接枚举每个子段。为了实现上面这个条件,只需要预处理每个后缀中第一个 > v >v >v 的位置,然后要求右端点超过其 max ⁡ \max max 即可。

可是度数为偶数怎么搞?我们会想到预处理 L i , j L_{i,j} Li,j 表示 [ i , j ) [i,j) [i,j) 内小于 a j a_j aj 的数的数量   m o d   2 \bmod 2 mod2 。类似的可以求 R i , j R_{i,j} Ri,j 。然后我们只需要 L l , j ⊕ R r , j = 0 L_{l,j}\oplus R_{r,j}=0 Ll,jRr,j=0,即 L l , j = R r , j L_{l,j}=R_{r,j} Ll,j=Rr,j 对于 j ∈ [ l , r ] j\in[l,r] j[l,r] 均成立。

两个序列相等?哈希!多么朴实无华,却又多么高效!于是两个问题都被解决了。

最后补充关于单个的点的情况。不难发现如果一个点没有连边,那么它的前面和它的后面之间也不会有边。即没有点会跨越它。此时如果还想图连通,不得不让前面或者后面仍然全为单独点。继续推导就是:前面一个连续不升序列,中间有一些边,后面一个连续不升序列,且前面没边、后面没边。这个很容易嘛,就找到中间 + + + 后面,再把前面接上。

时间复杂度 O ( n 2 ) \mathcal O(n^2) O(n2),需要卡一卡空间。

代码

#include <cstdio>
#include <iostream>
#include <cstring>
#include <vector>
#include <algorithm>
using namespace std;
typedef long long int_;
# define rep(i,a,b) for(int i=(a); i<=(b); ++i)
# define drep(i,a,b) for(int i=(a); i>=(b); --i)
inline int readint(){
	int a = 0; char c = getchar(), f = 1;
	for(; c<'0'||c>'9'; c=getchar())
		if(c == '-') f = -f;
	for(; '0'<=c&&c<='9'; c=getchar())
		a = (a<<3)+(a<<1)+(c^48);
	return a*f;
}
inline void writeint(int x){
	if(x > 9) writeint(x/10);
	putchar((x-x/10*10)^48);
}

const int MaxN = 8005;
const long long BASE = 3;
const int Mod = 998244353;
int bit[MaxN], a[MaxN];
int getHash(int v[],int l,int r){
	return (v[r]+Mod-1ll*bit[r-l+1]*v[l-1]%Mod)%Mod;
}

int L[MaxN], R[MaxN][MaxN];
short nxt[MaxN][MaxN]; // first element > y in suffix x
int tmp[MaxN]; // for LiSanHua and value L
int main(){
	int n = readint();
	rep(i,1,n) tmp[i] = a[i] = readint();
	sort(tmp+1,tmp+n+1);
	int *xez = unique(tmp+1,tmp+n+1);
	rep(i,1,n) a[i] = lower_bound(
		tmp+1,xez,a[i])-tmp;
	rep(i,bit[0]=1,n)
		bit[i] = (bit[i-1]*BASE)%Mod;
	rep(i,1,n) rep(j,1,i)
		R[i][j] = R[i-1][j]^(a[j]<a[i]);
	rep(i,1,n) rep(j,1,i) // prepare
		R[i][j] = (BASE*R[i][j-1]+R[i][j])%Mod;
	rep(i,1,n) nxt[n+1][i] = n+1;
	drep(i,n,1){
		rep(j,a[i],n) nxt[i][j] = nxt[i+1][j];
		drep(j,a[i]-1,1) nxt[i][j] = i;
	}
	int ans = 0;
	drep(l,n,1){
		rep(j,l,n) L[j] = L[j]^(a[l]<a[j]);
		int jb = tmp[l-1] = 0;
		rep(j,l,n) tmp[j] = (
			tmp[j-1]*BASE+L[j])%Mod;
		drep(i,l-1,1){
			if(a[i] < a[i+1]) break;
			++ jb; // one more choice
		}
		int r_ = nxt[l][a[l]]; // needed r
		int mn = a[l]; // minimum
		bool single = false; // isolated suffix
		rep(r,l+1,n){
			if(mn < a[r]) single = false;
			else mn = a[r]; // update
			if(getHash(tmp,l,r) == getHash(R[r],l,r))
				if(single || r >= r_){ // ok there
					single = true, ++ ans;
					if(r < nxt[l][a[l-1]])
						ans += jb; // can be added
				}
			r_ = max(r_,int(nxt[r+1][mn]));
		}
		++ ans; // when only one node
		rep(r,l+1,n){
			if(a[r-1] < a[r]) break;
			++ ans; // only isolated one
		}
	}
	printf("%d\n",ans);
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值