Codeforces Round 1076 (Div. 3) vp补题

原题链接

D 贪心 排序 二分查找

一开始没仔细想去做三分了...

题意:设置一个难度x,每把刀可以砍一次,一关砍 b i b_i bi次,所有威力 a i ≥ x a_i\geq x ai≥x的剑都可以拿去砍,最后得分 = 闯关数 × x =闯关数\times x =闯关数×x

x越大,可用的剑变少,能创的关数可能变少

所以x应该在闯关数一样的情况下尽量大

x最优是选到每把剑的威力,从小到大枚举x,用前缀和二分查找闯关数

cpp 复制代码
void solve(){
  int n;cin>>n;
  vector<int>a(n+1),b(n+2);
  b[n+1]=1e9+1;//为了能找到 闯关数=n 的情况
  forr(i,1,n){
    cin>>a[i];
  }
  forr(i,1,n)cin>>b[i];
  
  vector<int>pb(n+1,0);
  forr(i,1,n){
    pb[i]=pb[i-1]+b[i];
  }
  
  sort(a.begin()+1,a.begin()+n+1);
  int ans=0;
  forr(i,1,n){
    if(a[i]==a[i-1])continue;//已经被枚举过的威力值就跳过
    int p=upper_bound(pb.begin()+1,pb.end(),n-i+1)-pb.begin();//找第一个>可用剑数的关 这个关前面的都闯过去了
    p--;
    ans=max(ans,p*a[i]);
  }
  cout<<ans<<endl;

  // auto cal=[&](int x)->int{
  //   int cnt=0;
  //   forr(i,1,n){
  //     if(a[i]>=x)cnt++;
  //   }
  //   int mark=-1;
  //   forr(i,1,n+1){
  //     if(b[i]>cnt){
  //       mark=i-1;
  //       break;
  //     }else cnt-=b[i];
  //   }
  //   return mark*x;
  // };
  // int l=0,r=amx+1;
  // int ans=0;
  // while (l<r)
  // {
  //   int lm=l+(r-l)/3,rm=r-(r-l)/3;
  //   int lc=cal(lm),rc=cal(rm);
  //   // cout<<lm<<' '<<lc<<' '<<rm<<' '<<rc<<endl;
    
  //   if(lc<rc)l=lm+1;
  //   else if(lc==rc){
  //     if(lc==0)r=rm-1;
  //     else l=lm+1;
  //   }
  //   else r=rm-1;
  //   // int mid=(l+r)/2;
  //   // int lc=cal(mid-1),rc=cal(mid+1);
  //   // if(lc<=rc)l=mid;
  //   // else r=mid;
  // }
  // ans=max({ans,cal(l),cal(r)});
  // // cout<<"ans";
  // cout<<ans<<endl;
}

E dp(有点像完全背包?)

题意:求 1 ∼ n 1\sim n 1∼n每个数能从 a 1 ∼ a n a_1\sim a_n a1∼an选最少多少个数相乘得到
d p [ i × j ] = m i n ( d p [ i ] + d p [ j ] , d p [ i × j ] ) dp[i\times j]=min(dp[i]+dp[j],dp[i\times j]) dp[i×j]=min(dp[i]+dp[j],dp[i×j]), i , j i,j i,j都可以用数组中的数相乘得到,简单来想,可以把 i i i规定为 a a a数组中的数,j枚举 2 ∼ n 2\sim n 2∼n每个数( i × j ≤ n i\times j\leq n i×j≤n)

cpp 复制代码
void solve(){
  int n;cin>>n;
  set<int>a;
  forr(i,1,n){
    int x;cin>>x;
    a.insert(x);
  }
  vector<int>dp(n+1,inf);
  for(auto x:a)dp[x]=1;//初始化
  for(auto x:a){//枚举a中数(可选物品)
    for(int i=2;i*x<=n;i++){//枚举2~n(已有物品基础上)
      dp[x*i]=min(dp[x*i],dp[x]+dp[i]);
    }
  }
  forr(i,1,n){
    cout<<(dp[i]==inf?-1:dp[i])<<' ';
  }cout<<endl;
}

F 线性dp

题意:横轴x轴,每次只能向上下右走,x不能往回走,从 ( a x , a y ) → ( b x , b y ) (a_x,a_y)\rightarrow (b_x,b_y) (ax,ay)→(bx,by)遍历每个点求最短路径

x的单向性可以想到层层递推,每层要遍历每个点,就是要走遍每层最左最右的点
有点像这个题 P3842 [TJOI2007] 线段

cpp 复制代码
//dp[i][0]表示到第i行左端点最小代价 dp[i][1]表示到右端点 len是每行线段长度
dp[1][0]=r[1]-1+len[1],dp[1][1]=r[1]-1;
forr(i,2,n){
    dp[i][0]=min(dp[i-1][0]+abs(r[i]-l[i-1])+len[i],dp[i-1][1]+abs(r[i-1]-r[i])+len[i]);
    dp[i][1]=min(dp[i-1][0]+abs(l[i]-l[i-1])+len[i],dp[i-1][1]+abs(r[i-1]-l[i])+len[i]);
}
int ans=(n-1)+min(dp[n][0]+n-l[n],dp[n][1]+n-r[n]);

状态转移方程推导如图

dp[i][1]同理

F题代码:

cpp 复制代码
void solve(){
  int n,ax,ay,bx,by;
  cin>>n>>ax>>ay>>bx>>by;
  vector<pii>pos(n+1);
  forr(i,1,n)cin>>pos[i].first;
  forr(i,1,n)cin>>pos[i].second;
  sort(pos.begin()+1,pos.end());

  vector<int>x;vector<vint>y;
  x.push_back(ax);y.push_back({ay});
  for(int i=1;i<=n;i++){
    x.push_back(pos[i].first);
    int si=i;
    vector<int>yt;
    while(i<=n&&pos[si].first==pos[i].first){
      yt.push_back(pos[i].second);
      i++;
    }
    y.push_back(yt);
    i--;
  }
  x.push_back(bx);y.push_back({by});

  int m=x.size();
  vector<array<int,2>>dp(m+1,{inf,inf});
  dp[0][0]=dp[0][1]=0;
  forr(i,1,m-1){
    int l1=y[i-1].front(),r1=y[i-1].back(),l2=y[i].front(),r2=y[i].back();
    int need=(x[i]-x[i-1])+(r2-l2);//走到第i行+走遍第i行 必要的步数
    dp[i][0]=min(dp[i-1][0]+abs(r1-l2),dp[i-1][1]+abs(l1-l2))+need;
    dp[i][1]=min(dp[i-1][0]+abs(r1-r2),dp[i-1][1]+abs(l1-r2))+need;
  }
  cout<<dp[m-1][0]<<endl;

}

G DFS DFN序 思维

题意:图是一棵树(无环、连通、无向),所以任意两点 x,y 之间的路径是唯一的。

每次查询一对 (a,b),在不超过一半查询内,找到一个在 x 到 y 路径上的点。

次数不超过一半,所以每次问的a,b两点要覆盖整个树,并且不要重复

询问的回答:

  • 回答 0:两条路径完全不相交。(这个路径上所有点都不可能成为结果)
  • 回答 1:x 到 y 的路径与 a 到 b 的路径有公共顶点。
    这个路径某个点可能成为结果
    继续在这个路径上怎么找才能控制住查询次数?
    如果本次询问的 ( a ′ , b ′ ) (a',b') (a′,b′)路径上其他点都被问过并排除过了,只问 a ′ , b ′ a',b' a′,b′两点就可以了
    所以之前询问的 ( a , b ) (a,b) (a,b)要把 ( a ′ , b ′ ) (a',b') (a′,b′)路径上其他点都覆盖并排除,

dfn它能把树的结构映射成线性序列,dfn序相邻的点之间的路径没有没有处理过的点。

定义:在DFS遍历过程中,首次访问节点时的编号序列(DFN即"Depth-First Number"的缩写)。每个节点的DFN值是其被首次发现的时间戳,仅记录1次。
一文看懂DFS序、DFN序与欧拉序

cpp 复制代码
int ask(int u,int v){
  cout<<"? "<<u<<' '<<v<<endl;
  fls;
  int res;cin>>res;return res;
}
void ans(int x){
  cout<<"! "<<x<<endl;
  fls;
}
void solve(){
  int n;cin>>n;
  vector<vint>g(n+1);
  forr(i,1,n-1){
    int u,v;cin>>u>>v;
    g[u].push_back(v);
    g[v].push_back(u);
  }

	//找dfn序
  vector<int>dfn;
  auto dfs=[&](auto&& dfs,int now,int fa)->void{
    dfn.push_back(now);
    for(auto x:g[now]){
      if(x==fa)continue;
      dfs(dfs,x,now);
    }
  };
  dfn.push_back(0);
  dfs(dfs,1,0);

//问相邻的点
  for(int i=1;i<n;i+=2){
    if(ask(dfn[i],dfn[i+1])){
      if(ask(dfn[i],dfn[i]))ans(dfn[i]);
      else ans(dfn[i+1]);
      return;
    }
  }
  if(n&1)ans(dfn[n]);
  return;
}

G DFS 拓扑排序(构造入度) 奇偶性

写力竭了...总之删的大致思路是 偶权值点(不会改变权值和奇偶性)->奇权值点和之前没被删掉的偶权值点

cpp 复制代码
//奇偶性、加减->异或
//异或1:反转 异或0:不变 偶权值点对权值和奇偶性无影响
//思路:先去掉能去掉的偶权值点 再着力去掉几个能去掉的奇权值点
//由于只有奇权值点才会对消除有影响 只建立奇权值点的树 
//发现可以去掉的奇权值点必连了偶数条边 消除它就会消除偶数条边
//如果一个树中有奇数条边 消到最后剩一条边连两个奇权值点 消不掉
void solve(){
  int n;cin>>n;
  vector<int>a(n+1),s(n+1,0),vis(n+1,0),ans;
  forr(i,1,n){
    cin>>a[i];
    a[i]&=1;
  }
  vector<vint>g(n+1);
  forr(i,1,n-1){
    int u,v;cin>>u>>v;
    g[u].push_back(v);
    g[v].push_back(u);
    s[u]^=a[v];
    s[v]^=a[u];
  }
  //先去掉权重为偶数的点
  forr(i,1,n)if(!a[i]&&s[i]){
    ans.push_back(i);
    vis[i]=1;
  }
  //建奇数权重树/森林
  vector<vint>odd_g(n+1);
  auto dfs=[&](auto &&dfs,int now,int fa)->void{
    for(auto x:g[now]){
      if(x==fa)continue;
      if(a[now]&&a[x]){
        odd_g[now].push_back(x);
        odd_g[x].push_back(now);
      }
      dfs(dfs,x,now);
    }
    return;
  };
  dfs(dfs,1,0);

  //遍历每个奇数权重树 并用拓扑排序消除
  vector<int>deg(n+1,0),sz(n+1),tree; //入度/大小/树中的点
  auto odd_dfs=[&](auto&& odd_dfs,int now,int fa)->void{
    sz[now]=1;tree.push_back(now);
    for(auto x:odd_g[now]){
      if(x==fa)continue;
      odd_dfs(odd_dfs,x,now);
      //构造deg 要消除一个点 必须消除偶数个边
      if(sz[x]%2)deg[x]++; //如果x为根的子树大小为奇数 x<-now
      // 子树中边数为偶数 能消完所有的边 最后剩x一个点 只能等消除now来消除x-now的边 再消除x
      else deg[now]++; //如果x为根的子树大小为偶数 x->now
      // 子树中边数为奇数 消不完所有的边 会剩一条和x相连 x的权值和为偶数 x可以直接被消除
      sz[now]+=sz[x];
    }
    return;
  };
  auto topo=[&]()->void{
    queue<int>q;
    for(auto x:tree)if(deg[x]==0)q.push(x);
    while (q.size())
    {
      int now=q.front();q.pop();
      ans.push_back(now);vis[now]=1;
      for(auto x:g[now]){
        if(!vis[x]){
          if(a[x]==0){
            ans.push_back(x);//x是偶数 之前消不掉 消掉和它相邻的奇数后可以消掉
            vis[x]=1;
          }
          else{
            if(--deg[x]==0)q.push(x);
          }
        }
      }
    }
    return;
  };
  forr(i,1,n){
    if(!vis[i]&&a[i]){
      //每个奇数权重树
      tree.clear();
      odd_dfs(odd_dfs,i,0); //先遍历统计
      if(sz[i]%2==0){ //偶数个点奇数个边 这棵树删不完
        return rno,void();
      }
      topo();
    }
  }
  forr(i,1,n)if(!vis[i])return rno,void();
  ryes;
  for(auto x:ans)cout<<x<<' ';cout<<endl;
}
相关推荐
Bear on Toilet2 小时前
递归_二叉树_49 . 路径综合Ⅲ
数据结构·算法·前缀和·深度优先·递归
CHANG_THE_WORLD2 小时前
深入指针3 - 完全精讲版
数据结构·算法
im_AMBER2 小时前
Leetcode 124 二叉搜索树的最小绝对差 | 二叉树的锯齿形层序遍历
数据结构·学习·算法·leetcode·二叉树
ADDDDDD_Trouvaille2 小时前
2026.2.14——OJ78-82题
c++·算法
Hag_202 小时前
LeetCode Hot100 560.和为K的子数组
数据结构·算法·leetcode
田里的水稻2 小时前
FA_规划和控制(PC)-规律路图法(PRM)
人工智能·算法·机器学习·机器人·自动驾驶
追随者永远是胜利者2 小时前
(LeetCode-Hot100)23. 合并 K 个升序链表
java·算法·leetcode·链表·go
ab1515172 小时前
2.16完成107、108、111
算法
小O的算法实验室3 小时前
2026年IEEE IOTJ SCI2区TOP,面向关键节点感知的灾害区域无人机集群路径规划,深度解析+性能实测
算法·无人机·论文复现·智能算法·智能算法改进