Codeforces 1042C Array Product

这道题还是挺有意思,本人比赛时候上没做出来(英语不好漏看一个条件......)

题意描述:

给定一个数组(可能有正数,负数和0),可以进行如下两种操作:

1.选择两个位置 $ i $ , $ j $ , $ i $ 在 $ j $ 左边。把 $ j $ 处的数字 $ a[j]*=a[i] $ ,并把 $ a[i] $ 删除掉。

2.选择一个位置 $ x $ ,将这个位置的数字 $ a[x] $ 删除掉。该操作只能用一次。

显然,通过这两个操作,可以把数组中的数字处理到只剩一个。要求使最后的这个数字尽量大。

输出操作的序列。

$ (1 i j) $ 表示操作1

$ (2 x) $ 表示操作2

题解:

开到这一题的时候有点慌,但是想了想就发现,有一些很方便的性质:

·正数直接乘到一起

·如果有0的话,直接删掉就好。

·如果有偶数个负数,无需考虑删去,直接一个一个乘起来就好,最终得到的结果是正数;

·如果有奇数个负数,那么就选择「绝对值最小的负数」也就是「最大的负数」删去,剩下的仍然是偶数个负数,且这样结果最大。

以上就是我在比赛时候想到的......过了样例直接拿去交,然后就WA掉了......QAQ

比赛结束后旁边的dalao告诉我「删除操作只能用一次诶」

好吧是我英语不好没看懂。

然后怎么做?

考虑要删除一些位置的数,我们可以先把这些位置由小到大排序,再通过操作1,把这些数集中到最末的一位上,然后对最后一个位置进行操作2删除就可以了。

迎刃而解。

代码实现:

准备工作


set<int>removed;//记录某个位置的元素是否被删除了
int n,a[200050];//输入
struct ope{//operation,操作
    int c,l,r;//操作种类和位置
    ope(int c=0,int l=0,int r=0):
        c(c),l(l),r(r){
            
        }
};
ope os[200050];//记录操作
int cnt=0;
void v1(int l,int r){//操作1
    cnt++;
    os[cnt]=ope(1,l,r);
    removed.insert(l);
}
void v2(int x){//操作2
    cnt++;
    os[cnt]=ope(2,x,0);
    removed.insert(x);
}
int num_fu=0;//负数的个数
int max_fu_pos=0,max_fu=0;//用来找最大的负数的位置
int tr[200050],cnt2=0;//tr=to_remove,预先将所要删除的位置放在这里
void rm(int x){//将某个位置丢进to_remove中
    cnt2++;
    tr[cnt2]=x;
}
bool cmp(int x,int y){//排序用
    return x<y;
}

读入,检查负数个数,判断是否删去最大负数,删去0和最大负数(如果需要)


        scanf("%d",&n);
    for(int i(1);i<=n;i++){
        scanf("%d",&a[i]);
        if(a[i]<0){//如果是负数
            num_fu++;
                //更新最大负数位置
            if(max_fu==0)max_fu=a[i],max_fu_pos=i;
            else if(a[i]>max_fu)max_fu=a[i],max_fu_pos=i;
        }
        if(a[i]==0)rm(i);//删去0
    }
    if(num_fu%2){//如果需要删去最大负数
        rm(max_fu_pos);
    }
    if(cnt2){//因为有可能出现cnt2==0,即「不需要删去什么」的情况
        sort(tr+1,tr+1+cnt2,cmp);//从小到大排序
        for(int i(1);i<=cnt2-1;i++)v1(tr[i],tr[i+1]);//从前往后合并
        v2(tr[cnt2]);//删除最末一个
    }

合并剩下的负数和正数,记录操作


        int lst=0;//指向上一个的位置
    for(int i(1);i<=n;i++){
        if(removed.count(i))continue;//如果这一位已经被删了(0或最大负数),跳过
        if(lst==0){
            lst=i;
            continue;
        }
        v1(lst,i);
        lst=i;//更新指针
    }

注意:数据可能会出现一种情况,就是「删去所有0和最大负数后,数组已经空了」

比如:

4
0 -10 0 0

这样的话,如果把所有要删除的合并,然后删掉最末一个,就会出现「一个也不留」的情况。

所以在合并所有数字之后,输出操作序列之前,要加一个特判:

如果是「一个也不留」的情况,就不执行「删除最末一个」的操作,留下一个0作为答案。


        bool flag=1;//是否全为0 
    for(int i(1);i<=n;i++){
        if(a[i]!=0 and !removed.count(i)){//如果有一个数非零且没有被删掉,
            flag=0;
            break;
        }
    }

最后是输出答案:


        for(int i(1);i<=cnt;i++){
        if(os[i].l==0)continue;
        if(os[i].c==1)printf("%d %d %d\n",os[i].c,os[i].l,os[i].r);
        else if(!flag)printf("%d %d\n",os[i].c,os[i].l);//如果是上述情况就不输出操作2
    }

到此结束。

完整代码:


#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<set>
#define LL long long
using namespace std;
set<int>removed;
int n,a[200050];
struct ope{
    int c,l,r;
    ope(int c=0,int l=0,int r=0):
        c(c),l(l),r(r){
            
        }
};
ope os[200050];
int cnt=0;
void v1(int l,int r){
    cnt++;
    os[cnt]=ope(1,l,r);
    removed.insert(l);
}
void v2(int x){
    cnt++;
    os[cnt]=ope(2,x,0);
    removed.insert(x);
}
int num_fu=0;
int max_fu_pos=0,max_fu=0;
int tr[200050],cnt2=0;
void rm(int x){
    cnt2++;
    tr[cnt2]=x;
}
bool cmp(int x,int y){
    return x<y;
}
int main(){
    scanf("%d",&n);
    for(int i(1);i<=n;i++){
        scanf("%d",&a[i]);
        if(a[i]<0){
            num_fu++;
            if(max_fu==0)max_fu=a[i],max_fu_pos=i;
            else if(a[i]>max_fu)max_fu=a[i],max_fu_pos=i;
        }
        if(a[i]==0)rm(i);
    }
    if(num_fu%2){
        rm(max_fu_pos);
    }
    if(cnt2){
        sort(tr+1,tr+1+cnt2,cmp);
        for(int i(1);i<=cnt2-1;i++)v1(tr[i],tr[i+1]);
        v2(tr[cnt2]);
    }
    
    int lst=0;
    for(int i(1);i<=n;i++){
        if(removed.count(i))continue;
        if(lst==0){
            lst=i;
            //printf("ans init=%d\n",ans);
            continue;
        }
        v1(lst,i);
        //printf("ans*=%d\n",a[i]);
        lst=i;
    }
    bool flag=1;
    for(int i(1);i<=n;i++){
        if(a[i]!=0 and !removed.count(i)){
            flag=0;
            break;
        }
    }
    //printf("ans=%d\n",ans);
    for(int i(1);i<=cnt;i++){
        if(os[i].l==0)continue;
        if(os[i].c==1)printf("%d %d %d\n",os[i].c,os[i].l,os[i].r);
        else if(!flag)printf("%d %d\n",os[i].c,os[i].l);
    }
    return 0;
}

转载于:https://www.cnblogs.com/soul-M/p/9666952.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值