Codeforces1615 E. Purple Crayon(长链剖分,cf2400)

题意:

在这里插入图片描述
点1为根节点。
n,k<=2e5

解法:
比较显然的结论:
如果将一个点变成红色点,那么红色点的父节点就不能被选为蓝色点。
因此红方的最优操作一定是选择叶子节点变成红色。

1. 如果叶子数量sz<=k,那么蓝方就无法操作,
此时枚举红色节点数量[sz,k],计算答案最大值即可。
2. 如果叶子数量sz>k,那么红方选择叶子节点只能贪心的来选:
选择叶子到根节点这条链长度最大的叶子节点。然后将该链删掉。
贪心的选择k次。
问题在于如何快速的找到链最长的节点。
一个简单的做法是先对树进行长链剖分,红方选择最长的k条链即可。
最后枚举蓝色点的数量计算答案。
Code:
#include <bits/stdc++.h>
using namespace std;
#define X first
#define Y second
#define int long long
#define PI pair<int, int>
const int maxm=2e6+5;
// const int mod=998244353;
const int mod=1e9+7;

int n,k;
vector<int>g[maxm];
int mdep[maxm]; // 子节点最大深度
int mson[maxm]; // 最大深度子节点
int dep[maxm];  // 深度
vector<int>len; // 所有链的长度
void add(int x,int y){
  g[x].push_back(y);
}
void dfs1(int x,int fa){
  mdep[x]=dep[x];
  mson[x]=0;
  for(int v:g[x]){
    if(v==fa)continue;
    dep[v]=dep[x]+1;
    dfs1(v,x);
    mdep[x]=max(mdep[x],dep[v]);
    // 维护x的重儿子
    if(mson[x]==0||mdep[v]>mdep[mson[x]]){
      mson[x]=v;
    }
  }
}
void dfs2(int x,int fa,int tp){
  // x是链头, 将链长度存起来
  if(x==tp){
    len.push_back(mdep[x]-dep[x]+1);
  }
  // 先遍历重链
  if(mson[x]) {
    dfs2(mson[x],x,tp);
  }
  // 再遍历轻链
  for(int v:g[x]){
    if(v==fa)continue;
    if(v==mson[x])continue;
    dfs2(v,x,v);
  }
}
void solve() {
  cin>>n>>k;
  for(int i=1;i<n;i++){
    int l,r;cin>>l>>r;
    add(l,r);
    add(r,l);
  }
  dfs1(1,1);
  dfs2(1,1,1);
  sort(len.begin(),len.end());
  int sz=len.size();
  // 叶子数量sz<=k时, 红方可以在[sz,k]内任选红色点数量
  // 而蓝方无法选蓝色点
  // 枚举红点数量计算ans最大值
  if(sz<=k){
    int ans=0;
    for(int i=sz;i<=k;i++){
      int r=i;
      ans=max(ans,(n-r)*r);
    }
    cout<<ans<<endl;
    return ;
  }
  // 叶子数量>k时,红方贪心的选链长最大的k个叶子
  // 蓝方可以对剩余的链任取蓝点
  // 枚举蓝色点计算ans最小值
  int ans=(n-k)*k;
  int mb=0;
  for(int i=0;i<sz-k;i++){
    mb+=len[i];
  }
  for(int i=0;i<=mb;i++){
    int w=n-k-i;
    int r=k;
    int b=i;
    ans=min(ans,w*(r-b));
  }
  cout<<ans<<endl;
}
signed main() {
// #define MULTI_CASE
  ios::sync_with_stdio(0);
  cin.tie(0);
#ifndef ONLINE_JUDGE
  freopen("../in.txt", "r", stdin);
  freopen("../out.txt", "w", stdout);
#endif
#ifdef MULTI_CASE
  int T;
  cin >> T;
  while (T--)
#endif
    solve();
  return 0;
}

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值