【浮*光】【Codeforces Round #500 (Div. 2)】A,B,C,D,E,F

题目地址ヽ(✿゚▽゚)ノ


     目录

【Problem A】Piles With Stones

【Problem B】And

【Problem C】Photo of The Sky

【Problem D】Chemical table

【Problem E】Hills

【Problem F】AB-Strings


【Problem A】Piles With Stones

题目大意

有若干堆石子排成一排。第一天科研人员记录了它们每一堆的数量。

第二天科研人员又记录了这些石子的数量。晚上可能有非常多的游客来过。

每个游客有三种可选操作:

  1. 不对石子做什么。
  2. 拿走一个石子
  3. 将一个石子移动到另一堆中。

给定两天记录的数据,问这种情况是否可能。


Code

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;

/*【A. Piles With Stones】
若干堆石子排成一排。每天记录每一堆的数量。
晚上可能有非常多的游客来过。每个游客有三种可选操作:
1.不对石子做什么。2.拿走一个石子。3.将一个石子移动到另一堆中。
给定两天记录的数据,问该情况是否可能。*/

//【分析】直接判断第二天的数量是否小于等于第一天

int main(){
    int n,x,s1=0; scanf("%d", &n);
    for(int i=1;i<=n;i++)
        scanf("%d",&x), s1+=x;
    for(int i=1;i<=n;i++)
        scanf("%d",&x), s1-=x;
    if(s1<0) puts("No");
    else puts("Yes");
    return 0;
}

【Problem B】And

题目大意

给定n个数和x,将一个数按位与x算作一次操作,

要求序列中至少有两个相同的数,问最少的操作数。无解输出-1。


Code

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;

/*【B. And】
给定n个数和x,每次操作将一个数按位与x。
要求序列中至少有两个相同的数,问最少的操作数。无解输出-1。 */

/*【分析】显然最少的操作数只可能是-1,0,1,2。
对应情况:无解。不需要操作。a按位与x后与b相同。两个数按位与x后相同。
将每个数按位与x。运用set和map判断。*/

const int N=1e5+5;
int n,x,res=3;
int ar[N]; //原数组

map<int,int>sa; //判断有没有重复元素
//map<int,int>组成一个pair对象,第一个类型是键,第二个是值
//其中的count函数只判断一维int的值是否出现过

set<int>sb; //异或的集合
//map和set容器中不会出现相同的元素,因此count()的结果只能为0和1

int main(){
    scanf("%d%d",&n,&x);
    for(int i=1;i<=n;i++){
        scanf("%d",&ar[i]);
        if(sa.count(ar[i])){ //返回指定元素出现的次数
            puts("0"); return 0;
        }
        sa[ar[i]]=i; 
        //map函数查找序号(键)是否存在,只判断一维int的值是否出现过
    }
    for(int i=1;i<=n;i++){
        int b=ar[i]&x;
        if(sb.count(b)) res=min(res,2);
        sb.insert(b); //加入元素b
        if(sa.count(b)&&sa[b]!=i) res=1;
    }
    if(res==3) puts("-1"); //res初始化为3    
    else printf("%d",res);
    return 0;
}

【Problem C】Photo of The Sky

题目大意

有2n个数,要求分成元素个数相等的两组X和Y,

使得(max(X)−min(X))*(max(Y)−min(Y))最小。问这个最小值。

其中min(X),max(X)分别表示X中最小、最大的数。


Code

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;

/*【C.Photo of The Sky】
有2n个数,要求分成元素个数相等的两组X和Y,
使得(max(X)−min(X))*(max(Y)−min(Y))最小。问这个最小值。
其中min(X),max(X)分别表示X中最小、最大的数。 */

//【分析】1.max和min在同一集合,那么剩下的数一定是排序后连续的一段,
//2.max和min在不同集合,排序后挑出从小到大第1,n,n+1,2*n个的数相减即可。

ll a[200200]; //开两倍qaq

int main(){
    int n;scanf("%d",&n);
    for(int i=1;i<=2*n;i++) cin>>a[i];
    sort(a+1,a+2*n+1);
    if(a[1]==a[n] || a[n+1]==a[2*n]){ //特判有0的情况
        printf("0\n"); return 0;
    }
    ll ans=(a[n]-a[1])*(a[2*n]-a[n+1]); //方案2
    for(int i=2;i<=n;i++) //方案1
        ans=min(ans,(a[i+n-1]-a[i])*(a[n*2]-a[1]));
    cout<<ans<<endl;
    return 0;
}

【Problem D】Chemical table

题目大意

有一个n×m的网格图。

如果三个有原料的格子恰好在恰好围住它们的最小矩形的三个"顶点"上,

那么剩下的一个"顶点"能自动填上原料。

现在有q个位置填上了原料。你可以在某个格子上放置原料。

问如果要整张图都填满原料,至少还要放置多少原料。


Code

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;

/*【D.Chemical table】【思维题】【图论】
n×m的网格图。如果三个标记格在围住它们的最小矩形的三个"顶点"上,
那么剩下的一个"顶点"能自动标记。现在有q个位置填上了标记。
问如果要整张图都填满标记,至少还要放置多少标记。*/

/*【分析】将问题转化:因为一个点与这个点所对应的行和列都有关系, 
所以我们可以把这个点想象成它连接了它所在的行和列(转化为行列表示)。
三个格子自动生成第四个格子,就可以想象为这个矩形所在的两行两列互相连通。
--------->填满整个n*m的矩阵,就相当于让这n行m列之间互相连通。 
可以用并查集去维护两个连通的集合,并且查找连通块个数(-1即为答案)。
因为每新加一个标记点,就可以让某一行和某一列连通,即让两个连通块连通。 */

int fa[500019];

int find_fa(int x){
    if(x==fa[x]) return x;
    return fa[x]=find_fa(fa[x]);
}

int main(){
    int n,m,q,x,y; //x,y用于记录q个位置中的行列数
    scanf("%d%d%d",&n,&m,&q); //先前标记了q个位置
    for(int i=1;i<=500019;i++) fa[i]=i;
    int ans=n+m; //初始化:n行m列都不连通
    for(int i=1;i<=q;i++){
        scanf("%d%d",&x,&y);
        y+=200018; //避免x,y编号重复
        int fx=find_fa(x),fy=find_fa(y);
        if(fx!=fy){ ans-=1; fa[fx]=fy; } //合并
    }
    printf("%d\n",ans-1);
    return 0;
}

【Problem E】Hills

题目大意

有一条连绵起伏的山脉,第 i 个山峰的高度是 hi 。

有一个工程队,每小时能将任意一座山的高度减少1。

一个山峰能建立房子当且仅当它两边的山峰高度严格小于它。

要求输出当建立1.⋯,⌈n^2⌉ 个房子的时候至少需要工程队施工的时间。


状态设定

f[i][j][0]表示前i个数有j个满足条件,i、i-1都不满足条件的最少操作数。

f[i][j][1]表示前i个数有j个满足条件,i满足条件的最少操作数。

f[i][j][2]表示前i个数有j个满足条件,i-1满足条件的最少操作数。

( 这里的操作数都是把 i 当做最右一个,即不计算对第 i + 1 个数的操作 ) 


 转移方程

f[i][j][0]=min(f[i-1][j][0],f[i-1][j][2]);

-----------> 因为 i 和 i - 1 都不满足条件,只能转移状态1、3

f[i][j][1]=min(f[i-1][j-1][0]+max(0,a[i-1]-a[i]+1),
            f[i-1][j-1][2]+max(0,min(a[i-1],a[i-2]-1)-a[i]+1));

-----------> 这种情况下分为两种可能性:

1. 如果前两个都不满足条件,那么 i - 1 一定是原来的数,即没有被操作过(保持最优性)。

-----------> 需要的操作数就是 max(0,a[i-1]-a[i]+1) 。

2. 如果 i - 2 满足条件,那么 i - 1 已经变成了 min(a[i-1],a[i-2]-1)

-----------> 需要的操作数就是 max(0,min(a[i-1],a[i-2]-1)-a[i]+1) 。

f[i][j][2]=f[i-1][j][1]+max(0,a[i]-a[i-1]+1);

-----------> 如果 i - 1 满足条件,只有一种情况,需要的操作数是 max(0,a[i]-a[i-1]+1)


Code

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long ll;            

/*【E.Hills】
有一条连绵起伏的山脉,第i个山峰的高度是hi。
有一个工程队,每小时能将任意一座山的高度减少1。
一个山峰能建立房子当且仅当它[两边的山峰高度严格小于它]。
输出当建立1...[n/2](这里表示n除以2,四舍五入)个房子的时候,
至少需要工程队施工的时间。 */

// f[i][j][0]=min(f[i-1][j][0],f[i-1][j][2]);

// f[i][j][1]=min(f[i-1][j-1][0]+max(0,a[i-1]-a[i]+1),
//                f[i-1][j-1][2]+max(0,min(a[i-1],a[i-2]-1)-a[i]+1));

// f[i][j][2]=f[i-1][j][1]+max(0,a[i]-a[i-1]+1);  

// 手动排序,可以得到要先计算0,再计算2,最后计算1。
// 所以可以优化空间,即省略第一维度。

int a[5019],f[2519][3];

int main(){
    int n; scanf("%d",&n);
    for(int i=1;i<=n;++i) scanf("%d",&a[i]);

    for(int j=0;j<=(n+1)/2;j++) 
        for(int t=0;t<=2;++t) f[j][t]=0x3fffffff; //注意inf不能爆int
    //有的状态不存在,比如f[5][4][x],所以要把所有状态初始化为inf
    
    f[0][0]=f[1][1]=0; //DP起点:把f[1][0][0]和f[1][1][1]初始化为0

    for(int i=2;i<=n;++i) //↓↓↓01背包要逆序枚举
        for(int j=(i+1)/2;j>=1;--j){ //逆序枚举j:防止覆盖未转移的状态
            f[j][0]=min(f[j][0],f[j][2]); //按顺序计算DP数组
            f[j][2]=f[j][1]+max(0,a[i]-a[i-1]+1);
            f[j][1]=min(f[j-1][0]+max(0,a[i-1]-a[i]+1),
                f[j-1][2]+max(0,min(a[i-1],a[i-2]-1)-a[i]+1));
        }

    for(int j=1;j<=(n+1)/2;++j)
        cout<<min(f[j][0],min(f[j][1],f[j][2]))<<' '; //输出答案

    return 0;
}

【Problem F】AB-Strings

题目大意

有两个只含字符'a'、'b'的字符串。一次操作是指交换这两个字符串的可空前缀。

问:使得每个串内只包含一种字符最少的操作数。要求输出方案。


思路分析

(1)首先,连续的相同字符显然可以合并,直接减到只剩一个。

1.两串开头相同。分别取偶数长度前缀奇数长度前缀交换。字符串长度缩减:2 。

2.两串开头不同。各取一个奇数长度前缀并交换。字符串长度缩减:2 。

(2)什么时候会使字符串长度缩减速度下降

将S 、T中较长的称为A,较短的称为B,将 A 的开头字符设为 0,另一种字符设为 1 。

列举缩减速率变慢的情况(需要尽量避免):

  1.

    A = 010101……

    B = 0

    方法:① 在 A 中选取长度为奇数的前缀,接到 B 上。② 把 B 接到 A 上。 缩减: 1。

  2.

    A = 01

    B = 01

    缩减: 1。

  3.

    A = 010101……

    B = 1

    方法:① 在 A 中选偶数长度的前缀,接到 B 上。② 在 A 中取奇数长度的前缀,与 B 交换。

    缩减: 1。

( " 将 X 的某某前缀 接到 Y 上 " 表示将 X 的某某前缀与 Y 的长度为 0 的前缀交换。)

粗略的方法: 每次操作尽量使得操作完毕后两个串的长度近似。(这样使得串的长度尽量不会到 1 )

对于 第 1、2 种情况,将 B 的开头与 A 的长度为奇数的前缀交换,并使得交换后长度近似。

对于 第 3 种情况,采用 任意一种方法,并使得交换后长度近似。两种方法效果相同。

对于两种基本情况,可以在短串只取开头一个,长串判断截取前缀的长度,使交换后长度近似。

于是剩下的东西只需要读入的时候压缩一下,以及用链表维护一下字符串,分类讨论做决策。


Code

#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N=1000005;
int c[N],v[N],Next[N],cnt_node=0;
int L=0,R=1,len[2],head[2],n=0,ans[N][2];
char s1[N],s2[N];

int new_node(int color,int tot,int nxt){
    cnt_node++,c[cnt_node]=color,v[cnt_node]=tot,Next[cnt_node]=nxt;
    return cnt_node;
}

void build(int f,char s[]){
    int n=strlen(s+1),p=new_node(s[1]-'a',1,0);
    head[f]=p,len[f]=1;
    for (int i=2;i<=n;i++)
        if (s[i]==s[i-1])
            v[p]++;
        else
            p=Next[p]=new_node(s[i]-'a',1,0),len[f]++;
}

int func0(int x,int a,int b){
    return abs((a-2*x-1)-(b+2*x));
}

int func1(int x,int a,int b){
    return abs((a-2*x)-(b+2*x-2));
}

int func2(int x,int a,int b){
    return abs((a-2*x-1)-(b+2*x-1));
}

int Get_len(int f){
    int cnt=0;
    for (int p=head[f];p;p=Next[p])
        cnt++;
    return cnt;
}

void New_ans(int Lv,int Rv){
    n++;
    ans[n][L]=Lv,ans[n][R]=Rv;
}

int main(){
    scanf("%s%s",s1+1,s2+1);
    build(0,s1),build(1,s2);
    while (max(len[L],len[R])>1){
        if (len[L]<len[R])
            swap(L,R);
        if (c[head[L]]==c[head[R]]&&len[R]>1&&len[L]>2){
            int a=len[L],b=len[R],x1=(a-b+2)/4,x2=x1+1;
            int x=max(1,min(func1(x1,a,b)<=func1(x2,a,b)?x1:x2,a/2));
            int tot=v[head[L]],p=head[L];
            for (int i=1;i<=x*2-1;i++)
                p=Next[p],tot+=v[p];
            New_ans(tot,v[head[R]]);
            int hL=head[L],hR=head[R];
            v[head[L]=Next[p]]+=v[hR];
            head[R]=hL;
            Next[p]=Next[Next[hR]];
            v[p]+=v[Next[hR]];
            len[L]-=x*2,len[R]+=x*2-2;
            continue;
        }
        else if (c[head[L]]==c[head[R]]){
            int a=len[L],b=len[R],x1=(a-b-1)/4,x2=x1+1;
            int x=min(func0(x1,a,b)<=func0(x2,a,b)?x1:x2,(a-1)/2);
            int tot=v[head[L]],p=head[L];
            for (int i=1;i<=x*2;i++)
                p=Next[p],tot+=v[p];
            New_ans(tot,0);
            swap(head[R],head[L]);
            swap(Next[p],head[L]);
            v[p]+=v[Next[p]];
            Next[p]=Next[Next[p]];
            len[L]=(len[L]<3)?(Get_len(L)):(len[L]-(x*2+1));
            len[R]=(len[R]<3)?(Get_len(R)):(len[R]+(x*2));
        }
        else {
            int a=len[L],b=len[R],x1=(a-b-1)/4,x2=x1+1;
            int x=min(func2(x1,a,b)<=func2(x2,a,b)?x1:x2,(a-1)/2);
            int tot=v[head[L]],p=head[L];
            for (int i=1;i<=x*2;i++)
                p=Next[p],tot+=v[p];
            New_ans(tot,v[head[R]]);
            v[head[R]]+=v[Next[p]];
            v[p]+=v[Next[head[R]]];
            swap(Next[head[R]],Next[p]);
            Next[head[R]]=Next[Next[head[R]]];
            Next[p]=Next[Next[p]];
            swap(head[L],head[R]);
            len[L]=(len[L]<3)?(Get_len(L)):(len[L]-(x*2+1));
            len[R]=(len[R]<3)?(Get_len(R)):(len[R]+(x*2-1));
        }
    }
    printf("%d\n",n);
    for (int i=1;i<=n;i++)
        printf("%d %d\n",ans[i][0],ans[i][1]);
    return 0;
}

                                               ——时间划过风的轨迹,那个少年,还在等你。

猜你喜欢

转载自blog.csdn.net/flora715/article/details/82142386