Codeforces Global Round 5 Editorial
神仙场(不过打了应该可以上分)
(CF1237A~Balanced~Rating~Changes)
题意:给定序列(b_i),你需要构造一个序列(a_i)使得:
(a_i=leftlfloordfrac{b_i}{2} ight floor)或者(a_i=leftlceildfrac{b_i}{2} ight ceil)
且(sum a_i =0)
其中(b_i)有正有负,数据保证有解
( m Sol:)
注意到(a_i)可以拆开,其可以变成(a_i=dfrac{b_ipm b_i\% 2}{2})
于是(sum a_i=sum dfrac{b_ipm b_i\% 2}{2}=dfrac{sum b_i +sum (pm )b_i\%2}{2})
于是可以统计(b_i)之和,初始让所有(b_i \%2)前面的系数均为正数,于是只需要让一些数的权值变成负数即可,统计一下全体之和,每次修改其值变化为(-2)
(Code:)
#include<bits/stdc++.h>
using namespace std ;
#define rep( i, s, t ) for( register int i = s; i <= t; ++ i )
#define re register
int gi() {
char cc = getchar() ; int cn = 0, flus = 1 ;
while( cc < '0' || cc > '9' ) { if( cc == '-' ) flus = - flus ; cc = getchar() ; }
while( cc >= '0' && cc <= '9' ) cn = cn * 10 + cc - '0', cc = getchar() ;
return cn * flus ;
}
const int N = 50000 + 5 ;
int a[N], b[N], n ;
signed main()
{
n = gi() ; int Sum = 0, Sum2 = 0 ;
rep( i, 1, n ) a[i] = gi(), Sum += a[i], b[i] = abs( a[i] % 2 ), Sum2 += b[i] ;
int rSum = Sum + Sum2 ;
rep( i, 1, n ) {
if( rSum == 0 ) break ;
if( rSum > 0 && b[i] == 1 ) rSum -= 2, b[i] = -b[i] ;
}
rep( i, 1, n ) printf("%d
", ( a[i] + b[i] ) / 2 ) ;
return 0 ;
}
(CF1237B ~Balanced ~Tunnel)
题意:
给定一个大小为(n)的排列(a_i)以及一个大小为(n)的排列(b_i)
对于一对((i,j)),如果在序列(a)中(j)在(i)之前,但是在序列(b)中(i)却在(j)之前,(i)就要被标记。
但是对于每一个数(i),他最多会被标记一次。
现在请你求出一共有多少个数需要被标记。
( m Sol:)
套路题,考虑按照第一个序列中的顺序映射到第二个序列上去,于是原问题转化为求解第二个序列中的逆序对((i,j))个数且每个数(i)只能贡献一次
考虑采用传统的逆序对做法,在值域上建立权值线段树,由于每个数只能贡献一次,所以在计算贡献的时候考虑到了其则将其权值赋为(0)即可,复杂度(O(nlog n))
然而实际上这个做法麻烦了,注意到一个数会贡献当且仅当其后面有一个小于其的数,所以直接统计一下每个数到(n)间内的最小值即可,复杂度(O(n))
(Code:)
#include<bits/stdc++.h>
using namespace std ;
#define rep( i, s, t ) for( register int i = s; i <= t; ++ i )
#define re register
#define ls(x) x * 2
#define rs(x) x * 2 + 1
int gi() {
char cc = getchar() ; int cn = 0, flus = 1 ;
while( cc < '0' || cc > '9' ) { if( cc == '-' ) flus = - flus ; cc = getchar() ; }
while( cc >= '0' && cc <= '9' ) cn = cn * 10 + cc - '0', cc = getchar() ;
return cn * flus ;
}
const int N = 1e5 + 5 ;
int n, a[N], b[N], col[N] ;
struct Tr {
int w, tag ;
} tr[N * 3] ;
void pushmark( int x ) {
if( tr[x].tag ) {
tr[ls(x)].w = 0, tr[rs(x)].w = 0,
tr[ls(x)].tag = 1, tr[rs(x)].tag = 1,
tr[x].tag = 0 ;
}
}
void update( int x, int l, int r, int wh ) {
if( l == r ) { ++ tr[x].w; return ; }
int mid = ( l + r ) >> 1 ; pushmark( x ) ;
if( mid >= wh ) update( ls(x), l, mid, wh ) ;
else update( rs(x), mid + 1, r, wh ) ;
tr[x].w = tr[ls(x)].w + tr[rs(x)].w ;
}
void modify( int x, int l, int r, int ql, int qr ) {
if( r < ql || l > qr ) return ;
if( ql <= l && r <= qr ) { tr[x].w = 0, tr[x].tag = 1; return ; }
int mid = ( l + r ) >> 1 ; pushmark( x ) ;
modify( ls(x), l, mid, ql, qr ), modify( rs(x), mid + 1, r, ql, qr ) ;
tr[x].w = tr[ls(x)].w + tr[rs(x)].w ;
}
int Query( int x, int l, int r, int ql, int qr ) {
if( r < ql || l > qr ) return 0 ;
if( ql <= l && r <= qr ) return tr[x].w ;
int mid = ( l + r ) >> 1 ; pushmark(x) ;
return Query( ls(x), l, mid, ql, qr ) + Query( rs(x), mid + 1, r, ql, qr ) ;
}
signed main()
{
n = gi() ;
rep( i, 1, n ) a[i] = gi(), col[a[i]] = i ;
rep( i, 1, n ) b[i] = gi(), b[i] = col[b[i]] ;
int Ans = 0 ;
rep( i, 1, n ) {
Ans += Query( 1, 1, n, b[i], n ) ;
modify( 1, 1, n, b[i], n ) ;
update( 1, 1, n, b[i] ) ;
}
printf("%d
", Ans ) ;
return 0 ;
}
(CF1237C~ Balanced ~Removals)
这里就不放(C(easy))了...
题意:
给定(n)个三维坐标点((x_i,y_i,z_i))
每次你可以删除两个点((i,j))当且仅当不存在一个(k)同时满足:(min(x_i,x_j)le x_kle max(x_i, x_j),min(y_i,y_j)le y_kle max(y_i, y_j),min(z_i,z_j)le z_kle max(z_i, z_j))
保证点的坐标互不相同,你需要给出一个删除所有点的方案
注意删除点之后的点不会成为合法的(k)
(nle 50000)
( m Sol:)
(1):) 考虑只有一个维度的时候怎么处理,由于坐标互不相同则可以直接按照(x)排序
(2):) 接下来考虑加入一个维度,这个时候对于(x)互不相同的点仍然可以直接排序,但对于(x)相同的点则不好处理
考虑对每个(x)相同的点单独处理,则其(y)互不相同,可以使用((1).)的做法实现,然后注意到假设(x)相同的数为偶数,则如此处理之后可以将其全部删除,否则其为奇数,将剩下一个数,那么这样处理完之后对于每个(x),(x)相同的点也只有一个了,亦变成了问题((1).)
(3):) 考虑加入维度(z),我们可以类比上面的做法,(z)相同的单独使用((2).)去处理,最后仍然会转化为问题((1).)
复杂度(O(nlog n))
(Code:)
#include<bits/stdc++.h>
using namespace std ;
#define rep( i, s, t ) for( register int i = s; i <= t; ++ i )
#define re register
#define il inline
int gi() {
char cc = getchar() ; int cn = 0, flus = 1 ;
while( cc < '0' || cc > '9' ) { if( cc == '-' ) flus = - flus ; cc = getchar() ; }
while( cc >= '0' && cc <= '9' ) cn = cn * 10 + cc - '0', cc = getchar() ;
return cn * flus ;
}
const int inf = 123456789 ;
const int N = 50000 + 5 ;
struct Point {
int x, y, z, id ;
} a[N], b[N], c[N], de[N], de2[N], zero ;
struct Del {
int x, y ;
} D[N] ;
int n, num ;
bool cmp1( Point x, Point y ) {
return x.z < y.z ;
}
bool cmp2( Point x, Point y ) {
return x.y < y.y ;
}
bool cmp3( Point x, Point y ) {
return x.x < y.x ;
}
inline Point solve2( int L, int R ) {
rep( i, L, R ) c[i] = b[i] ;
sort( c + L, c + R + 1, cmp3 ) ; int i ;
for( i = L; i < R; i += 2 ) D[++ num] = (Del){ c[i].id, c[i + 1].id } ;
if( i == R ) return c[i] ;
else return zero ;
}
inline Point solve1( int L, int R ) {
if( L == R ) return a[L] ;
rep( i, L, R ) b[i] = a[i] ;
sort( b + L, b + R + 1, cmp2 ) ;
int cnt = 0, l = L, r = L, i ;
for( l = L; l <= R; l = r + 1 ) {
while( b[r + 1].y == b[l].y && r + 1 <= R ) ++ r ;
de2[++ cnt] = solve2( l, r ) ;
}
sort( de2 + 1, de2 + cnt + 1, cmp2 ) ;
for( i = 1; i <= cnt + 1; ++ i ) if( de2[i].id != 0 ) break ;
for( ; i < cnt; i += 2 ) D[++ num] = (Del){ de2[i].id, de2[i + 1].id } ;
if( i == cnt ) return de2[cnt] ;
else return zero ;
}
signed main()
{
n = gi() ; rep( i, 1, n )
a[i].x = gi(), a[i].y = gi(), a[i].z = gi(), a[i].id = i ;
sort( a + 1, a + n + 1, cmp1 ) ; zero.x = -inf, zero.y = -inf, zero.z = -inf ;
int l = 1, r = 1, cnt = 0 ;
for( l = 1; l <= n; l = r + 1 ) {
while( a[r + 1].z == a[l].z && r + 1 <= n ) ++ r ;
de[++ cnt] = solve1( l, r ) ;
}
sort( de + 1, de + cnt + 1, cmp1 ) ;
for( l = 1; l <= cnt + 1; ++ l ) if( de[l].id != 0 ) break ;
for( ; l < cnt; l += 2 ) D[++ num] = (Del){ de[l].id, de[l + 1].id } ;
rep( i, 1, num ) printf("%d %d
", D[i].x, D[i].y ) ;
return 0 ;
}
(CF1237D ~Balanced~ Playlist)
题意:
你正在听音乐,列表是循环播放的
即给定一个大小为(n(nle 10^5))环(a_i(1le a_i le 10^9)),其中(a_i)表示一首曲子的美妙度,你会循环播放曲子,当听到了(a_n)之后又转为听(a_1...)
每听到一首歌你都会记下来你如此听到的所有曲子中美妙度最大的曲子,设其为(x),则当你将要听到一首美妙度(a_i<dfrac{x}{2}()不取整())的歌的时候你会为了避免你美好的心情而终止听歌。
现在你希望知道对于每个(k),如果你最初播放的曲子为(k)的时候你能听到多少首曲子,如果歌曲会一直循环则输出(-1)
( m Sol:)
首先特判无解(dfrac{max(a_i)}{2}le min(a_i))
接下来显然要断环为链,考虑预处理出每个点(u)往后第一个遇到的满足(a_x<dfrac{a_u}{2})的点(x),设其为(r_x),(r_x)的求解可以利用单调栈(+)二分来做
于是假设从某个位置(i)开始可以去往(k)当且仅当对于任意的(ile jle k-1)有(r_j>k)
于是会惊人的发现我们只需要求出(i-end)这一段区间内最小的(r_j)便是其可以到达的最远点了,这个过程可以利用线段树实现
但是这样仍然是错的,因为对于(dfrac{n}{2}< ile n),这个表示不充分,这些(ile j<2 imes n)的(r_j)不能表示到了某个点(k<i)然后(k)继续往前走却因为碰到了较远的数而终止的情况
简单讲就是答案可能是绕原序列(1.5)圈而得到的,所以倍长一倍是不够的
所以对于原序列需要倍长(3)倍
(Code:)
#include<bits/stdc++.h>
using namespace std ;
#define rep( i, s, t ) for( register int i = s; i <= t; ++ i )
#define re register
#define int long long
#define ls(x) x * 2
#define rs(x) x * 2 + 1
#define inf 12345678900
int gi() {
char cc = getchar() ; int cn = 0, flus = 1 ;
while( cc < '0' || cc > '9' ) { if( cc == '-' ) flus = - flus ; cc = getchar() ; }
while( cc >= '0' && cc <= '9' ) cn = cn * 10 + cc - '0', cc = getchar() ;
return cn * flus ;
}
const int N = 3e5 + 5 ;
int n, a[N], col[N], s[N], top, Ans[N] ;
struct Tr {
int w ;
} tr[N * 3] ;
int check( int x ) {
int l = 1, r = top, ans = 1, mid ;
while( l <= r ) {
mid = ( l + r ) >> 1 ;
if( a[s[mid]] <= x ) ans = mid, l = mid + 1 ;
else r = mid - 1 ;
}
return s[ans] ;
}
void build( int x, int l, int r ) {
if( l == r ) { tr[x].w = col[l] ; return ; }
int mid = ( l + r ) >> 1 ;
build( ls(x), l, mid ), build( rs(x), mid + 1, r ) ;
tr[x].w = min( tr[ls(x)].w, tr[rs(x)].w ) ;
}
int Query( int x, int l, int r, int ql, int qr ) {
if( ql > r || l > qr ) return inf ;
if( ql <= l && r <= qr ) return tr[x].w ;
int mid = ( l + r ) >> 1 ;
return min( Query( ls(x), l, mid, ql, qr ), Query( rs(x), mid + 1, r, ql, qr ) ) ;
}
signed main()
{
n = gi() ; int Mx = -inf, Mi = inf ;
rep( i, 1, n ) a[i] = gi(), a[i + n] = a[i], a[i + 2 * n] = a[i], Mi = min( Mi, a[i] ), Mx = max( Mx, a[i] ) ;
if( ( Mx + 1 ) / 2 - 1 < Mi ) {
rep( i, 1, n ) printf("-1 ") ;
exit(0) ;
}
n = n * 3, s[++ top] = n + 1 ;
for( re int i = n; i >= 1; -- i ) {
col[i] = check( ( a[i] + 1 ) / 2 - 1 ) ;
while( a[s[top]] >= a[i] ) -- top ;
s[++ top] = i ;
}
build( 1, 1, n ) ; n /= 3 ;
for( re int i = n; i >= 1; -- i ) {
Ans[i] = Query( 1, 1, 3 * n, i, 3 * n ) - i ;
}
rep( i, 1, n ) printf("%I64d ", Ans[i] ) ;
return 0 ;
}
神仙题
(CF1237E ~Balanced~ Binary ~Search ~Trees)
题意:
需要求出有多少棵点数为(n(nle 10^6))点权为({1,2,...,n})的二叉搜索树满足:
(1):) 除了最下面一层外,所有层都是满的;
(2):) 对于每个点,如果它有左儿子,那么左儿子的点权和它的奇偶性不同;如果它有右儿子,那么右儿子的点权和它的奇偶性相同。
答案对(998244353)取模
( m Sol:)
可以发现定义(1)即这棵树是一个满二叉树,于是对于根节点,有除去其之后仍然有其为一颗满二叉树
设满足定义(2)的满二叉树为完美树,则可以发现由于原树为一棵二叉查找树,于是其最右边的点一定为(n),所以有根的奇偶性与(n)的奇偶性相同
不难看出两个性质:
(1):) 一棵完美树的子树仍然是完美树
(2):) 由于权值为(1-n),所以这棵二叉搜索树的中序遍历为(1-n),每棵子树则可以对应成为一个区间
考虑假设某一个数(x)作为根,此时有(x)的奇偶性与(n)相同,则其左子树的区间可以表示为([1,x-1]),右子树区间则可以表示为([x+1,n]),且有左子树大小为(x-1),右子树大小为(n-x),因为(n)的奇偶性和(x)相同,所以(n-x)必然为偶数,且(x-1)的奇偶性与(x)相反,由于左子树仍然是一棵完美树,所以再使用上述结论可以得到:
一颗完美树满足其左子树根的奇偶性与子树大小相同,而右子树大小为偶数
接下来由于二叉搜索树只关心相对的大小关系且其某一个子树可以被表示成为一个区间([l,r]),所以我们使用([1,r-l+1])对应替换此树所有节点对于答案没有影响,容易发现假设其原先为一棵完美树则替换后仍然是一颗完美树
所以问题与原树对应的区间编号无关而之和此树大小有关
接下来考虑将两颗完美树合并成为一颗大完美树以及其合法性,按照前面的条件我们可以得到合并之后的树为完美树当且仅当:(1.)合并之后满足原树为满二叉树,(2.)右子树大小为偶数
我们可以手玩得到:大小为(1,2,3,4,5)的完美树形态如下:
(size=1:quad 1)
(size=2:quad 2.left o 1)
(size=3:) 不存在合法
(size=4:)为样例
(size=5:)仅存在一个,为:
(3.left o 2,2.left o1,3.right o 5,5.left o 4)
可以观察到除去(size=1)之外的所有完美树高度均(>1)且不为满二叉树
由于 性质(1) 我们知道对于一个大小(>5)的完美树,有其最底层仍然是一棵完美树,换而言之其除去根之后必然是不满的,所以我们可以得出一个可怕的结论:
将两颗完美树合并成一棵大小(>5)的完美树当且仅当(1.):) 其左右子树为完美树且高度相同,(2):)右子树大小为偶数
现在我们可以设计一个非常粗略的(dp)了,令(dp_{i,j})表示大小为(i)的完美树高度为(j)的时候的方案数,然后利用这个东西转移,这样是(O(nlog n))的做法
仔细思考会发现一个更可怕的东西
我们知道前(5)项的高度和方案数(前为高度,后为方案数)大致如此:
((1,1),(2,1),(-,-),(3,1),(3,1))
注意到右子树的大小仅能是(2)和(4)
对于右子树(2)而言唯一的合并是左(2)右(2),合并得到(5)
对于右子树(4)而言唯一的合并是左(4/5)右(4),合并得到大小为(9/10)的完美树,高度为(4)
那么这样对于大小为(6-8)的树其均不具有完美树,于是接下来可以用的大小仅有(9/10)
类似合并可以发现(9/10)仅能通过合并得到(20/21),然后可行的为(41/42)...可以发现合法的树均只有(1)种
于是只需要拿(4/5)作为初值递推下去即可,复杂度(O(log n))
时间(3s)和(n)只有(10^6)应该是为了放其他大常数非正解做法过...我觉得要是把(n)开到(10^{18})我真的不觉得可以有什么人过...
(Code:)
#include<bits/stdc++.h>
using namespace std ;
#define rep( i, s, t ) for( register int i = s; i <= t; ++ i )
#define re register
int gi() {
char cc = getchar() ; int cn = 0, flus = 1 ;
while( cc < '0' || cc > '9' ) { if( cc == '-' ) flus = - flus ; cc = getchar() ; }
while( cc >= '0' && cc <= '9' ) cn = cn * 10 + cc - '0', cc = getchar() ;
return cn * flus ;
}
int n ;
signed main()
{
n = gi() ;
if( n == 1 || n == 2 ) puts("1") ;
else {
int x = 4, y = 5 ;
while( max( x, y ) < n ) {
int ux = x, uy = y ;
if( ux & 1 ) x = ux + uy + 1, y = uy + uy + 1 ;
else x = ux + uy + 1, y = ux + ux + 1 ;
if( x > y ) swap( x, y ) ;
}
if( x == n || y == n ) puts("1") ;
else puts("0") ;
}
return 0 ;
}