Codeforces Round 1020 (Div. 3)DEFG1

D. Flower Boy

题意

给你两个序列a,b,其中保证a的长度 \(n\) 大于b的长度 \(m\) ,要求从a左往右查询匹配b从左往右每一位的元素,当且仅当$ a_i >= b_j $可以正常匹配,现在允许一种操作,选择b数组中的一个数,消耗这个数大小的价值复制到a中任意位置,要求在最多进行一次以上操作后可以让b的每一位匹配到a的 \(m\) 个位置,如果可以输出消耗的价值大小,不可以输出-1.

思路

因为合法匹配b序列中最多只有一位不满足,显然有最小的价值肯定是0或者b中的一位元素,
考虑枚举这个可行位置,然后对每个可行位置取最小值。
那么难点来了,如何枚举,直接暴力的话,假定复制这个位置到a中pos位,则这个位置前面的每一位与a中小于pos的位置匹配,后每一位也与a中大于pos任意位匹配。这样自然而然可以想到前后缀维护b中可行的复制位置

AC code

#include<bits/stdc++.h>
using namespace std;
#define endl '\n'
typedef long long ll;
const ll inf=1e18;
void solve(){
    int n,m;cin>>n>>m;
    vector<ll>a(n+1),b(m+1);
    vector<ll>pre(n+1),suf(n+2);
    for(int i=1;i<=n;i++) cin>>a[i];
    for(int i=1;i<=m;i++) cin>>b[i];
    int j=1;
    for(int i=1;i<=n;i++){
        if(a[i]>=b[j]){
            pre[i]++;
            j++;
        }
        pre[i]+=pre[i-1];
    }
    j=m;
    for(int i=n;i>=1;i--){
        if(a[i]>=b[j]){
            suf[i]++;
            j--;
        }
        suf[i]+=suf[i+1];
    }
    ll ans=inf;
    for(int i=1;i<=n+1;i++){
        if(pre[i-1]+suf[i]>=m) ans=0;
        else if(pre[i-1]+suf[i]==m-1) ans=min(ans,b[pre[i-1]+1]);
    }
    if(ans==inf) ans=-1;
    cout<<ans<<endl;
}
int main(){
    cin.tie(0)->ios::sync_with_stdio(false);
    int T=1;cin>>T;
    while(T--) solve();
    return 0;
}

E. Wolf

题意

给你一个长为n的排列和q组询问
每组询问满足以下格式
\(l,r,k\)
表示对l到r区间使用一次二分查找,想要查找的元素是k元素
但由于排列不一定满足单调性,并且[l,r]区间不一定含有k所以二分查找不一定能找到合法的位置,所以允许你通过交换排列中任意两个不是待查找数的位置的操作使得二分查找的结果是k,可行输出最小操作次数,不可行输出-1

思路

对一个区间进行二分查找,首先如果这个数不在这个区间中那么一定是找不到的,因为题目允许的交换操作不可以对他使用.排列中每个数对应的位置记作\(P(i)\),那么每次二分操作一定是从\([(l+r)/2]\)开始不断逼近\(P(k)\)的,只需要记录二分到\(P(K)\)的左移步数(要几个比K大的数)和右移步数(要几个比K小的数),以及实际右移但是需左移位置个数和实际左移但是右移位置个数,
最终合法性通过总左移个数小于等于比K大的数的个数,总右移个数小于等于比K小的数的个数检验,
而操作次数的最小化max(需要左移但是进行右移步数,需要右移但是左移步数)·2即可

AC code

#include<bits/stdc++.h>
using namespace std;
#define endl '\n'
typedef long long ll;
const ll inf=1e18;
vector<int>a,p;
void solve(){
    int n,q;cin>>n>>q;
    a.resize(n+1),p.resize(n+1);
    for(int i=1;i<=n;i++) cin>>a[i],p[a[i]]=i;
    auto check=[&](int l,int r,int k){
        if(p[k]>r||p[k]<l) return -1;
        int sma=0,big=0;
        int tot_sma=0,tot_big=0;
        while(l<=r){
            int mid=l+r>>1;
            if(a[mid]==k) break;
            if(a[mid]>k){//准备前往左边
                if(p[k]<mid) r=mid-1,tot_big++;//确实在左边
                else sma++,l=mid+1,tot_sma++;//并非
            }
            else if(a[mid]<k){//准备前往右边
                if(p[k]>mid) l=mid+1,tot_sma++;//确实在右边
                else big++,r=mid-1,tot_big++;//并非
            }
        }
        if(tot_sma>=k||tot_big>n-k) return -1;
        return max(sma,big)*2;
    };
    for(int i=1;i<=q;i++){
        int l,r,k;
        cin>>l>>r>>k;
        cout<<check(l,r,k)<<" ";
    }
    cout<<endl;
}
int main(){
    cin.tie(0)->ios::sync_with_stdio(false);
    int T=1;cin>>T;
    while(T--) solve();
    return 0;
}

F. Goblin

题意

给你一个长为n的01串,对01串的每一位单独进行取反操作可以得到新的n个不同01串
现在按顺序排列这n个01串形成1个n·n的01矩阵,求01矩阵中最大全0联通块的大小

思路

逐位考虑,
当前第i位如果是0生成的列是一个仅有第i行是1其余全0的列
当前第i位如果是0生成的列是一个仅有第i行是0其余全1的列
想要求解矩阵中最大0块的大小,考虑把第i行上,第i行,第i行下的行进行拆分
考虑动态规划,从第一列逐列转移到最后一列每种情况最大联通0块的数量
定义状态,
\(当第i位是0时 \begin{cases} f[i][0] =f[i-1][0] +f[i-1][1]+(i-1) & \text{第i行之上0块个数} \\ f[i][1] =0 & \text{第i行的0块所在个数}\\ f[i][2] =f[i-1][1]+(n-i) & \text{第i行之下0块个数} \end{cases} \)
\(当第i位是1时 \begin{cases} f[i][0] = f[i][2] =0 & \text{第i行之上0块个数} \\ f[i][1] = f[i-1][2]+1 & \text{第i行的0块所在个数} \end{cases} \)
最后动态规划从1到n逐列遍历,枚举每一列0联通块极大值,最后输出答案即可

AC code

#include<bits/stdc++.h>
using namespace std;
#define endl '\n'
typedef long long ll;
const ll inf=1e18;
void solve(){
    int n;string s;
    cin>>n>>s;
    s="&"+s;
    vector f(n+1,vector<ll>(3));
    ll ans=0;
    for(int i=1;i<=n;i++){
        if(s[i]=='0'){
            f[i][0]=f[i-1][0]+f[i-1][1]+i-1;
            f[i][2]=f[i-1][2]+n-i;
        }
        else if(s[i]=='1'){
            f[i][1]=f[i-1][2]+1;
        }
        ans=max({ans,f[i][0],f[i][1],f[i][2]});
    }
    cout<<ans<<endl;
}
int main(){
    cin.tie(0)->ios::sync_with_stdio(false);
    int T=1;cin>>T;
    while(T--) solve();
    return 0;
}

G1. Baudelaire (easy version)

题意

给你一棵树,不告诉你根节点,你现在可以对程序使用两种询问,询问一,输出"? 1 n ",接着输出 n个节点,程序会返回给你询问的每个节点到根节点最短路径权值和的和,询问二,选择任意节点u,然后输出"? 2 u",翻转该节点的权值。注意该操作是持久的,会影响程序输出的结果.这是easy版本,保证本题一定是以1为中心的菊花图,但不一定保证1是本题的根节点.同时题目保证每个节点权值一定是1或-1
要求:两种询问总和不超过n+100给出结果,以"! "为格式,输出n个节点最后权值,否则WA了你

思路

交互题,由于本题菊花的性质,考虑对菊花中心首先进行询问然后分类讨论,
大体有以下3种情况,

1.菊花中心就是根节点,交互程序一定会返回一个绝对值为1的答案
2.菊花中心不是根节点,交互程序返回了一个绝对值为2的答案
3.菊花中心不是根节点,交互程序返回了一个绝对值为0的答案

对于第1种情况直接询问剩下节点的获得的答案减去1号节点权值就是每个节点的权值.
对于第2种情况直接翻转1号节点的权值,然后每个非根节点到根节点的路径权值保证只有自己的权值有效,而一号节点权值直接用原先1号点权值到根节点权值/2取相反数即可.
对于第3种情况直接询问每个节点到根节点的权值,记录就是答案,然后反转1号节点权值,记录1号权值等于节点到根节点权值/2.

AC code

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll inf=1e18;
const int N=2e5+10;
vector<int>G[N];
void solve(){
    int n;cin>>n;
    int x,y;
    for(int i=1;i<n;i++){
        cin>>x>>y;
        G[x].push_back(y),G[y].push_back(x);
    }
    auto ask=[&](const int &u) ->int {
        int x;
        cout<<"? 1 1 "<<u<<endl;
        cin>>x;
        cout<<endl;
        return x;
    };
    auto flip=[&](const int &u) ->void {
        cout<<"? 2 "<<u<<endl;
    };
    vector<int>res(n+1);
    int r1=ask(1);
    if(abs(r1)==1){
        res[1]=r1;
        for(int i=2;i<=n;i++) res[i]=ask(i)-r1;
    }
    else if(abs(r1)==2){
        res[1]=-r1/2;
        flip(1);
        for(int i=2;i<=n;i++) res[i]=ask(i);
    }
    else if(r1==0){
        for(int i=2;i<=n;i++) res[i]=ask(i);
        flip(1);
        res[1]=ask(1)/2;
    }
    cout<<"!";
    for(int i=1;i<=n;i++) cout<<" "<<res[i];
    cout<<endl;
}
int main(){
    cin.tie(0)->ios::sync_with_stdio(false);
    int T=1;cin>>T;
    while(T--) solve();
    return 0;
}
posted @ 2025-04-26 00:28  usedchang  阅读(90)  评论(0)    收藏  举报