Codeforces Round 882 (Div. 2)

文章提供了三个编程问题的解决方案,涉及数组的处理。第一个问题关注如何将数组分为指定数量的子区间以最小化区间权值和,策略是按差值从大到小选择分界点。第二个问题要求在与运算后和最小的情况下最大化区间数,解法是根据与运算的性质分析情况。第三个问题涉及异或操作,通过存储前缀异或和并计算最大后缀异或和。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

A. The Man who became a God

题意:输入给出包含n个数的数组,和要分成的区间个数k,定义区间权值为所有相邻两个数的差值的和,要求将数组分为k个子区间使得所有子区间的权值和最小

思路:将一个权值为x的区间分为两个子区间,假设两个子区间相邻处的两个元素的差值为y,两个子区间的权值分别为a和b,则有x=a+b+y,则若想要使a+b最小,就要使y最大,所以本题只需要将所有的相邻元素差求出来,然后按照从大到小的顺序找到前n-k个差,这n-k个差所在的位置就是要将区间分开的边界

ac代码:

#include<bits/stdc++.h>
#define endl '\n'
#define ll long long
#define INF 0x3f3f3f3f
#define pb push_back
#define int long long
#define Mirai ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
using namespace std;
typedef pair<int,int> pii;
const int N=1100;
int n,k;
int b[N];//标记断点
int a[N];
void solve()
{
    vector<pii> g;
    cin>>n>>k;
    memset(b,0,sizeof b);
    for(int i=0;i<n;i++)
    {
        cin>>a[i];
        if(i)g.pb({abs(a[i]-a[i-1]),i});
    }
    if(n==k)
    {
        cout<<0<<endl;
        return ;
    }
    sort(g.begin(),g.end());
    for(int i=g.size()-1;i>=g.size()-k+1;i--)
    {
        b[g[i].second]=1;//标记断点
    }
    // for(int i=0;i<n;i++)cout<<b[i]<<" ";
    // cout<<endl;
    int temp=0,res=0;
    for(int i=0;i<n-1;i++)
    {
        if(b[i+1]==0)
        {
            temp+=abs(a[i]-a[i+1]);
        }
        else 
        {
            res+=temp;
            temp=0;
        }
    }
    res+=temp;
    cout<<res<<endl;
}
signed main()
{
    Mirai;
    int T=1;
    cin>>T;
    while(T--)
    {
        solve();
    }
}

B. Hamon Odyssey

题意:给出包含n个数的数组,要求将该数组划分为数个子区间,要求将所有区间进行与运算之后求和,使得和最小并且区间数最多

trick:与运算:0&0=0,1&0=0,0&1=0,1&1=1,由此可以发现,与运算存在性质:0在与运算之后不可能变为1,而1与运算之后可能变为0,所以对一个数进行与运算,只会越运算越小或不变,而不会变大。

思路:本题是在要求与和最小的前提下使区间数最多,所以首先要考虑这个前提,既然与运算只会越运算越小,所以对于本题,考虑两种情况:1、f(1,n)!=0,既然所有数全部进行与运算之后结果仍然不为零,则将整段区间分为任意情况的子区间,可知每个子区间的与都会大于f(1,n),自然与的和也会大于f(1,n),所以对于f(1,n)!=0的情况,结果为1。2、f(1,n)=0,既然整段区间的与已经达到了最小,则此时再考虑怎么分才能使得区间数最多,既然题目要求的是子区间而不是子序列,所以只需要从前往后遍历一遍,找出所有与为1的子区间即可,特殊处理如果最后剩下的区间与不为0,则将该区间与前面的一个与为0的区间合并为一个区间即可,因为0与任何数都为0

ac代码:

#include<bits/stdc++.h>
#define endl '\n'
#define ll long long
#define INF 0x3f3f3f3f
#define pb push_back
#define int long long
#define Mirai ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
using namespace std;
typedef pair<int,int> pii;
const int N=2e5+10;
int n;
int a[N];
void solve()
{
    cin>>n;
    int now;
    for(int i=0;i<n;i++)
    {
        cin>>a[i];
        if(!i)now=a[i];
        else now&=a[i];
    }
    if(now)cout<<1<<endl;
    else 
    {
        int cnt=-1;
        for(int i=0;i<n;i++)
        {
            if(now==0)
            {
                now=a[i];
                cnt++;
            }
            else 
            {
                now&=a[i];
            }
        }
        if(!now)cnt++;
        cout<<cnt<<endl;
    }
}
signed main()
{
    Mirai;
    int T=1;
    cin>>T;
    while(T--)
    {
        solve();
    }
}

C. Vampiric Powers, anyone?

题意:输入给出n个非负整数,下标1~n,可以进行任意次以下操作:在数组末尾添加一个原数组的任意后缀异或和,要求输出可以得到的最大后缀异或和

trick:异或是一个可逆运算,如果a^b=c,则c^b=a,也就是一个数两次异或同一个数,就相当于没有异或这个数,所以可以通过前缀异或和来求区间异或和

思路:本题可以通过两次后缀异或和操作来实现区间异或和操作,若要得到xor[l-r],可以通过a[n+1]=xor[r+1,n],则a[n+2]=xor[l,n+1]=xor[l-r],则该题就转化成了求原数组的最大子区间异或和,而本题数组中的数最大不超过256,也就是说任意子区间的异或和都不会超过256,我们可以用一个set来存已经出现过的前缀异或和,不同前缀区间有相同的异或和可以只做一次运算,因为本题不需要找出具有最大异或和的区间是哪一段

ac代码:

#include<bits/stdc++.h>
#define endl '\n'
#define ll long long
#define INF 0x3f3f3f3f
#define pb push_back
#define int long long
#define Mirai ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
using namespace std;
typedef pair<int,int> pii;
const int N=1e5+10;
int a[N];
int n;
void solve()
{
    cin>>n;
    for(int i=1;i<=n;i++)cin>>a[i];
    set<int> s;
    int ans=0;
    s.insert(0);
    for(int i=1;i<=n;i++)
    {
        a[i]^=a[i-1];
        for(auto j:s)
        {
            ans=max(ans,a[i]^j);
            //将此前缀异或和与所有之前出现过的前缀异或和进行异或
            //可以得到以当前点之前的所有点为起点,以当前点为终点的区间的异或和
            //而我们只需要得到最大异或和,而这些前缀异或和中又有很多重复数字
            //所以用set来保存出现过的前缀异或和进行异或即可
        }
        s.insert(a[i]);
    }
    cout<<ans<<endl;
}
signed main()
{
    Mirai;
    int T=1;
    cin>>T;
    while(T--)
    {
        solve();
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值