Codeforces Round #740 (Div. 2, based on VK Cup 2021 - Final (Engine)) A-F全题解

Codeforces Round #740 (Div. 2, based on VK Cup 2021 - Final (Engine))

A. Simply Strange Sort

签到题,暴力做

#include <cstdio>
#include <iostream>
using namespace std;
#define maxn 1000
int n, T;
int a[maxn];

int main() {
	scanf( "%d", &T );
	while( T -- ) {
		scanf( "%d", &n );
		for( int i = 1;i <= n;i ++ ) scanf( "%d", &a[i] );
		int ans = 0;
		for( int i = 1;i;i ++ ) {
			for( int j = ( i & 1 ? 1 : 2 );j < n;j += 2 )
				if( a[j] > a[j + 1] ) swap( a[j], a[j + 1] ), ans = i;
			bool flag = 1;
			for( int j = 1;j < n;j ++ )
				if( a[j] > a[j + 1] ) {
					flag = 0;
					break;
				}
			if( flag ) break;
		}
		printf( "%d\n", ans );
	}
	return 0;
}

B. Charmed by the Game

简单题

分总场数 n = a + b n=a+b n=a+b的奇偶讨论,假设以Alice Bob第一场发球

算出每个人发球的场数,计算出某个胜场不够的人至少要break多少场

之后只能两人相互break相同场数才能保持彼此赢的场数满足a/b

vis[i]记录break恰好 i i i场是否可以,每次都是 + 2 +2 +2暴力加

碰到已经打过标记的就可以跳出循环

#include <cstdio>
#define maxn 200005
int ans;
bool vis[maxn];

int main() {
	int T, a, b, n, ta, tb;
	scanf( "%d", &T );
	while( T -- ) {
		scanf( "%d %d", &a, &b );
		n = a + b, ans = 0;
		for( int i = 0;i <= n;i ++ ) vis[i] = 0;
		if( n & 1 ) {
			ta = ( n + 1 ) >> 1, tb = n >> 1;
			if( ta < a )
				for( int i = a - ta;i <= a - ta + 2 * b;i += 2 )
					if( ! vis[i] ) vis[i] = 1; else break;
			else
				for( int i = b - tb;i <= b - tb + 2 * a;i += 2 )
					if( ! vis[i] ) vis[i] = 1; else break;
			ta = n >> 1, tb = ( n + 1 ) >> 1;
			if( ta < a )
				for( int i = a - ta;i <= a - ta + 2 * b;i += 2 )
					if( ! vis[i] ) vis[i] = 1; else break;
			else
				for( int i = b - tb;i <= b - tb + 2 * a;i += 2 )
					if( ! vis[i] ) vis[i] = 1; else break;
		}
		else {
			ta = tb = n >> 1;
			if( ta < a )
				for( int i = a - ta;i <= a - ta + 2 * b;i += 2 )
					if( ! vis[i] ) vis[i] = 1; else break;
			else
				for( int i = b - tb;i <= b - tb + 2 * a;i += 2 )
					if( ! vis[i] ) vis[i] = 1; else break;
		}
		for( int i = 0;i <= n;i ++ ) ans += vis[i];
		printf( "%d\n", ans );
		for( int i = 0;i <= n;i ++ ) 
			if( vis[i] ) printf( "%d ", i );
		printf( "\n" );
	}
	return 0;
}

C. Deep Down Below

简单题

贪心

考虑要通过一个洞穴,初始带的力量值最小是多少

显然为 max ⁡ { a r m o r i + 1 − ( i − 1 ) } \max\Big\{armor_i+1-(i-1)\Big\} max{armori+1(i1)}

在第 i i i个怪物前面可以有 i − 1 i-1 i1个怪物杀死获得提升的机会,然后要求严格大于怪物生命值,所以 + 1 +1 +1

将这个值定义为洞穴的通关要求,升序排列洞穴

每次通过一个洞穴力量值就会增加洞穴的怪物数那么大

在下一个洞穴前判断能否通过,不能就加上差的力量值

#include <cstdio>
#include <algorithm>
using namespace std;
#define int long long
#define maxn 100005
struct node { int id, val, k; }armor[maxn];
int T, n;

signed main() {
	scanf( "%lld", &T );
	while( T -- ) {
		scanf( "%lld", &n );
		for( int i = 1, x;i <= n;i ++ ) {
			armor[i].id = i, armor[i].val = 0;
			scanf( "%lld", &armor[i].k );
			for( int j = 1;j <= armor[i].k;j ++ ) {
				scanf( "%lld", &x );
				armor[i].val = max( armor[i].val, x + 1 - ( j - 1 ) );
			}
		}
		sort( armor + 1, armor + n + 1, []( node x, node y ) { return x.val < y.val; } );
		int ans = armor[1].val, now = armor[1].val;
		for( int i = 1;i <= n;i ++ )
			if( now >= armor[i].val ) now += armor[i].k;
			else ans += armor[i].val - now, now = armor[i].val + armor[i].k;
		printf( "%lld\n", ans );
	}
	return 0;
}

D1/D2. Up the Strip

简单 D P DP DP

设计 f i : f_i: fi: 移动到格子 i i i的方案数,由 n → 1 n\rightarrow 1 n1转移

  • i i i后面的每个格子都可以通过 − x -x x的操作转移过来

    f i + = ∑ j = i + 1 n f j f_i+=\sum_{j=i+1}^nf_j fi+=j=i+1nfj

  • i i i后面的格子(以 2 ∗ i 2*i 2i开始)都可以通过 / x /x /x的操作转移过来

    但是因为是下取整,所以有的 j j j,可能除以多个 x x x都能转移到 i i i,不能简单只加一个 f j f_j fj完事

    不妨反其道而行之,枚举 x x x,计算出区间 [ l , r ] [l,r] [l,r]满足 j ∈ [ l , r ] , ⌊ j x ⌋ = i j\in[l,r],\lfloor\frac{j}{x}\rfloor=i j[l,r],xj=i

    很简单的数学计算一下, l = i ∗ x , r = x ∗ ( i + 1 ) − 1 l=i*x,r=x*(i+1)-1 l=ix,r=x(i+1)1

    f i + = ∑ j = l r f j f_i+=\sum_{j=l}^rf_j fi+=j=lrfj

    时间复杂度是调和级数, l o g n \rm logn logn

后缀优化即可, s u m i = ∑ j = i n f j sum_i=\sum_{j=i}^nf_j sumi=j=infj

#include <cstdio>
#include <iostream>
using namespace std;
#define int long long
#define maxn 4000005
int n, mod;
int f[maxn], sum[maxn];

signed main() {
	scanf( "%lld %lld", &n, &mod );
	f[n] = sum[n] = 1;
	for( int i = n - 1;i;i -- ) {
		f[i] = sum[i + 1];
		for( int j = 2;i * j <= n;j ++ ) {
			int l = i * j, r = min( n, j * ( i + 1 ) - 1 );
			f[i] = ( f[i] + sum[l] - sum[r + 1] + mod ) % mod;
		}
		sum[i] = ( sum[i + 1] + f[i] ) % mod;
	}
	printf( "%lld\n", f[1] );
	return 0;
}

E. Bottom-Tier Reversals

勉强中档题

限制次数为 5 n 2 \frac{5n}{2} 25n,且只能操作奇数

暗示构造,且相邻奇偶绑定在一起才行

也就是说能否寻找一种构造

5 5 5次操作内,将 i , i − 1 i,i-1 i,i1放到相应位置且以后不再操作这两个数,操作范围变成 i − 2 i-2 i2

observation : 每次操作 [ 1 , x ] [1,x] [1,x] i ↔ x − i + 1 i\leftrightarrow x-i+1 ixi+1 x + 1 x+1 x+1为偶数,所以 i , x − i + 1 i,x-i+1 i,xi+1同奇偶

最后要满足a[i]=i,下标必须和值是同奇偶才会有解

判完无解后, 5 5 5次操作这是可以做到的

  • n → 1 n\rightarrow 1 n1构造,每次满足 n , n − 1 n,n-1 n,n1 i i i为奇数)放到相应位置,每次操作后范围 n − = 2 n-=2 n=2
  • 找到 i i i的位置 x x x(显然 x x x为奇数),第一次操作 [ 1 , x ] [1,x] [1,x]区间,使得 i i i成为序列第一个
  • 找到 i − 1 i-1 i1的位置 y y y(显然 y y y为偶数),第二次操作 [ 1 , y − 1 ] [1,y-1] [1,y1]区间,使得 i i i成为 i − 1 i-1 i1前面一个
  • 第三次操作 [ 1 , y + 1 ] [1,y+1] [1,y+1],使得 i − 1 i-1 i1成为序列第二个, i i i成为序列第三个
  • 第四次操作 [ 1 , 3 ] [1,3] [1,3],使得 i − 1 i-1 i1成为序列第二个, i i i成为序列第一个
  • 第五次操作 [ 1 , n ] [1,n] [1,n],使得 i i i成为序列最后一个, i − 1 i-1 i1成为序列倒数第二个
  • 满足条件
#include <cstdio>
#include <algorithm>
using namespace std;
#define maxn 2025
int T, n, cnt;
int p[maxn], id[maxn], ans[maxn << 2];

void Reverse( int len ) {
	ans[++ cnt] = len;
	for( int i = 1;i <= len;i ++ )
		id[p[i]] = len - id[p[i]] + 1;
	reverse( p + 1, p + len + 1 );
}

int main() {
	scanf( "%d", &T );
	next :
	while( T -- ) {
		cnt = 0;
		scanf( "%d", &n );
		for( int i = 1;i <= n;i ++ ) {
			scanf( "%d", &p[i] );
			id[p[i]] = i;
		}
		for( int i = 1;i <= n;i ++ )
			if( ( p[i] & 1 ) ^ ( i & 1 ) ) {
				printf( "-1\n" );
				goto next;
			}
		for( int i = n;i > 1;i -= 2 ) {
			Reverse( id[i] );
			int x = id[i - 1];
			Reverse( x - 1 );
			Reverse( x + 1 );
			Reverse( 3 );
			Reverse( i );
		}
		printf( "%d\n", cnt );
		for( int i = 1;i <= cnt;i ++ )
			printf( "%d ", ans[i] );
		printf( "\n" );
	}
	return 0;
}

F. Top-Notch Insertions

困难

observation : 根据输入的 m m m个操作,最后序列上放的下标是固定的

e.g. a 1 , a 2 , a 3 , a 4 , a 5 a_1,a_2,a_3,a_4,a_5 a1,a2,a3,a4,a5,操作(3,1) (4,1) (5,3)后,一定是 a 4 , a 3 , a 5 , a 1 , a 2 a_4,a_3,a_5,a_1,a_2 a4,a3,a5,a1,a2,不管 a i a_i ai的值真的是多少

假设最后排序后的序列为 [ b 1 , b 2 , . . . , b n ] [b_1,b_2,...,b_n] [b1,b2,...,bn]

根据题目知, ∀ i ∈ [ 1 , n ) b i ≤ b i + 1 \forall_i\in[1,n)\quad b_i\le b_{i+1} i[1,n)bibi+1

考虑 i i i,如果a[i]>=a[i-1],不发生排序,并不能告诉我们什么东西,最多能知道最后 b b b序列中 a i a_i ai排在 a i − 1 a_{i-1} ai1的后面某个位置

那么如果 a i a_i ai插到 j j j位置,能说明什么?

  • a[i]<a[j]
  • a[i]>=a[j-1]

同样的不等式不能提供有用的帮助,反而重要的是a[i]<a[j]这个条件

我们非常关心 a i a_i ai,满足 a i − 1 a_{i-1} ai1被插入,这意味着 a i − 1 < a i a_{i-1}<a_i ai1<ai(注意是严格小于)

其余的相邻两个数关系可能是小于,可能是小于等于

换言之,最后的 b b b序列,有些位置被打了标记,强制该位置前一个的值严格小于改位置的值

设被标记位置的个数为 c n t \rm cnt cnt,则最后的答案为 ( 2 n − c n t − 1 n ) \binom{2n-cnt-1}{n} (n2ncnt1)

  • 考虑 i i ∈ [ 1 , n ) i\quad i\in[1,n) ii[1,n) b i ≤ b i + 1 b_{i}\le b_{i+1} bibi+1,让 i i i后面所有的 b b b + 1 +1 +1,变成 b i < b i + 1 b_i<b_{i+1} bi<bi+1
  • 现在有 ∀ i b i < b i + 1 \forall i\quad b_i<b_{i+1} ibi<bi+1
  • 最大的 b b b取值可以为 n + n − 1 − c n t n+n-1-cnt n+n1cnt
  • 相当于在 [ 1 , m a x B ] [1,\rm maxB] [1,maxB]中选 n n n互不相同的数

至于怎么找该被标记的数

  • 相当于需要一种数据结构支持查找第 k k k
  • 线段树就可以了

注意本题只保证了 m m m的限制,没有 n n n的限制

所以需要都回滚

那么就需要记录每次问题后的操作点

#include <cstdio>
#define maxn 200005
#define int long long
#define mod 998244353
#define lson num << 1
#define rson num << 1 | 1
int n, m, T;
bool vis[maxn];
int x[maxn], y[maxn], id[maxn], pos[maxn];
int fac[maxn << 1], inv[maxn << 1], t[maxn << 2];

int qkpow( int x, int y ) {
	int ans = 1;
	while( y ) {
		if( y & 1 ) ans = ans * x % mod;
		x = x * x % mod;
		y >>= 1;
	}
	return ans;
}

void init( int n ) {
	fac[0] = inv[0] = 1;
	for( int i = 1;i <= n;i ++ ) 
		fac[i] = fac[i - 1] * i % mod;
	inv[n] = qkpow( fac[n], mod - 2 );
	for( int i = n - 1;i;i -- ) 
		inv[i] = inv[i + 1] * ( i + 1 ) % mod;
}

int C( int n, int m ) {
	return fac[n] * inv[m] % mod * inv[n - m] % mod;
}

void build( int num, int l, int r ) {
	t[num] = r - l + 1;
	if( l == r ) return;
	int mid = l + r >> 1;
	build( lson, l, mid );
	build( rson, mid + 1, r );
}

int findKth( int k, int num = 1, int l = 1, int r = 2e5 ) {
	t[num] --;//找第K大的时候 顺便完成-1的修改 
	if( l == r ) return l;
	int mid = l + r >> 1;
	if( k <= t[lson] ) return findKth( k, lson, l, mid );
	else return findKth( k - t[lson], rson, mid + 1, r );
}

void modify( int k, int num = 1, int l = 1, int r = 2e5 ) {
	t[num] ++;
	if( l == r ) return;
	int mid = l + r >> 1;
	if( k <= mid ) modify( k, lson, l, mid );
	else modify( k, rson, mid + 1, r );
}

int query( int k, int num = 1, int l = 1, int r = 2e5 ) {
	if( l == r ) return l;
	int mid = l + r >> 1;
	if( k <= t[lson] ) return query( k, lson, l, mid );
	else return query( k - t[lson], rson, mid + 1, r );
}

signed main() {
	init( 4e5 );
	build( 1, 1, 2e5 );
	scanf( "%lld", &T );
	while( T -- ) {
		scanf( "%lld %lld", &n, &m );
		for( int i = 1;i <= m;i ++ )
			scanf( "%lld %lld", &x[i], &y[i] );
		int cnt = 0;
		for( int i = m;i;i -- ) {
			id[i] = query( y[i] + 1 );
			if( ! vis[id[i]] ) cnt ++, vis[id[i]] = 1;
			pos[i] = findKth( y[i] );
		}
		printf( "%lld\n", C( 2 * n - 1 - cnt, n ) );
		for( int i = m;i;i -- )
			vis[id[i]] = 0, modify( pos[i] );
	}
	return 0;
}
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值