Codeforces Harbour.Space Scholarship Contest 2023-2024 (Div. 1 + Div. 2) A-F 个人练习

A. Increasing and Decreasing

Codeforces Harbour.Space Scholarship Contest 2023-2024 (Div. 1 + Div. 2) A-F 个人练习_第1张图片

题意:
给定正整数 x x x y y y n n n
问能否构造一个长度为 n n n 的数组 a a a,满足以下条件:

  • a 1 = x , a n = y a_1 = x, a_n = y a1=x,an=y
  • a a a 严格递增
  • b i = a i + 1 − a i b_i = a_{i+1} - a_i bi=ai+1ai b b b严格递减

思路:
因为要求 a a a 严格递增,所以 y y y 一定要大于 x + n − 1 x+n-1 x+n1
然后考虑从末尾开始,每往前一步,这个位置的值就要比后面一个位置的值 d + 1 d+1 d+1
也就是 b i = a i + 1 − a i b_i = a_{i+1} - a_i bi=ai+1ai 从最后面的 1 1 1 开始,逐步递增

迭代到 i = 1 i=1 i=1 时,如果当前的值小于 x x x ,说明无法满足,反之则可以通过调整 b 1 b_1 b1 的大小来满足条件

// Problem: A. Increasing and Decreasing
// Contest: Codeforces - Harbour.Space Scholarship Contest 2023-2024 (Div. 1 + Div. 2)
// URL: https://codeforces.com/contest/1864/problem/A
// Memory Limit: 256 MB
// Time Limit: 1000 ms
// 
// Powered by CP Editor (https://cpeditor.org)

#include
#define fore(i,l,r)	for(int i=(int)(l);i<(int)(r);++i)
#define fi first
#define se second
#define endl '\n' 

const int INF=0x3f3f3f3f;
const long long INFLL=0x3f3f3f3f3f3f3f3fLL;

typedef long long ll;

void solve(){
	int x,y,n;
	std::cin>>x>>y>>n;
	if(x+n-1>y){
		std::cout<<-1<<endl;
		return;
	}
	int d=1;
	std::vector<int> ans;
	ans.push_back(y);
	for(int i=n-1;i>=1;--i){
		if(i==1){
			y-=d++;
			if(y<x){
				std::cout<<-1<<endl;
				return;
			}
			ans.push_back(x);
			break;
		}
		y-=d++;
		ans.push_back(y);
	}
	for(auto it=ans.rbegin();it!=ans.rend();++it)
		std::cout<<*it<<' ';
	std::cout<<endl;
}

int main(){
    std::ios::sync_with_stdio(false);
    std::cin.tie(nullptr);
    std::cout.tie(nullptr);
    int t;
    std::cin>>t;
    while(t--){
    	solve();
    }
	return 0; 
}

B. Swap and Reverse

Codeforces Harbour.Space Scholarship Contest 2023-2024 (Div. 1 + Div. 2) A-F 个人练习_第2张图片

题意:
给定一个长度为 n n n 的只包含小写字母的字符串 s s s 和一个正整数 k k k,可以执行以下两种操作任意次:

  • 交换 s i s_i si s i + 2 s_{i+2} si+2
  • 翻转一个长度为 k k k 的区间 [ i , i + k − 1 ] [i,i+k-1] [i,i+k1]

问经过以上操作任意次后,能够得到的字典序最小的字符串是什么

思路:
先将字符串分奇偶位置染色: [ 1 , 0 , 1 , 0 , 1 , 0 , . . . ] [1,0,1,0,1,0,...] [1,0,1,0,1,0,...]
现在只考虑第一种操作,可以发现其实第一种操作就是类似于冒泡排序,可以分别地将位置和位置的元素排好序

那么加上第二种操作之后,对 k k k 分情况讨论:

  1. k k k奇数,那么翻转的区间可能形如 k = 5 k=5 k=5时: [ 1 , 0 , [1,0, [1,0, 1 1 1 , 0 , 1 ] ,0,1] ,0,1],中间元素不动,从中间的元素开始往两边看,可以发现每两个交换的位置的奇偶性是一样的,也就是说当 k k k 为奇数时,无法改变元素的颜色,只能对奇偶位置分开排列
  2. k k k偶数,如果我们翻转 [ i , i + k − 1 ] [i,i+k-1] [i,i+k1],可以发现这个区间内的所有元素的颜色都改变了,接着我们翻转 [ i + 1 , i + k ] [i+1,i+k] [i+1,i+k] 这个区间,这个区间内的所有元素颜色也会改变,其中,原来在 i + k i+k i+k i + k − 1 i+k-1 i+k1 这两个位置的元素的颜色会改变 i + k − 1 i+k-1 i+k1 这个位置的元素被换到了 i i i,不参与第二次翻转, i + k i+k i+k 这个位置的元素只参与第二次翻转),其余的元素的颜色都改变了两次,又变回了原来的颜色,也就是说,这种情况下的操作二可以改变任意两个相邻元素的颜色,这样就可以对整个字符串排序!
// Problem: B. Swap and Reverse
// Contest: Codeforces - Harbour.Space Scholarship Contest 2023-2024 (Div. 1 + Div. 2)
// URL: https://codeforces.com/contest/1864/problem/B
// Memory Limit: 256 MB
// Time Limit: 1000 ms
// 
// Powered by CP Editor (https://cpeditor.org)

#include
#define fore(i,l,r)	for(int i=(int)(l);i<(int)(r);++i)
#define fi first
#define se second
#define endl '\n' 

const int INF=0x3f3f3f3f;
const long long INFLL=0x3f3f3f3f3f3f3f3fLL;

typedef long long ll;

void solve(){
	int n,k;
	std::cin>>n>>k;
	std::string s;
	std::cin>>s;
	s='0'+s;
	if(k&1){
		std::priority_queue<char,std::vector<char>,std::greater<char>> q1,q2;
		fore(i,1,n+1)
			if(i&1)	q1.push(s[i]);
			else q2.push(s[i]);
		fore(i,1,n+1)
			if(i&1){
				std::cout<<q1.top();
				q1.pop();
			}
			else{
				std::cout<<q2.top();
				q2.pop();
			}
		std::cout<<endl;
	}
	else{
		s=s.substr(1);
		std::sort(s.begin(),s.end());
		std::cout<<s<<endl;
	}
}

int main(){
    std::ios::sync_with_stdio(false);
    std::cin.tie(nullptr);
    std::cout.tie(nullptr);
    int t;
    std::cin>>t;
    while(t--){
    	solve();
    }
	return 0; 
}

C. Divisor Chain

Codeforces Harbour.Space Scholarship Contest 2023-2024 (Div. 1 + Div. 2) A-F 个人练习_第3张图片

题意:
给定一个正整数 x x x,定义一个操作:

  • 选择 x x x 的一个因子 d d d x x x 减去 d d d,一个 d d d 最多使用两次

给出将 x x x 经过若干次操作变成 1 1 1 的详细过程

思路:
假如 x x x 的二进制表示只有一个 1 1 1,如 x = 8 ( 二进制表示是 100 0 2 ) x=8(二进制表示是1000_2) x=8(二进制表示是10002),那么我们可以这样操作:
8 → 4 → 2 → 1 8 \rightarrow 4 \rightarrow 2 \rightarrow 1 8421
相当于每次右移一位,并且所有使用的因子都是不同的

如果 x x x 的二进制表示不止一个 1 1 1,我们考虑如果将 x x x 变换成上述的形态

注意到一个二进制数的最低位的 1 1 1 一定是它的因子,如 x = 10 ( 二进制表示是 101 0 2 ) x=10(二进制表示是 1010_2) x=10(二进制表示是10102),最低位的 1 1 1 表示十进制的 2 2 2,是 x x x 的一个因子

因此我们可以将 x x x 的所有 1 1 1 从最低位一个个消去,最终变换成第一种最简单的形态,这种方法每个因子最多使用两次

// Problem: C. Divisor Chain
// Contest: Codeforces - Harbour.Space Scholarship Contest 2023-2024 (Div. 1 + Div. 2)
// URL: https://codeforces.com/contest/1864/problem/C
// Memory Limit: 256 MB
// Time Limit: 1000 ms
// 
// Powered by CP Editor (https://cpeditor.org)

#include
#define fore(i,l,r)	for(int i=(int)(l);i<(int)(r);++i)
#define fi first
#define se second
#define endl '\n' 

const int INF=0x3f3f3f3f;
const long long INFLL=0x3f3f3f3f3f3f3f3fLL;

typedef long long ll;

void solve(){
	int x;
	std::cin>>x;
	std::vector<int> ans;
	ans.push_back(x);
	int p=0;
	while(((1<<p)&x)==0)	++p;	//找到最低位的1
	while(x!=(1<<p)){
		x-=(1<<p);
		ans.push_back(x);
		++p;
		while(((1<<p)&x)==0)	++p;	//继续找下一个1
	}
	x>>=1;
	while(x){	//x=1000..00
		ans.push_back(x);
		x>>=1;
	}
	std::cout<<ans.size()<<endl;
	for(auto i : ans)	std::cout<<i<<' ';
	std::cout<<endl;
}

int main(){
    std::ios::sync_with_stdio(false);
    std::cin.tie(nullptr);
    std::cout.tie(nullptr);
    int t;
    std::cin>>t;
    while(t--){
    	solve();
    }
	return 0; 
}

D. Matrix Cascade

Codeforces Harbour.Space Scholarship Contest 2023-2024 (Div. 1 + Div. 2) A-F 个人练习_第4张图片

题意:
给定一个 n × n n \times n n×n 01 01 01 矩阵,定义一个操作:

  • 选择一个格子 ( i , j ) (i,j) (i,j),将其翻转,这次翻转会将 ∀ ( x , y ) , x > i a n d x − i ≥ ∣ y − j ∣ \forall (x,y),x>i \quad and \quad x-i\geq |y-j| (x,y),x>iandxiyj
    的格子都翻转

问如果要将矩阵变成全 0 0 0 ,最少需要多少次操作

思路:
不难发现其实一次操作会翻转的区域就是这个格子本身加上从这个格子开始向下的一个三角区域

Codeforces Harbour.Space Scholarship Contest 2023-2024 (Div. 1 + Div. 2) A-F 个人练习_第5张图片

所以对于第一行的 1 1 1,我们只能够通过翻转它本身来把它变成 0 0 0,而第二行到最后一行的 1 1 1 0 0 0 可能会被上面行的操作影响

翻完第一行的 1 1 1 后,第二行的 1 1 1 也是同理,这样就变成了一个个子问题,每次翻当前行的 1 1 1
但是这样直接暴力翻的时间复杂度可能会达到 O ( n 3 ) O(n^3) O(n3)

考虑优化,由于在同一个位置翻转两次等价于没有任何操作,所以一个位置要么只有一次操作,要么不操作
我们可以开一个开始翻转的差分数组 b b b 和一个结束翻转的差分数组 c c c b b b 代表斜率为 1 1 1 的那条直线, c c c 代表斜率为 − 1 -1 1 的那条直线,两个数组都初始化为 0 0 0,对于当前行,要一直从 b [ i ] [ j ] = t r u e b[i][j]=true b[i][j]=true 的位置翻转到 c [ i ] [ j ] = t r u e c[i][j]=true c[i][j]=true 的位置, b [ i + 1 ] [ j − 1 ] b[i+1][j-1] b[i+1][j1] b [ i ] [ j ] b[i][j] b[i][j] 继承, c [ i + 1 ] [ j + 1 ] c[i+1][j+1] c[i+1][j+1] c [ i ] [ j ] c[i][j] c[i][j] 继承

如果翻转 ( i , j ) (i,j) (i,j),那么 b [ i + 1 ] [ j − 1 ] = b [ i ] [ j ] ⨁ 1 b[i+1][j-1] = b[i][j] \bigoplus 1 b[i+1][j1]=b[i][j]1 c [ i + 1 ] [ j + 1 ] = c [ i ] [ j ] ⨁ 1 c[i+1][j+1] = c[i][j] \bigoplus 1 c[i+1][j+1]=c[i][j]1,也就是在原来的基础上多翻一次
如果不翻转这个位置的话,直接继承即可

注意, b b b 数组可能会在上面的行就达到了 j = 1 j=1 j=1 的状态,也就是说这一列下面所有的格子都会被影响,所以我们要在 b [ i + 1 ] [ 0 ] b[i+1][0] b[i+1][0] 继承一下, b [ i ] [ 0 ] b[i][0] b[i][0] 就表示那些超出来的开始翻转的位置
在处理完每一行后,都要将下一行的 b [ i + 1 ] [ 0 ] b[i+1][0] b[i+1][0] 更新,由于下一行的 b [ i + 1 ] [ 0 ] b[i+1][0] b[i+1][0] 可能受到了 b [ i ] [ 1 ] b[i][1] b[i][1] 的影响,所以 b [ i + 1 ] [ 0 ] b[i+1][0] b[i+1][0] 要直接 ⨁ b [ i ] [ 0 ] \bigoplus b[i][0] b[i][0]

时间复杂度: O ( n 2 ) O(n^2) O(n2)

#include
#define fore(i,l,r)	for(int i=(int)(l);i<(int)(r);++i)
#define fi first
#define se second
#define endl '\n' 

const int INF=0x3f3f3f3f;
const long long INFLL=0x3f3f3f3f3f3f3f3fLL;

typedef long long ll;

int main(){
    std::ios::sync_with_stdio(false);
    std::cin.tie(nullptr);
    std::cout.tie(nullptr);
    int t;
    std::cin>>t;
    while(t--){
    	int n;
    	std::cin>>n;
    	std::vector<std::string> s(n+1);
    	fore(i,1,n+1){
    		std::cin>>s[i];
    		s[i]='0'+s[i];
    	}
    	int ans=0;
    	std::vector<std::vector<int>> b(n+2,std::vector<int>(n+2,0));
    	std::vector<std::vector<int>> c(n+2,std::vector<int>(n+2,0));
    	fore(i,1,n+1){
    		int cur=b[i][0];	//当前位置是否被翻转过
    		fore(j,1,n+1){
    			cur=cur^b[i][j]^c[i][j-1];	//结束翻转c要有一个偏移量
    			if(s[i][j]=='0' && cur==1){
    				++ans;
    				b[i+1][j-1]=b[i][j]^1;	//在原来的基础上多翻转一次
    				c[i+1][j+1]=c[i][j]^1;
    			}
    			else if(s[i][j]=='1' && cur==0){
    				++ans;
    				b[i+1][j-1]=b[i][j]^1;	//在原来的基础上多翻转一次
    				c[i+1][j+1]=c[i][j]^1;
    			}
    			else{
    				b[i+1][j-1]=b[i][j];
    				c[i+1][j+1]=c[i][j];
    			}
    		}
    		b[i+1][0]^=b[i][0]; //斜率为1的直线把左边全部覆盖的情况
    	}
    	std::cout<<ans<<endl;
    }
	return 0; 
}

E. Guess Game

Codeforces Harbour.Space Scholarship Contest 2023-2024 (Div. 1 + Div. 2) A-F 个人练习_第6张图片

题意:
给定一个长度为 n n n 的非负整数序列 s s s A l i c e Alice Alice B o b Bob Bob 进行一个游戏:

随意选择两个下标 i , j i,j i,j (可能相同),令 a = s i b = s j a=s_i \hspace{5pt}b=s_j a=sib=sj,把 a a a a ∣ b a|b ab 的值告诉 A l i c e Alice Alice,把 b b b a ∣ b a|b ab 的值告诉 B o b Bob Bob,两人轮流回答

如果有一个玩家提出了 a a a b b b 的大小关系,那么游戏结束。每一轮当前玩家只能回答 I d o n ′ t k n o w I don't know Idontknow 或者 a = b 、 a > b 、 a < b a=b、a>b、aa=ba>ba<b 中的一个。两个玩家可以互相听到对方的回答

求出游戏结束的轮数的期望值 998244353 998244353 998244353 取模的结果

思路:
对于一对确定的 a , b a,b a,b,考虑它们的二进制表示, a ∣ b a|b ab 的位为 0 0 0 的那些位不用考虑,因为 a , b a,b a,b 在这些位都是 0 0 0。只考虑 a ∣ b a|b ab 0 0 0 的那些位,那么现在 a ∣ b a|b ab 就是 1 1 1 的序列,从最高位开始考虑,如果一开始 a a a 的最高位就是 0 0 0 ,那么显然在第一轮 A l i c e Alice Alice 就会直接回答 a < b aa<b,游戏结束。否则 A l i c e Alice Alice 就会回答 I d o n ′ t k n o w I don't know Idontknow,那么第二轮轮到 B o b Bob Bob 操作时, B o b Bob Bob 听到了他的回答,就可以确定 a a a 的这一位一定是 1 1 1,如果这时候 b b b 的这一位和更低一位有一个 0 0 0 的话, B o b Bob Bob 就会直接回答 b < a bb<a ,游戏结束;否则就会回答 I d o n ′ t k n o w I don't know Idontknow 。第三轮, A l i c e Alice Alice 操作时,就可以确定 b b b 的这两位一定都是 1 1 1

游戏的策略就是如此,模拟下去就会发现,对于 a a a b b b 最高位开始相同的那一串 1 1 1 来说,一轮操作就可以确定一个位置的 1 1 1 ,所以对于 a ∣ b a|b ab 的值:

  • 如果 a < b aa<b :令 i i i 是从 a ∣ b a|b ab 的最高位开始数(从 1 1 1开始计数)的第一个不同的位置: a i = 0 , b i = 1 a_i=0,b_i=1 ai=0,bi=1,游戏轮数就是 i + 1 − ( i i+1-(i i+1(i% 2 = = 1 ) 2==1) 2==1)。例如 a = 4 , b = 5 a=4,b=5 a=4,b=5,轮数就是 3 3 3

  • 如果 a = b a=b a=b : 令 k k k 等于 a ∣ b a|b ab 里面 1 1 1 的数量,轮数就是 k + 1 k+1 k+1

  • 如果 a > b a>b a>b :令 i i i 是从 a ∣ b a|b ab 的最高位开始数(从 1 1 1开始计数)的第一个不同的位置: a i = 1 , b i = 0 a_i=1,b_i=0 ai=1,bi=0,游戏轮数就是 i + ( i i+(i i+(i% 2 = = 1 ) 2==1) 2==1)。例如 a = 6 , b = 4 a=6,b=4 a=6,b=4,轮数就是 2 2 2

如果直接暴力列举每一对 a , b a,b a,b 的话,时间复杂度会达到 O ( n 2 l o g s i ) O(n^2logs_i) O(n2logsi)

可以使用 b i t T r i e bit Trie bitTrie 优化,从第 30 30 30 位开始插入,每一个节点最多有两个儿子, 一个表示下一位是 0 0 0,另一个表示下一位是 1 1 1。再统计每个节点的通过的数字数量,可以发现每个分叉点就是一些数字第一次出现不同的地方。并且 a < b aa<b a > b a>b a>b 的情况可以合并起来,贡献变为 2 i + 1 2i+1 2i+1

单独考虑每个点的贡献,时间复杂度: O ( n l o g s i ) O(nlogs_i) O(nlogsi)

最后用费马小定理求出 n 2 n^2 n2 的逆,乘起来即可

// Problem: E. Guess Game
// Contest: Codeforces - Harbour.Space Scholarship Contest 2023-2024 (Div. 1 + Div. 2)
// URL: https://codeforces.com/contest/1864/problem/E
// Memory Limit: 256 MB
// Time Limit: 3000 ms
// 
// Powered by CP Editor (https://cpeditor.org)

#include
#define fore(i,l,r)	for(int i=(int)(l);i<(int)(r);++i)
#define fi first
#define se second
#define endl '\n' 

const int INF=0x3f3f3f3f;
const long long INFLL=0x3f3f3f3f3f3f3f3fLL;

typedef long long ll;

const ll mod=998244353;

struct node{
	int to[2];
	ll cnt;
	node(){
		cnt=0;
		to[0]=to[1]=-1;
	}
};

std::vector<node> tree;

ll fast_pow(ll a,ll b,ll mod){
	ll res=1;
	a%=mod;
	while(b){
		if(b&1)	res=res*a%mod;
		a=a*a%mod;
		b>>=1;
	}
	return res;
}

int bit(int x,int p){
	return (x>>p)&1;
}

void insert(int x){
	int idx=0;
	for(int i=29;i>=0;--i){
		int b=bit(x,i);
		if(tree[idx].to[b]==-1){
			tree[idx].to[b]=tree.size();
			tree.push_back(node());
		}
		++tree[idx].cnt;
		idx=tree[idx].to[b];
	}
	++tree[idx].cnt;
}

void solve(int idx,int k,ll& ans){
	if(tree[idx].to[0]!=-1 && tree[idx].to[1]!=-1){	//ab
		ll i=k+1;
		ans=(ans+(2ll*i+1ll)*tree[tree[idx].to[0]].cnt%mod*tree[tree[idx].to[1]].cnt%mod)%mod;
	}
	else if(tree[idx].to[0]==-1 && tree[idx].to[1]==-1){	//a=b
		ll i=k+1;
		ans=(ans+i*tree[idx].cnt%mod*tree[idx].cnt%mod)%mod;
	}
	if(tree[idx].to[0]!=-1)	solve(tree[idx].to[0],k,ans);
	if(tree[idx].to[1]!=-1) solve(tree[idx].to[1],k+1,ans);
}

int main(){
    std::ios::sync_with_stdio(false);
    std::cin.tie(nullptr);
    std::cout.tie(nullptr);
    int t;
    std::cin>>t;
    while(t--){
    	tree.clear();
    	tree.push_back(node());
    	int n;
    	std::cin>>n;
    	fore(i,0,n){
    		int x;
    		std::cin>>x;
    		insert(x);
    	}
    	ll ans=0;
    	solve(0,0,ans);
    	ans=ans*fast_pow(1ll*n*n,mod-2,mod)%mod;
    	std::cout<<ans<<endl;
    }
	return 0; 
}

F. Exotic Queries

Codeforces Harbour.Space Scholarship Contest 2023-2024 (Div. 1 + Div. 2) A-F 个人练习_第7张图片

题意:
给定一个长度为 n n n 的正整数序列 a a a q q q 个询问,每个询问包含一对 l , r l,r l,r
对于每个独立的询问,可以在序列 a a a 内选择一段连续的区间,将这段区间内的所有数字减去一个非负值,一次询问可以选择多个区间或者不选。注意所有选择的区间要么互相包含,要么互不相交
对于每个询问,回答要将序列 a a a值域 [ l , r ] [l,r] [l,r] 之间的元素全部变为 0 0 0 ,最少需要多少次操作?

思路:
只考虑每个询问值域里面的那些元素,可以发现最优的策略一定是先把最小的元素减为 0 0 0,然后考虑次小的,一直这样操作下去。

但是一次操作过后可能会形成很多个 小山峰,这些小山峰被中间更小的元素隔离开了,必须单独操作
[ 2 , 3 , 1 , 3 ] [2,3,1,3] [2,3,1,3],操作完第一次后: [ 1 , 2 , 0 , 2 ] [1,2,0,2] [1,2,0,2],分成了左右两个山峰,这时候单独考虑两个山峰的最小值,分成很多个子问题

对于一个询问 [ l , r ] [l,r] [l,r],最少的操作次数就是 a a a 的值域在 [ l , r ] [l,r] [l,r] 有多少种数字,这时候从头到尾只有一个山峰。其他情况之所以答案会增加是因为中途出现了山谷
对于这个样例的 [ 2 , 3 , 1 , 3 ] [2,3,1,3] [2,3,1,3],两个 3 3 3 之间比它们小的最大的数字 1 1 1,会形成山谷,因此两个 3 3 3 对答案的贡献会 + 1 +1 +1

一般地,对于在一个询问值域 [ l , r ] [l,r] [l,r] 里的所有元素,相等的两个元素之间比它们小的最大的数字如果大于等于 l l l,那么它们这一对对答案的贡献会 + 1 +1 +1,我们只需要统计有多少个这样的数对即可

对于如何维护相等的两个元素之间比它们小的最大的数字,我们可以使用线段树,按照升序的顺序,把 a a a 内的元素从小到大插入线段树,这样子每一对相等元素中间的位置查询到的最大值,一定都是严格比它们小的

然后我们单独考虑每一对相等元素对答案的贡献,可以发现它们只会对询问区间左端点 l ≤ m a x v l \leq maxv lmaxv (它们中间比他们小的最大元素)的询问有贡献,我们可以在树状数组上面维护这些所有的贡献,然后对于一个询问 [ l , r ] [l,r] [l,r],只需要考虑 [ l , r ] [l,r] [l,r] 的贡献即可,就是一个前缀和之差 s u m ( r ) − s u m ( l − 1 ) sum(r)-sum(l-1) sum(r)sum(l1)

基于上面的思路,我们只需离线处理,对所有询问按区间右端点排列,然后对每个询问先更新线段树和树状数组,在计算答案。这样可以保证当前树状数组上的所有贡献都是值域 ≤ r \leq r r

// Problem: F. Exotic Queries
// Contest: Codeforces - Harbour.Space Scholarship Contest 2023-2024 (Div. 1 + Div. 2)
// URL: https://codeforces.com/contest/1864/problem/F
// Memory Limit: 1024 MB
// Time Limit: 4000 ms
// 
// Powered by CP Editor (https://cpeditor.org)

#include
#define fore(i,l,r)	for(int i=(int)(l);i<(int)(r);++i)
#define fi first
#define se second
#define endl '\n' 
#define lowbit(x)	((x)&-(x))

const int INF=0x3f3f3f3f;
const long long INFLL=0x3f3f3f3f3f3f3f3fLL;

typedef long long ll;

const int N=1000050;

struct node{
	int maxv;
	int l;
	int r;
};

struct ask{
	int l;
	int r;
	int id;
}query[N];

struct SegmentTree{	//线段树维护区间最大值
	node tree[N<<2];
	
	void build(int p,int l,int r){
		tree[p]={0,l,r};
		if(l==r)	return;
		int mid=l+r>>1;
		build(p<<1,l,mid);
		build(p<<1|1,mid+1,r);
		tree[p].maxv=std::max(tree[p<<1].maxv,tree[p<<1|1].maxv);
	}
	
	void update(int p,int target,int val){
		if(tree[p].l==tree[p].r){
			tree[p].maxv=val;
			return;
		}
		int mid=tree[p].l+tree[p].r>>1;
		if(target<=mid)	update(p<<1,target,val);
		else update(p<<1|1,target,val);
		tree[p].maxv=std::max(tree[p<<1].maxv,tree[p<<1|1].maxv);
	}
	
	int query(int p,int l,int r){
		if(l<=tree[p].l && tree[p].r<=r)	return tree[p].maxv;
		if(r<tree[p].l || l>tree[p].r)	return 0;
		int mid=tree[p].l+tree[p].r>>1;
		int res=0;
		if(l<=mid)	res=std::max(res,query(p<<1,l,r));
		if(r>mid)	res=std::max(res,query(p<<1|1,l,r));
		return res;
	}
	
}sgtree;

struct FenwickTree{	//树状数组记录贡献
	int n;
	std::vector<int> tree;
	
	FenwickTree(int n){
		this->n=n;
		tree.assign(n+1,0);
	}
	
	void update(int x,int d){
		while(x<=n){
			tree[x]+=d;
			x+=lowbit(x);
		}
	}
	
	int sum(int x){
		int res=0;
		while(x>0){
			res+=tree[x];
			x-=lowbit(x);
		}
		return res;
	}
	
};

bool cmp(const ask& x,const ask& y){
	return x.r<y.r;
}


int main(){
    std::ios::sync_with_stdio(false);
    std::cin.tie(nullptr);
    std::cout.tie(nullptr);
    int n,q;
    std::cin>>n>>q;
    std::vector<int> v(n+1);
    std::vector<std::vector<int>> a(n+1,std::vector<int>());
    std::vector<int> cnt(n+1,0);	//数组在某一段值域一共有多少种数字
    std::vector<int> ans(q+1);
    fore(i,1,n+1){
    	std::cin>>v[i];
    	cnt[v[i]]=1;	//这个值域有一种
    	a[v[i]].push_back(i);
    }
    fore(i,1,q+1){
    	std::cin>>query[i].l>>query[i].r;	
    	query[i].id=i;
    }
    std::sort(query+1,query+q+1,cmp);	//按区间右端点排序
    sgtree.build(1,1,n);
    fore(i,1,n+1)	cnt[i]+=cnt[i-1];	//数字种类累加
    FenwickTree fentree(n);
    
    int x=1;
    fore(i,1,q+1){
    	int l=query[i].l,r=query[i].r;
    	while(x<=r){
    		if(a[x].size()>=2){
    			fore(i,0,a[x].size()-1){
    				/* 查询比它们小的最大值,并记录贡献 */
    				int maxv=sgtree.query(1,a[x][i],a[x][i+1]);
    				if(maxv!=0)	fentree.update(maxv,1);
    			}
    		}
    		for(auto pos : a[x])	sgtree.update(1,pos,x);
    		++x;
    	}
    	ans[query[i].id]=cnt[r]-cnt[l-1]+fentree.sum(r)-fentree.sum(l-1);
    }
    
    fore(i,1,q+1)	std::cout<<ans[i]<<endl;
    
	return 0; 
}

你可能感兴趣的:(codeforces,练习,算法)