Codeforces Round #727 (Div. 2) 题解

本文涵盖了数学题的区间影响问题,如固定人数的贡献计算,以及字符串前缀和、稳定分组、卡片游戏、奇怪数组等技术的贪心和线段树解决方案。通过实例展示了如何利用算法解决实际问题,如爱歌字符统计、稳定组合和复杂游戏策略。
摘要由CSDN通过智能技术生成


#727-Div.2

A. Contest Start

数学题,分类讨论

  • 一般的,一段区间 [ l , r ] [l,r] [l,r]会对后面固定人数造成影响,假设是 k k k
  • 最后 k k k个人,因为自己后面的人数不够 k k k,所以贡献总和是 k × ( k − 1 ) 2 \frac{k\times(k-1)}{2} 2k×(k1)

显然固定人数为 t x \frac{t}{x} xt

#include <cstdio>
#define int long long
int T, n, x, t;

signed main() {
	scanf( "%lld", &T );
	while( T -- ) {
		scanf( "%lld %lld %lld", &n, &x, &t );
		int k = t / x;
		if( n < k ) printf( "%lld\n", n * ( n - 1 ) / 2 );
		else printf( "%lld\n", ( n - k ) * k + ( k - 1 ) * k / 2 );
	}
	return 0;
}

B. Love Song

前缀和统计 [ l , r ] [l,r] [l,r]区间内每个字符的个数 × \times ×字符在字母表的排位

#include <cstdio>
#define maxn 100005
int n, Q;
char s[maxn];
int cnt[maxn][26];

int main() {
	scanf( "%d %d %s", &n, &Q, s + 1 );
	for( int i = 1;i <= n;i ++ ) {
		for( int j = 0;j < 26;j ++ )
			cnt[i][j] = cnt[i - 1][j];
		cnt[i][s[i] - 'a'] ++;
	}
	while( Q -- ) {
		int l, r;
		scanf( "%d %d", &l, &r );
		int ans = 0;
		for( int i = 0;i < 26;i ++ )
			ans += ( i + 1 ) * ( cnt[r][i] - cnt[l - 1][i] );
		printf( "%d\n", ans );
	}
	return 0;
}

C. Stable Groups

贪心。

排个序,把能放在一起的放在一起,然后记录相邻组之间需要的“桥”的个数,用优先队列存储

显然,需要越少越先满足,留下更多的 k k k满足后面

#include <queue>
#include <cstdio>
#include <algorithm>
using namespace std;
#define int long long
#define maxn 200005
priority_queue < int, vector < int >, greater < int > > q;
int n, k, x;
int a[maxn];

signed main() {
	scanf( "%lld %lld %lld", &n, &k, &x );
	for( int i = 1;i <= n;i ++ )
		scanf( "%lld", &a[i] );
	sort( a + 1, a + n + 1 );
	int cnt = 1;
	for( int i = 1;i < n;i ++ )
		if( a[i + 1] - a[i] > x ) {
			cnt ++;
			q.push( ( a[i + 1] - a[i] - 1 ) / x );
		}
	while( ! q.empty() ) {
		if( q.top() <= k ) k -= q.top(), cnt --, q.pop();
		else break;
	}
	printf( "%lld\n", cnt );
	return 0;
}

D. PriceFixed

贪心模拟,按 b b b排序

用最难满足的购买尽可能触碰易满足的 b b b

#include <cstdio>
#include <algorithm>
using namespace std;
#define int long long
#define maxn 100005
struct node {
	int a, b;
}it[maxn];
int n;

bool cmp( node x, node y ) {
	return x.b > y.b;
}

signed main() {
	scanf( "%lld", &n );
	for( int i = 1;i <= n;i ++ )
		scanf( "%lld %lld", &it[i].a, &it[i].b );
	sort( it + 1, it + n + 1, cmp );
	int tot = 0, ans = 0;
	for( int i = 1;i <= n;i ++ ) {
		if( tot >= it[n].b ) {
			ans += it[n].a;
			tot += it[n].a;
			it[n].a = 0;
			i --;
			n --;
		}
		else if( tot + it[i].a < it[n].b ) {
			tot += it[i].a;
			ans += ( it[i].a << 1 );
			it[i].a = 0;
		} else {
			int t = it[n].b - tot;
			it[i].a -= t;
			ans += ( t << 1 );
			tot += t;
			ans += it[n].a;
			tot += it[n].a;
			it[n].a = 0;
			n --;
			i --;
		}
	}
	if( ! it[n].a )
		printf( "%lld\n", ans );
	else {
		if( tot >= it[n].b )
			printf( "%lld\n", ans + it[n].a );
		else if( it[n].a + tot <= it[n].b )
				printf( "%lld\n", ans + ( it[n].a << 1 ) );
		else {
			int t = it[n].b - tot;
			ans += ( t << 1 );
			it[n].a -= t;
			ans += it[n].a;
			printf( "%lld\n", ans );
		}
	}
	return 0;
}

E. Game with Cards

L i , j : L_{i,j}: Li,j: 左手为第 i i i个数,右手为第 j j j个数, i < j i<j i<j,是否合法

R i , j : R_{i,j}: Ri,j: 右手为第 i i i个数,左手为第 j j j个数, i < j i<j i<j,是否合法

显然有暴力的 n 2 n^2 n2转移
L i , j = [ a i ∈ [ a l , j , a r , j ⋂ a j ∈ [ b l , j , b r , j ] ] = { L i , j − 1 i ≠ j − 1 ∑ k = 1 j − 2 R k , j − 1 i = j − 1 R i , j = [ a i ∈ [ b l , j , b r , j ⋂ a j ∈ [ a l , j , a r , j ] ] = { R i , j − 1 i ≠ j − 1 ∑ k = 1 j − 2 L k , j − 1 i = j − 1 L_{i,j}=\bigg[a_i\in [a_{l,j},a_{r,j}\bigcap a_j\in[b_{l,j},b_{r,j}]\bigg]=\begin{cases}L_{i,j-1}&&&&&i≠j-1\\\sum_{k=1}^{j-2}R_{k,j-1}&&&&&i=j-1\end{cases}\\R_{i,j}=\bigg[a_i\in [b_{l,j},b_{r,j}\bigcap a_j\in[a_{l,j},a_{r,j}]\bigg]=\begin{cases}R_{i,j-1}&&&&&i≠j-1\\\sum_{k=1}^{j-2}L_{k,j-1}&&&&&i=j-1\end{cases}\\ Li,j=[ai[al,j,ar,jaj[bl,j,br,j]]={Li,j1k=1j2Rk,j1i=j1i=j1Ri,j=[ai[bl,j,br,jaj[al,j,ar,j]]={Ri,j1k=1j2Lk,j1i=j1i=j1
显然是可以用线段树维护成 l o g log log

#include <cstdio>
#define maxn 100005
int n, m;

struct SegMentTree {
	struct node {
		int l, r, tag, id, s;
	}t[maxn * 30];
	int cnt = 0, root;
	
	#define lson t[now].l
	#define rson t[now].r
	
	void pushdown( int now ) {
		if( t[now].tag ) {
			t[lson].s = t[rson].s = 0;
			t[lson].tag = t[rson].tag = t[now].tag;
			t[now].tag = 0;
		}	
	}
	
	void clear( int now, int l, int r, int L, int R ) {
		if( r < L || R < l ) return;
		if( ! now ) return;
		if( L <= l && r <= R ) {
			t[now].s = 0;
			t[now].tag = 1;
			return;
		}
		pushdown( now );
		int mid = ( l + r ) >> 1;
		clear( lson, l, mid, L, R );
		clear( rson, mid + 1, r, L, R );
		t[now].s = t[lson].s | t[rson].s;
	}
	
	void insert( int &now, int l, int r, int pos, int id ) {
		if( ! now ) now = ++ cnt;
		if( l == r ) {
			t[now].s = 1;
			t[now].id = id;
			return;
		}
		pushdown( now );
		int mid = ( l + r ) >> 1;
		if( pos <= mid ) insert( lson, l, mid, pos, id );
		else insert( rson, mid + 1, r, pos, id );
		t[now].s = t[lson].s | t[rson].s;
	}
	
	int query( int now = 1, int l = 0, int r = m ) {
		if( l == r ) return t[now].id;
		pushdown( now );
		int mid = ( l + r ) >> 1;
		if( t[lson].s ) return query( lson, l, mid );
		else return query( rson, mid + 1, r );
	}
	
	bool check() { return t[1].s; }
	
}L, R;

struct read {
	int x, L_rangel, L_ranger, R_rangel, R_ranger;
	read(){}
	read( int X, int AL, int AR, int BL, int BR ) {
		x = X, L_rangel = AL, L_ranger = AR, R_rangel = BL, R_ranger = BR;
	}
}MS[maxn];

int ansl[maxn], ansr[maxn], ans[maxn];
bool ChooseL[maxn], ChooseR[maxn];

int main() {
	scanf( "%d %d", &n, &m );
	L.insert( L.root, 0, m, 0, 0 );
	R.insert( R.root, 0, m, 0, 0 );
	for( int i = 1, x, L_rangel, L_ranger, R_rangel, R_ranger;i <= n;i ++ ) {
		scanf( "%d %d %d %d %d", &x, &L_rangel, &L_ranger, &R_rangel, &R_ranger );
		MS[i] = read( x, L_rangel, L_ranger, R_rangel, R_ranger );
		bool l_ok = L.check(), r_ok = R.check();
		if( l_ok ) ansl[i] = L.query();
		else ansl[i] = -1;
		if( r_ok ) ansr[i] = R.query();
		else ansr[i] = -1;
		if( L_rangel <= x && x <= L_ranger ) {
		//x is included in L->[l,r] which is suitable for request of option 0
			R.clear( R.root, 0, m, 0, R_rangel - 1 );
			R.clear( R.root, 0, m, R_ranger + 1, m );
			if( i > 1 && R_rangel <= MS[i - 1].x && MS[i - 1].x <= R_ranger && l_ok )
				R.insert( R.root, 0, m, MS[i - 1].x, i - 1 ), ChooseR[i] = 1;
			//however we must also sure that the number we keep on right hand is also included in R->[l,r] 
			//only both are acceptable can we do option 0 truly
		}
		else R.clear( R.root, 0, m, 0, m );//otherwise the whole array of L should be cleared
		if( R_rangel <= x && x <= R_ranger ) {
			L.clear( L.root, 0, m, 0, L_rangel - 1 );
			L.clear( L.root, 0, m, L_ranger + 1, m );
			if( i > 1 && L_rangel <= MS[i - 1].x && MS[i - 1].x <= L_ranger && r_ok )
				L.insert( L.root, 0, m, MS[i - 1].x, i - 1 ), ChooseL[i] = 1;
		}
		else L.clear( L.root, 0, m, 0, m );
	}
	if( L.check() || R.check() ) {//backward print(END -> BEGIN)
		printf( "Yes\n" );
		int pos = n, lim, f;
		if( L.check() ) lim = L.query(), f = 0;
		else lim = R.query(), f = 1;
		while( pos ) {
			if( f ) {//this option choose 0(replace left)
				ans[pos] = 0;
				if( pos > lim + 1 ) goto out;
				else f ^= 1, lim = ansl[pos];
			}
			else {//this option choose 1(replace right)
				ans[pos] = 1;
				if( pos > lim + 1 ) goto out;
				else f ^= 1, lim = ansr[pos];
			}
			out : pos --;
		}
		for( int i = 1;i <= n;i ++ )
			printf( "%d ", ans[i] );
	}
	else printf( "No\n" );
	return 0;
}

F. Strange Array

连着两题线段树好,很好!

定义 S 1 : [ l , r ] S_1:[l,r] S1:[l,r] 中小于 a i a_i ai的个数, S 2 : [ l , r ] S_2:[l,r] S2:[l,r] 中等于 a i a_i ai的个数, S 3 : [ l , r ] S_3:[l,r] S3:[l,r] 中大于 a i a_i ai的个数

显然位置 i i i的奇怪度为
max ⁡ { S 1 + S 2 − ⌈ S 1 + S 3 + S 2 + 1 2 ⌉ ⌈ S 1 + S 3 + S 2 + 1 2 ⌉ − ( S 1 + 1 ) \max\begin{cases} S_1+S_2-\lceil\frac{S_1+S_3+S_2+1}{2}\rceil\\ \lceil\frac{S_1+S_3+S_2+1}{2}\rceil-(S_1+1) \end{cases} max{S1+S22S1+S3+S2+12S1+S3+S2+1(S1+1)
发现:同时在比 a i a_i ai大和比 a i a_i ai小的数中扔掉若干个相同的数,答案并不会发生变化

如此说来有用的只有二者的差,S1S2S3

如果 a i a_i ai放在 S 2 S_2 S2的第一位,我们则非常希望 m a x { S 3 } , m i n { S 1 } max\{S_3\},min\{S_1\} max{S3},min{S1}这样中位数才会远离 S 2 S_2 S2的第一位

如果 a i a_i ai放在 S 2 S_2 S2的倒数第一位,我们则非常希望 m i n { S 3 } , m a x { S 1 } min\{S_3\},max\{S_1\} min{S3},max{S1}

显然可以线段树维护查询了,将小于等于 a i a_i ai的标记为 − 1 -1 1

对于若干个相同的 a i a_i ai位置,先查再改是一种情况,改了再查是另一种情况

#include <cstdio>
#include <vector>
#include <iostream>
#include <algorithm>
using namespace std;
#define maxn 200005
struct node {
	int maxx, minn, lazy;
}t[maxn << 2];
vector < int > s[maxn];
int n;
int ans[maxn];

void pushdown( int num ) {
	t[num << 1].maxx += t[num].lazy;
	t[num << 1].minn += t[num].lazy;
	t[num << 1].lazy += t[num].lazy;
	t[num << 1 | 1].maxx += t[num].lazy;
	t[num << 1 | 1].minn += t[num].lazy;
	t[num << 1 | 1].lazy += t[num].lazy;
	t[num].lazy = 0;
}

void modify( int num, int l, int r, int L, int R, int val ) {
	if( L <= l && r <= R ) {
		t[num].lazy += val;
		t[num].maxx += val;
		t[num].minn += val;
		return;
	}
	pushdown( num );
	int mid = ( l + r ) >> 1;
	if( L <= mid ) modify( num << 1, l, mid, L, R, val );
	if( mid < R ) modify( num << 1 | 1, mid + 1, r, L, R, val );
	t[num].maxx = max( t[num << 1].maxx, t[num << 1 | 1].maxx );
	t[num].minn = min( t[num << 1].minn, t[num << 1 | 1].minn );
}

int query_max( int num, int l, int r, int L, int R ) {
	if( L > R ) return 0; 
	if( R < l || r < L ) return -1e9;
	if( L <= l && r <= R ) return t[num].maxx;
	pushdown( num );
	int mid = ( l + r ) >> 1;
	return max( query_max( num << 1, l, mid, L, R ), query_max( num << 1 | 1, mid + 1, r, L, R ) );
}

int query_min( int num, int l, int r, int L, int R ) {
	if( L > R ) return 0;
	if( R < l || r < L ) return 1e9;
	if( L <= l && r <= R ) return t[num].minn;
	pushdown( num );
	int mid = ( l + r ) >> 1;
	return min( query_min( num << 1, l, mid, L, R ), query_min( num << 1 | 1, mid + 1, r, L, R ) );
}


int main() {
	scanf( "%d", &n );
	for( int i = 1, x;i <= n;i ++ ) {
		scanf( "%d", &x );
		s[x].push_back( i );
		modify( 1, 1, n, i, n, -1 );
	}
	for( int i = 1;i <= n;i ++ ) {
		for( int j = 0;j < s[i].size();j ++ ) {
		//放在众多相同的最后一个->后面的尽量少前面的尽量多 
			int k = s[i][j];
			int l = max( 0, query_max( 1, 1, n, 1, k - 1 ) );//compare with 0 means just choosing k itself
			int r = query_min( 1, 1, n, k, n );
			ans[k] = max( ans[k], ( l - r + 2 ) / 2 - 1 );
		}
		for( int j = 0;j < s[i].size();j ++ )
			modify( 1, 1, n, s[i][j], n, 2 );
		//抵消掉之前i对[i,n]造成的-1影响 然后再变成1后的影响 所以一次性加2 
		for( int j = 0;j < s[i].size();j ++ ) {
		//放在众多相同的第一个->后面的尽量多前面的尽量少 
			int k = s[i][j];
			int l = min( 0, query_min( 1, 1, n, 1, k - 1 ) );
			int r = query_max( 1, 1, n, k, n );
			ans[k] = max( ans[k], ( r - l ) - ( r - l + 2 ) / 2 );
		}
	}
	for( int i = 1;i <= n;i ++ )
		printf( "%d ", ans[i] );
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值