Codeforces Round 958 (Div. 2) ABCD题解

比赛链接https://codeforces.com/contest/1988

A. Split the Multiset

题意

起初 S S S中只有一个数字 n n n , 你每次操作可以选择 S S S中的一个任意一个数 x x x,将他拆分成最多k个数,并且这k个数的和仍为 x x x ,问最少需要多少次拆分,能让 S S S中只剩下 n n n 1 1 1

思路

可以发现,每次拆分出 k − 1 k-1 k1 个 1 效率最高。 所以对于 n , k n,k n,k ,我们有总拆分次数为 ⌈ n − 1 k − 1 ⌉ \lceil \frac{n-1}{k-1} \rceil k1n1

代码

#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,k;
    cin>>n>>k;
    cout<< (n -1 + k - 2) / (k - 1)<<endl;
}
signed main(){
    int T = 1;
    cin>>T;
    while(T--){
        solve();
    }
    return 0;
}

B. Make Majority

题意

给你一个长度为n的01序列 a a a,你可以进行任意次如下操作:

  1. 选择两个数 l , r ( 1 ≤ l ≤ r ≤ ∣ a ∣ ) l,r (1\le l\le r \le |a|) l,r(1lra) , 其中 ∣ a ∣ |a| a 表示a的长度。
  2. 将这段区间替换为 c c c, (如果选择的区间中1的数量大于0的数量,那么c=1,否则c=0)

问能否在进行一定次数的操作后, 使得a变为 1

思路

首先我们可以先选择连续的0 , 这样这些连续的0就一定会变为一个0 。这样每个0周围都会有1~2个1 。 我们说此刻的序列为 a ′ a' a

于是我们每次可以选择一个0,以及它旁边的两个1, 这样这三个数就会被替换为。不断循环往复, 每次替换都会减少一个1以及一个0。 因此我们直接统计 a ′ a' a 中1和0的数量,若1的数量更多那么就输出 Y e s Yes Yes, 否则输出 N o No No

代码

#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;
    cin>>n;
    string str;
    cin>>str;
    string s;
    rep(i,0,n-1){
        if(str[i] == '0'){
            s += str[i];
            while(i<n && str[i] == '0') i++;
        }
        if(i == n) continue;
        s += str[i];
    }
    int c0 = 0,c1 = 0;
    for(auto p : s){
        if( p == '0') c0 ++;
        else c1 ++;
    }
    if(c0 >= c1) cout<<"No\n";
    else cout<<"Yes\n";
}
signed main(){
    int T = 1;
    cin>>T;
    while(T--){
        solve();
    }
    return 0;
}

C. Increasing Sequence with Fixed OR

题意

给你一个数 n n n , 请你构造一个数列 a a a , 使得 1 ≤ a 1 < a 2 < . . . < a k 1 \le a_1 < a_2 < ... < a_k 1a1<a2<...<ak , 并且对于任意的$ 2\le i \le k$ 都有 a i − 1 ∣ a i = n a_{i-1} | a_i = n ai1ai=n 其中|表示按位或运算。要求你构造的 k k k尽可能大。

思路

因为要求相邻的数按位或都是n, 所以对于任意的 a i a_i ai ,一定有 a i ∣ n = n a_i | n = n ain=n, 即 a i a_i ai 一定是从二进制的n中选择一些1来组成。于是我们便只需要考虑n为全1的情况即可(因为n中的数位0在 a i a_i ai 中一定为 0 0 0 )

  • 因为 a i ∣ a i + 1 = n a_i | a_{i+1} = n aiai+1=n 所以对于某一位 j j j ,若 a i , j = 0 a_{i,j} = 0 ai,j=0 那么 一定有 a i + 1 , j = 1 a_{i+1,j} = 1 ai+1,j=1
  • 再根据另一个条件 1 ≤ a 1 < a 2 < . . . < a k 1 \le a_1 < a_2 < ... < a_k 1a1<a2<...<ak ,我们可以得知,当 a i , j = 1 a_{i,j} = 1 ai,j=1 时 ,那么 a i + 1 , j , a i + 2 , j , . . . , a k , j a_{i+1,j},a_{i+2,j},...,a_{k,j} ai+1,j,ai+2,j,...,ak,j 都是1。

于是我们便可以这样构造我们的数列:01111,10111,11011,11101,11110,11111 , 于是若n的二进制中中有t个1,那么我们构造的数列长度便为 t + 1 t+1 t+1 . 构造方法即 让0从高到低地位于t个位中的每一位,最后再加上n本身

需要注意的是,如果n是二进制数, 即n中只有一位1,那么答案是1,数列的值为n本身。

代码

#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;
    cin>>n;
    vector<int> v;
    for(int i = 0;i<63;i++){
        if((1ll<<i) & n){
            v.push_back(i);
        }
    }
    if(v.size() == 1) {cout<<1<<'\n'<<n<<'\n';return ;}
    cout<<(v.size()+1)<<endl;
    for(int i = v.size()-1;i>=0;i--){
        int t = n;
        t ^= (1ll<<v[i]);
        cout<<t<<" ";
    }
    cout<<n<<'\n';

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

D. The Omnipotent Monster Killer

题意

n n n头怪兽, 每个怪兽都有其自己的攻击力,他们通过 n − 1 n-1 n1条边连成一个树。 你可以进行足够多的回合。在每个回合中, 会进行如下过程:

  1. 所有活着的怪兽都对你造成一次等同于其攻击力的伤害
  2. 你选择一些怪兽(这些怪兽不能相邻),将他们杀死

问最少受到多少伤害,你才能够杀死所有的怪物。

思路

首先可以发现,对于n头怪兽,我们最多需要 l o g 2 n log_2n log2n 个回合就可以在最优解的情况下将他们全部击杀。(考虑完全树)。

这样对于$n < 3 * 10^5 $ ,总回合数最多19个回合就可以将他们全部击败。

于是我们考虑树形dp, 设定节点1为 d p [ i ] [ j ] dp[i][j] dp[i][j] 表示节点 i i i在第 j j j 个回合被杀死,那么以它为根节点的子树所造成的总伤害。

于是有:

最终答案: a n s = m i n 1 ≤ j ≤ 19 d p [ 1 ] [ j ] ans = min_{1\le j \le 19} dp[1][j] ans=min1j19dp[1][j]

状态转移: d p [ i ] [ j ] = ∑ v ∈ s o n s ( i ) m i n 1 ≤ k ≤ 19 & k ≠ j d p [ v ] [ k ] + a c k [ i ] ∗ j dp[i][j] = \sum_{v\in sons(i)} min_{1\le k\le 19 \& k \not=j} dp[v][k] + ack[i] * j dp[i][j]=vsons(i)min1k19&k=jdp[v][k]+ack[i]j

复杂度 O ( n ∗ l o g 2 n ) O(n * log^2n) O(nlog2n)

代码

#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 dp[300005][25];
int ack[300005];
vector<int> edge[300005];
void dfs(int u,int fa){
    for(int i = 1;i<=22;i++){
        dp[u][i] = INF;
    }
    for(auto p : edge[u]){
        if(p == fa) continue;
        dfs(p,u);
    }
    for(int j = 1;j<=22;j++){
        bool f = 0;
        dp[u][j] = 0;
        for(auto p : edge[u]){
            if(p == fa) continue;
            f = 1;
            int mn = INF;
            for(int k = 1;k<=22;k++){
                if(k == j) continue;
                   mn = min(mn,dp[p][k]);
            }
            dp[u][j] += mn;
        }
        dp[u][j] += ack[u] * j;
    }
    return ;
}
void solve(){
    int n;
    cin>>n;
    rep(i,1,n) cin>>ack[i];
    if(n == 1) {cout<<ack[1] << endl; return ;}
    rep(i,1,n) edge[i].clear();
    for(int i = 1;i<n;i++){
        int x ,y;cin>>x>>y;
        edge[x].push_back(y);
        edge[y].push_back(x);
    }
    dfs(1,0);
    int ans = INF;
    for(int i= 1;i<=22;i++){
        ans = min(ans,dp[1][i]);
    }
    cout<<ans<<'\n';
}
signed main(){
    int T = 1;
    cin>>T;
    while(T--){
        solve();
    }
    return 0;
}

优化 O ( n l o g n ) O(nlogn) O(nlogn)

对于递推过程,如果我们用前缀/后缀 数组记录 前缀最小值,以及后缀最小值。

p r e [ u ] [ i ] = M I N 1 ≤ j ≤ i d p [ u ] [ j ] l s t [ u ] [ i ] = M I N i ≤ j ≤ 19 d p [ u ] [ j ] pre[u][i] = MIN_{1\le j\le i} dp[u][j] \\lst[u][i] = MIN_{i\le j\le 19} dp[u][j] pre[u][i]=MIN1jidp[u][j]lst[u][i]=MINij19dp[u][j]

于是我们在递推时就可以直接写 d p [ i ] [ j ] = ∑ v ∈ s o n s ( i ) m i n ( p r e [ v ] [ j − 1 ] , l s t [ v ] [ j + 1 ] ) + a c k [ i ] ∗ j dp[i][j] = \sum_{v\in sons(i)} min(pre[v][j-1],lst[v][j+1]) + ack[i] * j dp[i][j]=vsons(i)min(pre[v][j1],lst[v][j+1])+ack[i]j

我们就可以省去一层循环。 实现复杂度 O ( n l o g n ) O(nlogn) O(nlogn)

#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 dp[300005][25];
int ack[300005];
vector<int> edge[300005];
int pre[300005][25];
int lst[300005][25];
void dfs(int u,int fa){
    for(int i = 1;i<= 19;i++){
        dp[u][i] = INF;
    }
    for(auto p : edge[u]){
        if(p == fa) continue;
        dfs(p,u);
    }
    for(int j = 1;j<= 19;j++){
        bool f = 0;
        dp[u][j] = 0;
        for(auto p : edge[u]){
            if(p == fa) continue;
            f = 1;
			//直接利用前后缀求出mn
            int mn = min(pre[p][j-1],lst[p][j+1]);
            dp[u][j] += mn;
        }
        dp[u][j] += ack[u] * j;
    }
	// 求前后缀
	pre[u][0] = INF;
	for(int i = 1;i<= 19;i++){
		pre[u][i] = min(dp[u][i],pre[u][i-1]);
	}
	lst[u][20] = INF;
	for(int i =  19;i>=1;i--){
		lst[u][i] = min(dp[u][i],lst[u][i+1]);
	}
    return ;
}
void solve(){
    int n;
    cin>>n;
    rep(i,1,n) cin>>ack[i];
    if(n == 1) {cout<<ack[1] << endl; return ;}
    rep(i,1,n) edge[i].clear();
    for(int i = 1;i<n;i++){
        int x ,y;cin>>x>>y;
        edge[x].push_back(y);
        edge[y].push_back(x);
    }
    dfs(1,0);
    int ans = INF;
    for(int i= 1;i<= 19;i++){
        ans = min(ans,dp[1][i]);
    }
    cout<<ans<<'\n';
}
signed main(){
    int T = 1;
    cin>>T;
    while(T--){
        solve();
    }
    return 0;
}

优化 O ( n ) O(n) O(n)

如果再细心一些,可以发现,对于一个节点 i i i , 设它的度数为 d e g i deg_i degi , 它会在第 b i b_i bi 轮被杀死, 那么一定有 b i ≤ d e g i b_i \le deg_i bidegi 。于是我们没必要对每个结点都循环到19, 只需要循环到它对应的度数即可。

  • 3
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
抱歉,根据提供的引用内容,我无法理解你具体想要问什么问题。请提供更清晰明确的问题,我将竭诚为你解答。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [Codeforces Round 860 (Div. 2)题解](https://blog.csdn.net/qq_60653991/article/details/129802687)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT0_1"}}] [.reference_item style="max-width: 33.333333333333336%"] - *2* [【CodeforcesCodeforces Round 865 (Div. 2) (补赛)](https://blog.csdn.net/t_mod/article/details/130104033)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT0_1"}}] [.reference_item style="max-width: 33.333333333333336%"] - *3* [Codeforces Round 872 (Div. 2)(前三道](https://blog.csdn.net/qq_68286180/article/details/130570952)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT0_1"}}] [.reference_item style="max-width: 33.333333333333336%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值