Codeforces Round #740 (Div. 2, based on VK Cup 2021 - Final (Engine))
文章目录
- A. Simply Strange Sort
- B. Charmed by the Game
- C. Deep Down Below
- D1/D2. Up the Strip
- E. Bottom-Tier Reversals
- F. Top-Notch Insertions
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+bn=a+bn=a+b的奇偶讨论,假设以Alice
Bob
第一场发球
算出每个人发球的场数,计算出某个胜场不够的人至少要break
多少场
之后只能两人相互break
相同场数才能保持彼此赢的场数满足a/b
用vis[i]
记录break
恰好iii场是否可以,每次都是+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;elsefor( 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;elsefor( 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;elsefor( 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{armori+1−(i−1)}\max\Big\{armor_i+1-(i-1)\Big\}max{armori+1−(i−1)}
在第iii个怪物前面可以有i−1i-1i−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
简单DPDPDP题
设计fi:f_i:fi: 移动到格子iii的方案数,由n→1n\rightarrow 1n→1转移
-
iii后面的每个格子都可以通过−x-x−x的操作转移过来
fi+=∑j=i+1nfjf_i+=\sum_{j=i+1}^nf_jfi+=∑j=i+1nfj
-
iii后面的格子(以2∗i2*i2∗i开始)都可以通过/x/x/x的操作转移过来
但是因为是下取整,所以有的jjj,可能除以多个xxx都能转移到iii,不能简单只加一个fjf_jfj完事
不妨反其道而行之,枚举xxx,计算出区间[l,r][l,r][l,r]满足j∈[l,r],⌊jx⌋=ij\in[l,r],\lfloor\frac{j}{x}\rfloor=ij∈[l,r],⌊xj⌋=i
很简单的数学计算一下,l=i∗x,r=x∗(i+1)−1l=i*x,r=x*(i+1)-1l=i∗x,r=x∗(i+1)−1
fi+=∑j=lrfjf_i+=\sum_{j=l}^rf_jfi+=∑j=lrfj
时间复杂度是调和级数,logn\rm lognlogn
后缀优化即可,sumi=∑j=infjsum_i=\sum_{j=i}^nf_jsumi=∑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
勉强中档题
限制次数为5n2\frac{5n}{2}25n,且只能操作奇数
暗示构造,且相邻奇偶绑定在一起才行
也就是说能否寻找一种构造
在555次操作内,将i,i−1i,i-1i,i−1放到相应位置且以后不再操作这两个数,操作范围变成i−2i-2i−2
observation
: 每次操作[1,x][1,x][1,x],i↔x−i+1i\leftrightarrow x-i+1i↔x−i+1,x+1x+1x+1为偶数,所以i,x−i+1i,x-i+1i,x−i+1同奇偶
最后要满足a[i]=i
,下标必须和值是同奇偶才会有解
判完无解后,555次操作这是可以做到的
- 从n→1n\rightarrow 1n→1构造,每次满足n,n−1n,n-1n,n−1(iii为奇数)放到相应位置,每次操作后范围n−=2n-=2n−=2
- 找到iii的位置xxx(显然xxx为奇数),第一次操作[1,x][1,x][1,x]区间,使得iii成为序列第一个
- 找到i−1i-1i−1的位置yyy(显然yyy为偶数),第二次操作[1,y−1][1,y-1][1,y−1]区间,使得iii成为i−1i-1i−1前面一个
- 第三次操作[1,y+1][1,y+1][1,y+1],使得i−1i-1i−1成为序列第二个,iii成为序列第三个
- 第四次操作[1,3][1,3][1,3],使得i−1i-1i−1成为序列第二个,iii成为序列第一个
- 第五次操作[1,n][1,n][1,n],使得iii成为序列最后一个,i−1i-1i−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
: 根据输入的mmm个操作,最后序列上放的下标是固定的
e.g.
a1,a2,a3,a4,a5a_1,a_2,a_3,a_4,a_5a1,a2,a3,a4,a5,操作(3,1)
(4,1)
(5,3)
后,一定是a4,a3,a5,a1,a2a_4,a_3,a_5,a_1,a_2a4,a3,a5,a1,a2,不管aia_iai的值真的是多少
假设最后排序后的序列为[b1,b2,...,bn][b_1,b_2,...,b_n][b1,b2,...,bn]
根据题目知,∀i∈[1,n)bi≤bi+1\forall_i\in[1,n)\quad b_i\le b_{i+1}∀i∈[1,n)bi≤bi+1
考虑iii,如果a[i]>=a[i-1]
,不发生排序,并不能告诉我们什么东西,最多能知道最后bbb序列中aia_iai排在ai−1a_{i-1}ai−1的后面某个位置
那么如果aia_iai插到jjj位置,能说明什么?
a[i]<a[j]
a[i]>=a[j-1]
同样的不等式不能提供有用的帮助,反而重要的是a[i]<a[j]
这个条件
我们非常关心aia_iai,满足ai−1a_{i-1}ai−1被插入,这意味着ai−1<aia_{i-1}<a_iai−1<ai(注意是严格小于)
其余的相邻两个数关系可能是小于,可能是小于等于
换言之,最后的bbb序列,有些位置被打了标记,强制该位置前一个的值严格小于改位置的值
设被标记位置的个数为cnt\rm cntcnt,则最后的答案为(2n−cnt−1n)\binom{2n-cnt-1}{n}(n2n−cnt−1)
- 考虑ii∈[1,n)i\quad i\in[1,n)ii∈[1,n),bi≤bi+1b_{i}\le b_{i+1}bi≤bi+1,让iii后面所有的bbb都+1+1+1,变成bi<bi+1b_i<b_{i+1}bi<bi+1
- 现在有∀ibi<bi+1\forall i\quad b_i<b_{i+1}∀ibi<bi+1
- 最大的bbb取值可以为n+n−1−cntn+n-1-cntn+n−1−cnt
- 相当于在[1,maxB][1,\rm maxB][1,maxB]中选nnn个互不相同的数
至于怎么找该被标记的数
- 相当于需要一种数据结构支持查找第kkk大
- 线段树就可以了
注意本题只保证了mmm的限制,没有nnn的限制
所以需要都回滚
那么就需要记录每次问题后的操作点
#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;
}