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 ) dpi\\times j=min(dpi+dpj,dpi\\times j) dpi×j=min(dpi+dpj,dpi×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]);

状态转移方程推导如图

dpi1同理

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;
}
相关推荐
Jerry1 小时前
LeetCode 28. 找出字符串中第一个匹配项的下标
算法
Jerry3 小时前
LeetCode 459. 重复的子字符串
算法
海石6 小时前
1500分的题目,确实有实力,不过还是我略胜一筹
算法·leetcode
海石6 小时前
【记忆化搜索】条条大路通AC,走好适合你的那一条,走到后再考虑走得快
算法·leetcode
Jerry8 小时前
LeetCode 151. 反转字符串中的单词
算法
a11177610 小时前
LM 算法迭代过程动画演示(SLAM)
算法
头茬韭菜11 小时前
Context 的生死抉择:四层压缩、截断算法与 Session Memory
算法·ai
Jerry11 小时前
LeetCode 541. 反转字符串 II
算法
Jerry11 小时前
LeetCode 344. 反转字符串
算法
搞科研的小刘选手11 小时前
【香港大学主办&IEEE出版】第六届计算机视觉、应用与算法国际学术会议(CVAA 2026)
算法·计算机视觉·应用·学术会议