【NOIP2017提高A组集训10.21】Dark

Description:

LichKing 希望收集邪恶的黑暗力量,并依靠它称霸世界。
世间的黑暗力量被描述成一个长度为N 的非负整数序列{Ai},每次它可以选择这个序列中的两个相邻的正整数,让他们的值同时减一并获得一点邪恶力量,直到不存在满足条件的数。
然而你不希望他能够得逞,所以你会使得他收集的能量尽可能少。
1<=n<=1e5,1<= ni=1ai <=5e6

题解:

考场去撸第三题了,这题没有仔细想,其实这题是比第三题水的。

第一种方法:
1<= ni=1ai <=5e6这个提醒太明显了。

所以可以随手设出 fi,j,0/1 表示第i个剩下j,第i-1个是否为0。

fi,j,0=min(fi1,a[i]j,0,fi1,a[i]j,1)+a[i]j(a[i]j<=a[i1])
fi,j,1=min(fi1,k,0)+a[i]j(a[i1]>=k>=a[i]j)

Ans=min(fn,k,0(a[n]>=k>=0),fn,0,1)

第二种方法:
可以注意到最后的序列每两个数一定为0,即连续四个数会出现两个0。
所以设 fi 为a[i]=0且1-i合法最小代价.
fi=min()
1.fi1+a[i]
2.fi2+a[i]
3.fi3+max(a[i1],a[i])

i-4以下的转移要确定把4个数弄合法的最小代价,由于连续四个数以上会出现至少2个0,我只用由第二个0转移过来,而不用从第一个0转移过来,因为第一个0会转移到第二个0去,由第一个0转移过来的情况已经在前3种转移中出现过了,所以i-4以下的转移是不必要的。
最后 Ans=min(fn,fn1)

于是得到了一个虐爆Tj的O(n)做法。

这个做法的关键在于最终形成序列的性质,算是比较好的思路了。

Code(1):

#include<cstdio>
#define fo(i, x, y) for(int i = x; i <= y; i ++)
#define fd(i, x, y) for(int i = x; i >= y; i --)
#define min(a, b) ((a) < (b) ? (a) : (b))
using namespace std;

const int N = 1e5 + 5, M = 5e6 + 5;

int n, a[N], o, f[2][M][2];

int main() {
    freopen("dark.in", "r", stdin);
    freopen("dark.out", "w", stdout);
    scanf("%d", &n);
    fo(i, 1, n) scanf("%d", &a[i]);
    fo(i, 1, n) {
        o = !o;
        fo(j, 0, a[i]) {
            if(a[i] - j <= a[i - 1])
                f[o][j][0] = min(f[!o][a[i] - j][0], f[!o][a[i] - j][1]) + a[i] - j; else
                f[o][j][0] = 1e9;
        }
        int mi = 1e9;
        fo(j, a[i] + 1, a[i - 1]) mi = min(mi, f[!o][j][0]);
        fo(j, 0, a[i]) {
            if(a[i] - j <= a[i - 1]) mi = min(mi, f[!o][a[i] - j][0]);
            f[o][j][1] = mi + a[i] - j;
        }
    }
    int ans = f[o][0][1];
    fo(i, 0, a[n]) ans = min(ans, f[o][i][0]);
    printf("%d", ans);
}

Code(2):

#include<cstdio>
#define fo(i, x, y) for(int i = x; i <= y; i ++)
#define min(a, b) ((a) < (b) ? (a) : (b))
#define max(a, b) ((a) > (b) ? (a) : (b))
using namespace std;

int n, a[100005], f[100005];

int main() {
    freopen("dark.in", "r", stdin);
    freopen("dark.out", "w", stdout);
    scanf("%d", &n);
    fo(i, 1, n) scanf("%d", &a[i]);
    f[0] = 0;
    fo(i, 1, n) {
        f[i] = f[i - 1] + a[i];
        if(i >= 2) f[i] = min(f[i], f[i - 2] + a[i]);
        if(i >= 3) f[i] = min(f[i], f[i - 3] + max(a[i], a[i - 1]));
    }
    printf("%d", min(f[n], f[n - 1]));
}
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值