1 条题解

  • 0
    @ 2025-8-24 22:49:22

    自动搬运

    查看原文

    来自洛谷,原作者为

    avatar Vct14
    **

    搬运于2025-08-24 22:49:22,当前版本为作者最后更新于2023-08-11 13:18:58,作者可能在搬运后再次修改,您可在原文处查看最新版

    自动搬运只会搬运当前题目点赞数最高的题解,您可前往洛谷题解查看更多

    以下是正文


    显然地,若要对 cxc_x 进行变换操作,要分类讨论:当 cxyc_x \ne y 时,为了更快的接近目标状态,一定会将 cxc_x 改成 yy;当 cx=yc_x=y 时,cxc_x 一定会被改为一个不为 yy 的值。

    所以,我们将数组中等于 yy 的元素染成红色,将不等于 y y 的元素染成蓝色。变换操作就相当于选择一个区间,将区间内的蓝色变为红色,红色变为蓝色。而目标就变为了将数组全部变为红色。

    for(int i=1; i<=n; i++) a[i]=(read()==y);//true 表示红,false 表示蓝
    for(int i=1; i<=n; i++) b[i]=(read()==y);//同上
    

    先不考虑翻转操作和如何合并,假设合并后 cc 数组染色后如图,考虑这种情况下最少需要多少次操作达到目标状态。

    容易发现,这张图至少需要 44 次操作才能达到目标,即将所有蓝色段变为红色。推广到更一般的情况,我们可以发现,最小的操作次数就是蓝色段的个数。

    我们先考虑如何让数组 a,ba,b 合并后满足合并要求并统计出蓝色段个数。为了最小化操作次数,就要让蓝色段的数量最少,合并时应该让尽量多的蓝元素挨在一块,让尽量多的红元素挨在一块。

    可以发现,执行以下两种操作若干次直到 a,ba,b 为空,可以满足要求:

    • 取出 aa 的前缀的若干个元素放入 cc 数组(后方元素向前移)。
    • 取出 bb 的前缀的若干个元素放入 cc 数组(后方元素向前移)。

    意识到这点,我们就得到思路了:

    1. 将初始颜色段设为红色段。因为红色在前可以让更多的蓝色挨在一起。
    2. 若当前段是蓝色段,蓝色段计数器 ss+1s\gets s+1
    3. 找到 aa 数组的前缀有多少个与该颜色段颜色相同,全部放入 cc 数组。
    4. 找到 bb 数组的前缀有多少个与该颜色段颜色相同,全部放入 cc 数组。
    5. 变换为下一段颜色,重复第 22 步。

    最终,ss 就是合并后最小的蓝色段数量。

    bool now=true;
    int c1=0,c2=0,s=0;
    while(1){
    	if(!now) s++;
    	while(a[c1+1]==now && c1+1<=n) c1++;
    	while(b[c2+1]==now && c2+1<=n) c2++;
    	if(c1==n && c2==n) break;
    	now=!now;
    }
    

    最优的合并策略已考虑,现在考虑最优翻转策略。由于答案是蓝色段的个数,所以翻转操作要让蓝色段数量减少。

    我们选择一个红色段的左端点为 ll,与其相邻蓝色段的右端点为 rr,将 [l,r][l,r] 翻转,就可以减少一个蓝色段了。

    于是,我们可以发现,每一次的翻转都可以减少一个蓝色段!因此,若需要翻转的次数 s1zs-1\geqslant z,只需尽可能减少蓝色段,即翻转 zz 次。答案就是 szs-z

    有人可能有疑问:如果翻转到只剩一个蓝色段后(需要 s1s-1 次),翻转次数小于 zz(即 s1<zs-1<z)怎么办?其实,可以直接进行变换操作将其全部变为红色,答案为 11,再不停翻转整个区间 [1,2n][1,2n]

    还有一种特殊情况,就是输入的所有元素都是红元素(s=0s=0),直接输出 00 即可。

    代码

    #include<bits/stdc++.h>
    using namespace std;
    
    const int N=1e6+10;
    bool a[N],b[N];
    
    char *p1,*p2,buf[100000];
    #define r() (p1==p2 && (p2=(p1=buf)+fread(buf,1,100000,stdin),p1==p2)?EOF:*p1++)
    int read(){
        int x=0,f=1;
        char ch=r();
        while(ch<48||ch>57){
            if(ch=='-') f=-1;
            ch=r();
        }
        while(ch>=48 && ch<=57) x=x*10+ch-48,ch=r();
       	return x*f;
    }
    
    int main(){
    	int n=read(),y=read(),z=read();
    	for(int i=1; i<=n; i++) a[i]=(read()==y);
    	for(int i=1; i<=n; i++) b[i]=(read()==y);
    	bool now=true;
    	int c1=0,c2=0,s=0;
    	while(1){
    		if(!now) s++;
    		while(a[c1+1]==now && c1+1<=n) c1++;
    		while(b[c2+1]==now && c2+1<=n) c2++;
    		if(c1==n && c2==n) break;
    		now=!now;
    	}
    	if(!s) cout<<0;
    	else if(s-1>=z) cout<<s-z;
    	else cout<<1;
    	return 0;
    }
    
    • 1

    「AWOI Round 2 C」数组操作?数组操作!

    信息

    ID
    8328
    时间
    500ms
    内存
    128MiB
    难度
    3
    标签
    递交数
    0
    已通过
    0
    上传者