2017暑期集训Day 11 背包

A - Bottles

[Problem]

有n个苏打水的瓶子,每瓶中装有一定量苏打水,给你每瓶的体积,现在像把这些苏打水装到尽量少的瓶子中,并且移动尽可能少的苏打水。

[Solution]

f[i]代表凑齐i体积需要瓶子的最小数量,g[i]代表盛有的苏打水体积,在满足f[i]尽可能小的情况下,g[i]最大

[Code]

#include<cstdio>
#include<iostream>
#include<cstring>
using namespace std;
const int N = 100000 + 500;
const int inf = 0xfffffff;
int n, p, k;
long long  g[N], f[N], remain[N], v[N];
int main()
{
    scanf("%d", &n);
    for(int i = 1; i <= n; i++)
        scanf("%d", &remain[i]);
    for(int i = 1; i <= n; i++)
        scanf("%d", &v[i]);
    for(int i = 0; i <= 10000; i++)
    {
        g[i] = 0;
        f[i] = inf;
    }
    f[0] = 0;
    for(int i = 1; i <= n; i++)
        for(int j = 10000; j >= v[i]; j--)
    {
        int res = j - v[i];
        if (f[res] + 1 < f[j] || (f[res] + 1 == f[j] && g[res] + remain[i] > g[j]))
        {
            f[j] = f[res] + 1;
            g[j] = g[res] + remain[i];
        }
    }
    long long sum = 0;
    for(int i = 1; i <= n; i++)
        sum += remain[i];
    int ans = inf, tot = 0;
    for(int i = sum; i <= 10000; i++)
    {
        if (f[i] < ans || (f[i] == ans && g[i] > tot) )
        {
            ans = f[i];
            tot = g[i];
        }
    }
    printf("%d %d", ans, sum - tot);
    return 0;
}

B - Sonya and Problem Wihtout a Legend - Bottles

[Problem]

给定一个序列,询问将其变成单调增加序列的花费

[Solution]

上述问题是这个问题的变式:

给定一个序列,将其变成单调不下降序列的花费。

先说一个事实:目标序列中所有出现的数都在原数列出现过。

下面简单说一下证明,首先我们对于第k个数,即a[k],如果我们选择增加这个数,首先考虑我们为什么非要增加呢?因为前面的数比a[k]大,那我们要增加到什么程度呢?增加到a[k-1]就好呀,因为在满足条件的情况下,a[k]越小, 对于以后的选择就越多,代价便越小。

如果我们选择减少这个数,因为后面的一些数太小,我们改动他后面的数代价太高,从而选择选择减少这个数,那减少到什么程度呢,减少到a[k+1]就好呀

总结一下就是,我们对于更改a[k],原因变式a[k]有一些约束条件,而这个条件的边界便是原系列的值,我们本着贪心的思想,在满足条件的情况下尽可能产生较小的花费

这样我们用f[i][j]代表把前i个数排成单调不下降序列,其a[i]调整到原数列的第j小数,

f[i][j] min(f[i -1 ][k]) + cost(a[i] - > b[j]) (1 <= k <= j)
我们每次转移的时候保留一个最小值即可,实现O(1)的转移

下面回到正题:

此题是单调序列呀,这怎么搞?

a[i] < a[i + 1] <=> a[i] <= a[i + 1] - 1 <=> a[i] - i <= a[i + 1] - (i + 1) <=> b[i] <= b[i + 1]

构造b数列,问题转化为上面的那个问题

[Code]

#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N = 3000 + 500;
int n, p, k;
int a[N], b[N];
long long f[N][N], g[N][N];
int main()
{
    scanf("%d", &n);
    for(int i = 1; i <= n; i++)
        {
            scanf("%d", &a[i]);
            a[i] -= i;
            b[i] = a[i];
        }
    sort(b + 1, b + n + 1);
    memset(f, 110, sizeof(f));
    memset(g, 110, sizeof(g));
    for(int i = 1; i <= n; i++)
        {
            f[1][i] = abs(a[1] - b[i]);
            g[1][i] = min(g[1][i - 1], f[1][i]);
        }
    for(int i = 2; i <= n; i++)
        for(int j = 1; j <= n; j++)
        {
            f[i][j] = g[i - 1][j] + abs(a[i] - b[j]);
            g[i][j] = min(g[i][j -1], f[i][j]);
        }
    long long ans = g[n][1];
    for(int i = 1; i <= n ; i++)
        ans = min(ans, f[n][i]);
    cout<<ans;
    return 0;
}

C - Couple Cover

[Problem]

给定n(1e6)个数,每次取出 (i, j), 询问满足a[i] * a[j] >= p 的对数有多少? 共用3e6组询问, 读入数据保证小于等于3e6

[Solution]

很玄学的复杂度,M^1.5竟然不超时。。。。

[Code]

#include<cstdio>
#include<iostream>
#include<cstring>
using namespace std;
const int N = 3000000 + 500;
#define inf 3000000
int n, m, p, k, top;
long long  num[N], f[N];
int main()
{
    scanf("%d", &n);
    for(int i = 1; i <= n; i++)
    {
        int x;
        scanf("%d", &x);
        num[x]++;
    }
    for(int i = 1; i <= inf; i++)
        if (num[i])
          for(int j = 1; j <= i; j++)
             if (num[j])
          {
              if (i * j > inf)
                break;
              if (i == j)
                f[i * j] += num[i] * (num[i] - 1);
              else
                f[i * j] += num[i] * num[j] * 2;
          }
    for(int i = 2; i <= inf; i++)
        f[i] = f[i] + f[i - 1];
    scanf("%d", &m);
    long long ans = 1LL * n * (n - 1);
    while(m--)
    {
        int x;
        scanf("%d", &x);
        x--;
        printf("%I64d\n",ans - f[x]);
    }
    return 0;
}

D Boredom

[Solution]

很裸的递推

[Code]

#include<cstdio>
#include<iostream>
#include<cstring>
using namespace std;
const int N = 100000 + 500;
int n, p, k;
long long  num[N], f[N];
int main()
{
    //freopen("b.in", "r", stdin);
    scanf("%d", &n);
    for(int i = 1; i <= n; i++)
    {
        int x;
        scanf("%d", &x);
        num[x]++;
    }
    f[0] = 0;
    f[1] = num[1];
    long long  ans = 0;
    for(int i = 1; i <= 100000 + 1; i++)
    {
        f[i] = max(f[i - 1], f[i - 2] + num[i] * i);
        ans = max(ans, f[i]);
    }
    cout<<ans;
    return 0;
}

[Summary]

这次的题目比较少,恐怕自己现在就dp的bug率很低吧。。。。。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值