题目链接

A. Median of an Array

题意

给出一个数组$a$,每次操作可以让其中一个数增加一。 问最少需要几次操作能改变中位数的值。

长度为n的数组a的中位数表示为,令b是a排完序后的数组,那么$b_{\lceil \frac{n}{2}\rceil}$ 即为a的中位数。

思路

假设中位数为$m$ ,那么我们就需要让最终的中位数变为$m+1$ 。做法就是对一些值为$m$ 各进行一次操作。我们假设小于等于m的数的数量为$cnt$ , 起初一定有$cnt \ge \lceil \frac{n}{2}\rceil$为了让$m+1$是中位数,就必须$cnt< \lceil \frac{n}{2}\rceil$ 。 每次操作能使得cnt减少1 。于是我们需要的操作数量就是$cnt - \lceil \frac{n}{2}\rceil + 1$

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#include<bits/stdc++.h>
using namespace std;
#define IO ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
#define int long long
#define rep(i,l,r) for(int i = l;i<=r;i++)
#define per(i,r,l) for(int i = r;i>=l;i--)
const int INF = 0x3f3f3f3f3f3f3f3f;
typedef pair<int,int> PII;
void solve(){
map<int,int> mp; //每个数都出现了几次
int n;
cin>>n;
rep(i,1,n){
int num;cin>>num;
mp[num] ++;
}
int cnt = 0;
for(auto p:mp){
if(cnt+p.second < (n+1)/2){
cnt += p.second;
}
else{//这时候找到了中位数,并且数量是cnt
cout<<cnt + p.second - (n+1)/2 + 1<<endl;
return;
}

}
}
signed main(){
int T = 1;
cin>>T;
while(T--){
solve();
}
return 0;
}

B. Maximum Sum

题意

给出一个初始长度为$n$的数组$a$ 。

对于每步操作,你可以选出其中一个子数组,将这个子数组的总和作为一个新项,插入到整个数组的任意位置。(可以选择空的子数组,这样就插入了一个0)

按照如上操作进行k次后, 问整个数组的总和最大是多少。

思路

首先介绍选取策略:我们选取总和最大的子数组(最大区间和)作为第一步操作。这样得到的第一个sum一定是最大的。接下来我们将sum放在这个子数组的末尾,下一次选取子数组时,我们就可以多加上新的那个数(如果新增的数小于0,那么肯定是不能加的,直接令子数组设置为空,sum就变为了0),依次类推共进行k次即可。

例如:

1
2
3 3
2 2 8

我们进行的三步操作为

1
2
3
4
5
6
7
2 2 8
## 选取[2 2 8]
2 2 8 12
## 选取[2 2 8 12]
2 2 8 12 24
## 选取[2 2 8 12 24]
2 2 8 12 24 48

可以发现,如果我们新增了一个sum在这个子数组之后,下一次就会是$sum 2$ ,也就是每次新增的都是上一次的两倍。最终整段子数组的数值总和即为$sum_0 2^{k}$ ($sum_0$表示最初子数组的总和,k表示操作次数),那么整个数组的总和就是$SUM + sum_0(2^k - 1)$ ,其中$SUM$表示整个初始数组的总和。

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
#include<bits/stdc++.h>
using namespace std;
#define IO ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
#define int long long
#define rep(i,l,r) for(int i = l;i<=r;i++)
#define per(i,r,l) for(int i = r;i>=l;i--)
const int INF = 0x3f3f3f3f3f3f3f3f;
typedef pair<int,int> PII;
int mod = 1e9+7;
int qpow(int x,int n){ //快速幂
int ans = 1;
while(n){
if(n&1) ans = ans * x % mod;
x = x * x % mod;
n >>= 1;
}
return ans;
}
void solve(){
int n,k;
cin>>n>>k;
vector<int> dp(n+5,-INF);//dp数组用来找最大区间和
vector<int> num(n+5);
int sum = 0;
rep(i,1,n){//求整个数组的和
cin>>num[i];
sum += num[i];
}
rep(i,1,n){
dp[i] = max(num[i],dp[i-1]+num[i]);
}
int mx = -INF;
rep(i,0,n){
mx = max(mx,dp[i]);
}
//此时的mx就是最大区间和,也就是sum0
int ans = sum;//
if(mx > 0) ans+=mx%mod*qpow(2,k)-mx;//这里记得要对mx取模再乘2的次幂
cout<<(ans%mod+mod)%mod<<'\n';//记得取模

}
signed main(){
int T = 1;
cin>>T;
while(T--){
solve();
}
return 0;
}

C. Tree Cutting

题意

给你一个树,让你删掉其中k条边,于是就得到了$k+1$ 个子树,现在问在进行完k次删边后,这些子树的大小(即子树中节点的数量)的最小值x ,最大可能是多少。

思路

最小的最大值(或者最大的最小值),一般情况都是二分答案。

check函数为: 使用dfs遍历整棵树,来检索最多能分成几棵子树,如果分出的子树数量大于等于$k+1$ 那么就是True,否则就是False

对于dfs函数,我们可以使用一个返回值来表示当前节点下的子树能贡献多少个节点给祖先

  • 如果当前收获的节点数大于等于x,那么就意味着当前这棵子树可以独立地构成一个合格的子树。于是我们切断他和父亲的边,让答案加一。并且return 0;
  • 如果当前收获的节点数$cnt$小于x,那么就不能独立构成一个合格的子树,就需要将所有的节点都传递到父亲节点。也就是return cnt;

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
#include<bits/stdc++.h>
using namespace std;
#define IO ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
#define int long long
#define rep(i,l,r) for(int i = l;i<=r;i++)
#define per(i,r,l) for(int i = r;i>=l;i--)
const int INF = 0x3f3f3f3f3f3f3f3f;
typedef pair<int,int> PII;
const int maxn = 1e5+5;
vector<int> ed[maxn];
int n,k;
vector<int> vis(maxn);
int ans;
int dfs(int now,int x){
int sum = 1;
vis[now] = 1;
for(auto v : ed[now]){
if(!vis[v]){
vis[v] = 1;
sum += dfs(v,x);
}
}
if(sum >= x) {ans ++;return 0;}
else return sum;
}
bool check(int x){
//初始化一些变量为0
ans = 0;
rep(i,1,n) vis[i] = 0;
//dfs求一下ans的值
dfs(1,x);
return ans>=(k+1);
}
void solve(){
cin>>n>>k;
rep(i,1,n) ed[i].clear();
rep(i,1,n-1){
int u,v;cin>>u>>v;
ed[u].push_back(v);
ed[v].push_back(u);
}
int l = 1,r = n;
while(l<r){
int mid = (l+r+1)/2;
if(check(mid)) l = mid;
else r = mid-1;
}
cout<<l<<endl;
return ;
}
signed main(){
int T = 1;
cin>>T;
while(T--){
solve();
}
return 0;
}

D. Birthday Gift

题意

给你一个长度为$n$的数组$a$,即 $a_1,a_2,…,a_n$ 。 现在你需要将他们划分为一些部分,来使得 每一部分的异或和 全部按位或 起来后的答案$ans$ 满足$ans\le x$ 。问你最多能划分多少部分。如果无解就输出$-1$。

即找到k组$(l,r)$对, 使得$li \le r_i$ 并且$r_i + 1 = l{i+1}, l1 = 1,r_k = n$ 。满足$(a{l1} \oplus a{l1+1}\oplus … \oplus a{r1})|(a{l2} \oplus a{l2+1}\oplus … \oplus a{r2})|…|(a{lk} \oplus a{lk+1}\oplus … \oplus a{r_k}) \le x $

问k最大是多少。

思路

我们用$sumi$表示a的前i项的异或和。即$sum_i = a_1 \oplus a_2 …\oplus a_i$ ,那么原式就变为了$(sum{r1}\oplus sum{l1-1}) | (sum{r2}\oplus sum{l2-1})|…|(sum{rk}\oplus sum{lk-1}) \le x $ 。 这个式子还是有些麻烦,我们发现他还等价于$sum{r1} | sum{r2}|…|sum{r_k} \le x$ 。于是我们就把区间异或的或,变成了单点的或。

有一点需要注意的是,$sum_n$ 是必选项。

于是我们考虑x的每一位,

  • 如果这一位是0, 那么就不做改变,直接看有多少个$sum_i$满足$sum_i | x == x$ 。
  • 如果这一位是1,那么我们就可以将这一位置为0,然后将所有较小位置为1,我们设处理后的x为xx,那么有$xx = (x - (1<<j))|(1<<j-1)$ ,我们可以看有多少个$sum_i$满足$sum_i | xx == xx$ 。

因为$sum_n$是必选项,所以如果$sum_n$ 不满足上述条件,那么这一整组应该直接跳过。

在所有的按位分组中,找到最大的分组数即可。

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
#include<bits/stdc++.h>
using namespace std;
#define IO ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
#define int long long
#define rep(i,l,r) for(int i = l;i<=r;i++)
#define per(i,r,l) for(int i = r;i>=l;i--)
const int INF = 0x3f3f3f3f3f3f3f3f;
typedef pair<int,int> PII;
void solve(){
int n,x;
cin>>n>>x;
vector<int> vec(n+5);
rep(i,1,n){
cin>>vec[i];
vec[i] ^= vec[i-1];
}
int ans = -1;
rep(k,0,32){
int xx = x;
int cnt = 0;
if((x>>k) & 1){
xx -= 1<<k;
xx |= ((1<<k)- 1);
}
if((vec[n] | xx) != xx) {continue;}
rep(i,1,n){
if((vec[i] | xx) == xx){
cnt++;
ans = max(ans,cnt);
}
}
}
if(ans) cout<<ans<<'\n';
else cout<<-1<<'\n';
}
signed main(){
int T = 1;
cin>>T;
while(T--){
solve();
}
return 0;
}