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−(i−1)}
在第 i i i个怪物前面可以有 i − 1 i-1 i−1个怪物杀死获得提升的机会,然后要求严格大于怪物生命值,所以 + 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 n→1转移
-
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 2∗i开始)都可以通过 / 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=i∗x,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,i−1放到相应位置且以后不再操作这两个数,操作范围变成 i − 2 i-2 i−2
observation
: 每次操作
[
1
,
x
]
[1,x]
[1,x],
i
↔
x
−
i
+
1
i\leftrightarrow x-i+1
i↔x−i+1,
x
+
1
x+1
x+1为偶数,所以
i
,
x
−
i
+
1
i,x-i+1
i,x−i+1同奇偶
最后要满足a[i]=i
,下标必须和值是同奇偶才会有解
判完无解后, 5 5 5次操作这是可以做到的
- 从 n → 1 n\rightarrow 1 n→1构造,每次满足 n , n − 1 n,n-1 n,n−1( 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 i−1的位置 y y y(显然 y y y为偶数),第二次操作 [ 1 , y − 1 ] [1,y-1] [1,y−1]区间,使得 i i i成为 i − 1 i-1 i−1前面一个
- 第三次操作 [ 1 , y + 1 ] [1,y+1] [1,y+1],使得 i − 1 i-1 i−1成为序列第二个, i i i成为序列第三个
- 第四次操作 [ 1 , 3 ] [1,3] [1,3],使得 i − 1 i-1 i−1成为序列第二个, i i i成为序列第一个
- 第五次操作 [ 1 , n ] [1,n] [1,n],使得 i i i成为序列最后一个, i − 1 i-1 i−1成为序列倒数第二个
- 满足条件
#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)bi≤bi+1
考虑
i
i
i,如果a[i]>=a[i-1]
,不发生排序,并不能告诉我们什么东西,最多能知道最后
b
b
b序列中
a
i
a_i
ai排在
a
i
−
1
a_{i-1}
ai−1的后面某个位置
那么如果 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} ai−1被插入,这意味着 a i − 1 < a i a_{i-1}<a_i ai−1<ai(注意是严格小于)
其余的相邻两个数关系可能是小于,可能是小于等于
换言之,最后的 b b b序列,有些位置被打了标记,强制该位置前一个的值严格小于改位置的值
设被标记位置的个数为 c n t \rm cnt cnt,则最后的答案为 ( 2 n − c n t − 1 n ) \binom{2n-cnt-1}{n} (n2n−cnt−1)
- 考虑 i i ∈ [ 1 , n ) i\quad i\in[1,n) ii∈[1,n), b i ≤ b i + 1 b_{i}\le b_{i+1} bi≤bi+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+n−1−cnt
- 相当于在 [ 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;
}