A. Median Maximization
题意:给出一个数组的大小,和这个数组的和,我们需要构造一个数组,使得我们最后得到的数组排序之后的上中位数尽可能大,要求这个数组的数字都要大于等于0. 贪心+构造。显然,由于我们数组的总值是固定的,所以为了使中位数尽可能大,我们需要想办法尽可能将有限的数字分的份数更少。怎么分呢?就是中位数的位置前的都分0,其他就平均分,中位数向下取整就行了。最后的答案就是
⌊
s
/
(
n
−
⌈
n
2
⌉
+
1
)
⌋
\lfloor s/(n-\lceil \frac{n}{2} \rceil +1)\rfloor
⌊ s / ( n − ⌈ 2 n ⌉ + 1 ) ⌋ 代码如下
# include <bits/stdc++.h>
using namespace std;
void solve ( ) {
int n, s;
cin>> n>> s;
int k= n/ 2 ;
if ( n% 2 ) k++ ;
int num= n- k+ 1 ;
int ans= s/ num;
cout<< ans<< endl;
}
int main ( ) {
int t;
cin>> t;
while ( t-- ) {
solve ( ) ;
}
return 0 ;
}
B. MIN-MEX Cut
题意:给你一个01串,需要将这个01串分为若干个连续的部分,对于每个部分,它的权值就是
M
E
X
{
0
,
1
,
2
}
MEX\{0,1,2\}
M E X { 0 , 1 , 2 } .问总的权值最小是多少。 思路:贪心。我们观察发现,剪切下来的部分同时出现了0和1一定不是最优的。因为这样的话权值就是2,但是我们可以把这个分为连续的几段0和1,对于连续的0,权值为0,对于连续的1,权值为0.然后我们可以根据这个来计算总的权值。但是我们发现,最后的权值一定不会大于2。因为我们把整个字符串当作一部分,总的权值就是2,所以最后答案和2取一个最小即可。 代码如下
# include <bits/stdc++.h>
using namespace std;
void solve ( ) {
string s;
cin>> s;
int ans= 0 ;
int n= s. size ( ) ;
for ( int i= 1 ; i< n; i++ ) {
if ( s[ i] != s[ i- 1 ] ) {
if ( s[ i- 1 ] == '0' ) ans++ ;
}
}
if ( s[ n- 1 ] == '0' ) ans++ ;
cout<< min ( ans, 2 ) << endl;
}
int main ( ) {
int t;
cin>> t;
while ( t-- ) {
solve ( ) ;
}
return 0 ;
}
C. MAX-MEX Cut
题意:这个是在上一个题目的扩展,这个是一个两层的。而且这个题目要使最后的结果最大。问我们怎么分可以使最后的结果最大。 思路: 还是贪心的思想,我们发现,为了使最后的结果最大,对于两层的数字一个为0,一个为1的情况,我们单独将他们分为一组是最优的。这样不会有浪费。所以,我们先排除01或者10的情况,用一个数组标记这一列的数字被使用了。然后就是00和11的情况了,对于11的情况,我们要尽可能将他们和左边或者右边的00组成一组,这样得到的权值就是2.如果不存在合法的组的情况,那就让11组成一组,虽然这样对答案没有任何贡献。如果11左边或者右边存在00的情况,那么将他们组成一组,同时打上被使用了的标记。维护最后的答案即可。 代码
# include <bits/stdc++.h>
using namespace std;
string s1, s2;
bool vis[ 100010 ] ;
void solve ( ) {
memset ( vis, false , sizeof ( vis) ) ;
int n;
cin>> n;
cin>> s1>> s2;
int ans= 0 ;
for ( int i= 0 ; i< n; i++ ) {
if ( s1[ i] != s2[ i] ) {
ans+= 2 ;
vis[ i] = true ;
}
}
for ( int i= 0 ; i< n; i++ ) {
if ( s1[ i] == '1' && s2[ i] == '1' ) {
if ( i- 1 >= 0 && vis[ i- 1 ] == false && ( s1[ i- 1 ] == '0' && s2[ i- 1 ] == '0' ) ) {
ans+= 2 ;
vis[ i] = true ;
vis[ i- 1 ] = true ;
} else {
if ( i+ 1 < n&& vis[ i+ 1 ] == false && s1[ i+ 1 ] == '0' && s2[ i+ 1 ] == '0' ) {
ans+= 2 ;
vis[ i] = true ;
vis[ i+ 1 ] = true ;
}
}
}
}
for ( int i= 0 ; i< n; i++ ) {
if ( vis[ i] == false && s1[ i] == '0' && s2[ i] == '0' ) {
ans++ ;
}
}
cout<< ans<< endl;
}
int main ( ) {
int t;
cin>> t;
while ( t-- ) {
solve ( ) ;
}
return 0 ;
}
D2. Seating Arrangements (hard version)
题意:有一个nm的矩阵。矩阵按照从左到右,从上到下的位置进行编号。初始的时候位置都是空的。现在给我们一个序列,需要我们将这个序列里面的数字按照初始的顺序依次放到这个矩阵里面,但是放的时候有一个规则,就是数值小的存放到矩阵编号小的里面。当要存放一个数字的时候,在同一行从左到右经过的目前已经安放的数字个数就是安放这个数字所需要的花费,问最后花费最小是多少。 思路:首先,我们需要先对这个序列进行排序。排好序后,我们一排一排进行考虑。我们发现,在这一排安放数字的大小都是固定了的。只是具体那几个位置的数字我们不知道。为了使最后的答案最小。对于同一行的相同的数字,我们需要让下标小的在右边,下标大的在左边。对于数值不一样的数字,我们需要让下标小的先使用,下标大的后使用。就是一个sort排序就行了。我们记录每个位置的数字的大小和这个数字的下标,然后首先按照数值和下标的大小从小到大排序,然后在每一行内部的数字,由于数值的大小是固定了的。所以我们只需要对相同的数值的内部按照下标的从大到小进行排即可。下标大的就说明是更晚才会安放,自然放到靠左边即可。这里可以自己好好想想。 代码
# include <bits/stdc++.h>
using namespace std;
string s1, s2;
vector< pair< int , int > > v;
void solve ( ) {
v. clear ( ) ;
int n, m;
cin>> n>> m;
for ( int i= 0 ; i< n* m; i++ ) {
int x;
cin>> x;
v. push_back ( make_pair ( x, i) ) ;
}
sort ( v. begin ( ) , v. end ( ) ) ;
for ( int i= 0 ; i< n* m; i++ ) {
v[ i] . second= - v[ i] . second;
}
int ans= 0 ;
for ( int i= 0 ; i< n; i++ ) {
sort ( v. begin ( ) + ( m* i) , v. begin ( ) + ( i+ 1 ) * m) ;
for ( int j= m* i; j< ( i+ 1 ) * m; j++ ) {
for ( int k= m* i; k< j; k++ ) {
if ( v[ j] . second< v[ k] . second) ans++ ;
}
}
}
cout<< ans<< endl;
}
int main ( ) {
int t;
cin>> t;
while ( t-- ) {
solve ( ) ;
}
return 0 ;
}
E. Buds Re-hanging
题意:给出一棵树,定义树的芽是树的叶子的父节点(非根节点)。可以执行无数次将芽与它的父节点之间的边消除连到其他的结点的操作。问这样的操作下树的叶子结点的个数最少右多少个。 思路:首先,我们发现,按照题目的执行方法。最后这棵树的形式一定是只有根,叶子,芽这三种结点。然后,对于每一个芽,我们发现,如果把这个芽连到根结点,那么树的叶子结点的个数不变,如果连到根结点得以叶子结点,那么结点的个数就会减1。这里套用一下官方的题解。由于最后的树的形态如上面所描述,所以最后我们假设芽的个数为k,那么叶子结点的个数就是n-k-1(总的结点-芽的结点-根结点),如果此时根有叶子结点,那么我们将一个芽与这个根节点的叶子结点相连。这样总的叶子结点就少了一个,但是这个芽还是芽,然后其他的芽执行以下操作,连接到其他的芽孢的叶子结点,这样每连一次,总的叶子结点的个数就少了1,连了k-1次(注意,最后一定会剩下一个芽)总的就会少k-1个,然后再加上一个叶子结点的,所以总的就是
n
−
(
k
−
1
)
+
1
−
k
−
1
=
n
−
2
∗
k
−
1
n-(k-1)+1-k-1=n-2*k-1
n − ( k − 1 ) + 1 − k − 1 = n − 2 ∗ k − 1 ,如果根没有叶子结点,那么就将当前的芽连接到其他的芽的叶子结点上面。执行了k-1次操作之后总的叶子结点的个数就少了k-1,所以答案就是
n
−
k
−
1
−
(
k
−
1
)
=
n
−
k
∗
2
n-k-1-(k-1)=n-k*2
n − k − 1 − ( k − 1 ) = n − k ∗ 2 。所以我们利用dfs这棵树求到每个结点的类型,同时判断根是否由叶子结点,最后就可以直接得到答案了。 ps: 这里我一直没有想通的是如果根有多个叶子结点的时候为什么会答案和一个叶子节点是一样的。后面仔细想想,如果根有多个叶子结点,那么我们每次执行一次操作的时候肯定都是减少1的操作,所以其实和直接连在其他的芽孢上的效果是一样的,这里值得好好体会。 代码
# include <bits/stdc++.h>
using namespace std;
const int N= 200010 ;
vector< int > v[ N] ;
int type[ N] ;
int in[ N] ;
void dfs ( int x, int fa) {
bool flag= false ;
for ( auto y: v[ x] ) {
if ( y== fa) continue ;
dfs ( y, x) ;
if ( type[ y] == 1 ) flag= true ;
}
if ( ! flag) type[ x] = 1 ;
else type[ x] = 2 ;
}
void solve ( ) {
int n;
scanf ( "%d" , & n) ;
for ( int i= 1 ; i<= n; i++ ) {
type[ i] = 0 ;
v[ i] . clear ( ) ;
}
for ( int i= 1 ; i< n; i++ ) {
int x, y;
scanf ( "%d %d" , & x, & y) ;
v[ x] . push_back ( y) ;
v[ y] . push_back ( x) ;
}
dfs ( 1 , 0 ) ;
bool flag= false ;
for ( auto x: v[ 1 ] ) {
if ( type[ x] == 1 ) {
flag= true ;
}
}
int ans= 0 ;
int k= 0 ;
for ( int i= 2 ; i<= n; i++ ) {
if ( type[ i] == 2 ) k++ ;
}
ans= n- k- k- flag;
printf ( "%d\n" , ans) ;
}
int main ( ) {
int t;
scanf ( "%d" , & t) ;
while ( t-- ) {
solve ( ) ;
}
return 0 ;
}