yuwj  

回归回归,终于考完了,暑假加训开始了!!!集训开始了!!!
今天还把Typora的主题给搞了个自己喜欢的,同时加入了我校ACM集训队,虽然比较弱,但是还是有区域银牌实力的!训练氛围比我之前那个sb工作室好多了。
关于我怎么加入的,很简单,老师看我愿意坚持训练就让我加入了,对,就是这么简单。然后,关于我校集训队计划,暂时没有动静,电子科大都已经快完成了暑假前集训的选拔了
没办法,咱们弱校没得比,也不知道集训计划怎么样的,还是就是自己玩?

好了好了,上正文,今天的补题,直接Typora复制的,也别嫌我懒,cf版刷阶段题意也没写直接标记了思路代码结束了

6.19 解题报告

前排提醒,务必自己再做一遍

abcDEF

D

题意

01串翻转操作,变成至多1个连续1段的最小操作数

思路

枚举答案串,推式子,前后缀预处理,后缀化简

特判:答案串全是0,cost=n-pre[n]

令pre[i]:表示前缀0的数量

枚举这段连续段的[l,r]段,尝试这个序列变成000[1111]000的最小操作数

钦定$[l,r]$区间为答案的连续1段,则cost=左变0+中变1+右变0,左=l-1-pre[l-1],中=pre[r]-pre[l-1],右=(n-r)-(pre[n]-pre[r])

l:l-1-2*pre[l-1], r:n-r-pre[n]+2*pre[r],ans=min(A[l]+B[r]),处理后缀mn[i]即可优化r的枚举,枚举l就是答案了

倒序枚举可以省掉A和B数组

代码

#include <bits/stdc++.h>
using namespace std;
const int MAXN=2e5+10;
char s[MAXN];
int t,n,sum[MAXN];
int main(){
    for(scanf("%d",&t);t;t--){
        scanf("%d",&n);scanf("%s",s+1);
        for(int i=1;i<=n;++i)sum[i]=sum[i-1]+1-(s[i]-'0');
        int ans=n-sum[n],mn=n;
        for(int i=n;i;--i){
            mn=min(mn,n-i-sum[n]+2*sum[i]);
            ans=min(ans,mn+i-1-2*sum[i-1]);
        }
        printf("%d\n",ans);
    }
    return 0;
}

E

题意

从1节点到n节点最小或路径和

思路

贪心构造+并查集

按位贪心,从高位到低位,看看每一位能否是0,并查集判断连通性,就说明能够全0走到否则就必须是1

如何判断每一位可以为0?

当前构造答案为ans=010110110

判断每一位时,i位到最高位确定,后面所有位任选,即w|X!=X -> 不能选这条边,反之可以选,选择所有边之后,判断1和n是否在连通块中即可

代码

/*
高位到低位的贪心,看看这个bit位能不能保持0到达终点n?
*/
#include <bits/stdc++.h>
using namespace std;
const int MAXN=2e5+10;
int n,m;
struct DSU {
    vector<int> p, sz;
    DSU(int n = 0){init(n);} 
    void init(int n){p.resize(n+1); sz.assign(n+1,1); iota(p.begin(),p.end(),0);} 
    int find(int x){return p[x]==x?x:p[x]=find(p[x]);}
    bool unite(int a,int b){a=find(a); b=find(b); if(a==b) return false; if(sz[a]<sz[b]) swap(a,b); p[b]=a; sz[a]+=sz[b]; return true;}
}dsu;
struct EDGE{
    int u,v,w;
}e[MAXN];
bool check(int ans,int X){
    dsu.init(n);
    for(int i=1;i<=m;++i){
        if((e[i].w|X)==X)dsu.unite(e[i].u,e[i].v);
    }
    return dsu.find(1)==dsu.find(n);
}
int main(){
    cin>>n>>m;
    for(int i=1;i<=m;++i)cin>>e[i].u>>e[i].v>>e[i].w;
    int ans=0;
    for(int i=31;i>=0;i--){
        if(!check(ans,ans|((1<<i)-1)))ans|=(1<<i);
    }
    cout<<ans<<'\n';
    return 0;
}

F线段树不会写呜呜呜

F

题意

一个排列作为高度,从任意i位置开始跳到j位置,j位置限制{$Hj \le Hi-D,|i-j|\le R$}最多能跳多少次?

思路

扫描线+线段树优化dp

dp[i]:表示第i个位置开始跳能跳多少次

转移dp[i]=max(dp[j] for all j in (i-R,i+R))+1

发现查找的其实是一个区间,所以可以用线段树优化,就是区间最大值o(n)-> o(logn)

第二维限制:扫描线

符合条件的可跳Hj具有单调性,枚举高度H // (不断更新可转移位置,然后DP)

边加入线段树,边查询即可,加入的时候加入了当前高度可以跳到的高度位置j,在线段树中区间查[i-R,i+R]的max(dp[j])即可

线段树:建树下标,存储信息:可选位置j,DP[j]

代码

//只需要一个支持单点修改的线段树,然后在值域区间上枚举加入线段树就行

CF板刷

2033C

双指针+真交换

/*
1.交换的两个位置天然指定可以使用双指针 -> 避免了交换后产生的影响,单独考虑直接不用管了
2.如果两个位置向后相等,考虑交换,最后一遍统计
3.交换操作是真交换
*/
#include <bits/stdc++.h>
using namespace std;
const int MAXN=2e5+10;
int num[MAXN],n;
int main(){
    int T;
    for(cin>>T;T;T--){
        cin>>n;for(int i=1;i<=n;++i)cin>>num[i];
        for(int i=1,j=n;i<=n/2;++i,j--){
            if(num[i-1]==num[i]||num[j]==num[j+1])swap(num[i],num[j]);
        }
        int cnt=0;
        for(int i=1;i<n;++i)if(num[i]==num[i+1])cnt++;
        cout<<cnt<<'\n';
    }
    return 0;
}

2033E

讲到过,排列成圈问题

这个题目就是最后共有m个圈,每个圈的花费cost=ceil((size-2)/2)

出错点:板子写错了WWW,换了个板子A了

#include <bits/stdc++.h>
using namespace std;
const int MAXN=1e6+10;
int num[MAXN],id[MAXN],n;
struct DSU {
    vector<int> p, sz;
    DSU(int n = 0){init(n);} 
    void init(int n){p.resize(n+1); sz.assign(n+1,1); iota(p.begin(),p.end(),0);} 
    int find(int x){return p[x]==x?x:p[x]=find(p[x]);}
    bool merge(int a,int b){a=find(a); b=find(b); if(a==b) return false; if(sz[a]<sz[b]) swap(a,b); p[b]=a; sz[a]+=sz[b]; return true;}
    int getsz(int x){return sz[p[x]];}
};

int main(){
    int T;
    for(cin>>T;T;T--){
        memset(num,0,sizeof num);
        cin>>n;for(int i=1;i<=n;++i)cin>>num[i];
        int ans=0;DSU dsu(n);
        for(int i=1;i<=n;++i)
        {
            if(num[i]==i)continue;
            if(!dsu.merge(i,num[i]))ans+=(dsu.getsz(num[i])-1)/2;
        }
        cout<<ans<<'\n';
    }
    return 0;
}

2028B

数学推式子,分类讨论

发现是等差数列,

就是找最大的k使得,$b*(k-1)+c\le n$,

原来的等差变成mex数组,最后变成的肯定是[0...n-1]的情形

这样等差数组的上限就变成了n了,所得的排列也是k项就结束了

代码:

/*
题意:a数组是等差数列,每次最大值转化为mex
1 3 5 7 9 11 13 15 17 19 21
b*(k-1)+c<=n,最大的k满足
*/
#include <bits/stdc++.h>
using namespace std;
using ll=long long;
int main(){
    int T;
    for(cin>>T;T;T--){
        ll n,b,c;cin>>n>>b>>c;
        if(c>=n)cout<<n<<'\n';
        else{
            if(b)cout<<n-(n-c-1)/b-1<<'\n';
            else{
                if(n-c>=3)cout<<-1<<'\n';
                else cout<<n-1<<'\n';
            }
        }
    }
    return 0;
}
posted on 2025-06-19 23:02  xiaowang524  阅读(19)  评论(0)    收藏  举报