Codeforces #278 Div 1 简要题解

比赛总结

这次div1打得还是很惨,2个小时里一直在wa A题,wa了七八次才ac,罚时太惨。。。
手速真的非常重要啊

A. Fight the Monster

题目链接

http://codeforces.com/contest/487/problem/A

题目大意

奥特曼打小怪兽,每局开始时,双方都会掉血,各自掉 max(0,ATKYDEFM),max(0,ATKMDEFY) ,初始时奥特曼血量是 HPY ,小怪兽血量是 HPM 。两个家伙谁先没血谁先输。但是奥特曼可以开挂,可以花 a 元钱让ATKY加1,花 d 元钱让DEFY加1,花 h 元钱让HPY加1,问最少花多少钱,他才能打败小怪兽

思路

刚开始我很sb地去写了个二分,结果数据太硬,如果直接暴力枚举奥特曼的三个参数的大小的话,要么范围枚举小了会wa,要么范围枚举大了又会TLE

其实没必要二分,也没必要把奥特曼的三个参数都枚举,只需要枚举 DEFY ATKY ,然后计算出最少所需的 HPY ,然后计算出这样的方案要花多少钱,更新答案就好了。

代码

#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <algorithm>

#define INF 0x7fffffffffffffff

using namespace std;

typedef long long int LL;

LL HPy,ATKy,DEFy,HPm,ATKm,DEFm,h,a,d;
LL ans=INF;

inline void check()
{
        for(LL aa=0;aa<=1000;aa++)
            for(LL dd=0;dd<=1000;dd++)
            {
                LL hh;
                ATKy+=aa;
                DEFy+=dd;

                LL lifey,lifem;
                if(max((LL)0,ATKy-DEFm)==(LL)0)
                {
                    ATKy-=aa;
                    DEFy-=dd;
                    continue;
                }
                else
                {
                    lifem=HPm/max((LL)0,ATKy-DEFm);
                    if(HPm%max((LL)0,ATKy-DEFm)) lifem++;
                }
                if(max((LL)0,ATKm-DEFy)==(LL)0) hh=(LL)0;
                else
                {
                    lifey=1+lifem*max((LL)0,ATKm-DEFy);
                    if(HPy<lifey) hh=lifey-HPy;
                    else hh=0;
                }
                ATKy-=aa;
                DEFy-=dd;
                if(hh*h+aa*a+dd*d<ans) ans=hh*h+aa*a+dd*d;
            }
}

int main()
{
    scanf("%I64d%I64d%I64d%I64d%I64d%I64d%I64d%I64d%I64d",&HPy,&ATKy,&DEFy,&HPm,&ATKm,&DEFm,&h,&a,&d);
    check();
    printf("%I64d\n",ans);
    return 0;
}

B. Strip

题目链接

http://codeforces.com/contest/487/problem/B

题目大意

给你一个序列 a ,要你将它分成连续的若干段,每一段长度大于等于l,最大值减最小值小于等于 s 。问最少分成多少段。

思路

膜用线段树干掉此题的各位大爷。。。

蒟蒻只会用set乱搞。。。

首先我们求出L[i]=分出的一段子序列,右端点为 i ,左端点最远的位置。显然L[i]具有单调性,即 i<j,L[i]L[j] ,这个大家自己脑补下就能明白。于是我们可以扫一遍整个序列,从小到大枚举序列的下标 i ,维护一个multiset,其中的所有元素的最大值减最小值小于等于s,并维护一个变量 now ,代表这个multiset里维护的元素是属于区间 [now,i1] 的,如果 a[i] 加入后,集合里最大值减最小值大于 s ,则需要在multiset里删除掉a[now]a[now],如此反复直到最大值减最小值小于等于 s 为止,此时L[i]=now

然后我们再求出 f[i]= 前i个数字的最少划分次数。裸的做法是 O(n2) 的,但是我们这里可以再次用multiset将其优化到 O(nlogn) ,还是用到了 L[i] 的单调性。我们可以扫一遍序列 a ,从小到大枚举序列的下标i,在集合里维护 [L[i],il] 段的 f[] 值,每次从中取最小值做DP即可。若取不出一个 f 值的话,就赋f[i]=inf,表明这个状态无效了

代码

#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <algorithm>
#include <set>

#define MAXN 110000
#define INF 0x3f3f3f3f

using namespace std;

//int maxv[MAXN<<2],minv[MAXN<<2];
int a[MAXN],n;

int f[MAXN],L[MAXN]; //L[i]=右端点为i,左端点最远划分的位置

multiset<int>bst;

int main()
{
    int l,s;
    scanf("%d%d%d",&n,&s,&l);
    for(int i=1;i<=n;i++)
        scanf("%d",&a[i]);
    int now=1;
    for(int i=1;i<=n;i++)
    {
        while(!bst.empty()) //bst里保存的是[now,i-1]里的点
        {
            int minv=*bst.begin(),maxv=*(--bst.end()); //[now,i-1]里的最大值和最小值
            minv=min(minv,a[i]),maxv=max(maxv,a[i]);
            if(maxv-minv<=s) break;
            bst.erase(bst.lower_bound(a[now]));
            now++;
        }
        L[i]=now;
        bst.insert(a[i]);
    }
    bst.clear(); //之后multiset里保存的是每个位置上的f值
    now=0; //multiset里保存的是[now,i-1]的f值
    for(int i=1;i<=n;i++)
    {
        if(i-l>=0) bst.insert(f[i-l]);
        while(now<=i-l&&now<L[i]-1&&!bst.empty())
        {
            //cout<<now<<endl;
            bst.erase(bst.lower_bound(f[now]));
            now++;
        }
        if(bst.empty())
            f[i]=INF;
        else f[i]=*bst.begin()+1;
    }
    if(f[n]>=INF) printf("-1\n");
    else printf("%d\n",f[n]);
    return 0;
}

C. Prefix Product Sequence

题目链接

http://codeforces.com/contest/487/problem/C

题目大意

定义前缀和 pre[i]=ij=1a[j]modn ,要你构造序列 [1,2,...,n] 的一个排列序列 a ,使得a的前缀和组成的序列是 [0,1,2,...,n1] 的一个排列

思路

显然若 n 为合数的话(4除外),无论怎么构造,得到的前缀和序列肯定是有重复元素的,这和hash的模数不能取合数的道理是一样的。而由于4比较特殊,它是正整数里的第一个合数,因此没有这个问题,我们可以手玩出n=4的解

其他情况下,显然a[1]=1,a[n]=n,剩余的部分,我们这样构造: a[i]=irev[i1]modn ,这样的话,就能在mod n的意义下使第 i 项前缀积避免受到第i1及以前的元素的影响,保证每一项前缀积不一样

代码

#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <algorithm>

#define MAXN 110000

using namespace std;

typedef long long int LL;

LL a[MAXN],rev[MAXN];
int n;

bool isPrime(int x)
{
    for(int i=2;i*i<=x;i++)
        if(!(x%i)) //!!!!!!
            return false;
    return true;
}

int main()
{
    scanf("%d",&n);
    if(n==4)
    {
        printf("YES\n1\n3\n2\n4\n");
        return 0;
    }
    if(!isPrime(n))
    {
        printf("NO\n");
        return 0;
    }
    printf("YES\n");

    rev[1]=1;
    for(int i=2;i<=n;i++)
        rev[i]=rev[n%i]*rev[n%i]%n*i%n*(n/i)%n*(n/i)%n;
    a[1]=1,a[n]=n;
    for(int i=2;i<n;i++) a[i]=i*rev[i-1]%n;
    for(int i=1;i<=n;i++) printf("%I64d\n",a[i]);
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值