• Codeforces 920D Tanks


    题目链接

    题意

    (n) 个容积无限的水缸,初始时水量为(a_1,a_2,...,a_n),有一把容积为(k)的勺子,可以从一个水缸中舀水倒入另一个水缸中。问能否给出操作序列,使得最终某一个水缸中水的容量为(V).

    思路

    参考 - 粉兔.

    结论

    首先,如果(sum_{i=1}^{n}a_ilt V),显然不可行。

    否则,一旦(exists p_1,p_2,...,p_t,s.t.(sum_{i=1}^{t}a_{p_i})\%k==V\%k),则我们说,这件事是可行的。

    为什么呢?因为一旦可以取到同余的值,那么无论总量是多余还是不足都可以用勺子解决:多了,就往外舀;少了,就从其他缸((i.e.)(j)((j!=p_i(i=1,2,...,t))))中补进来。

    至于操作的具体细节,暂放一下。

    dp

    那么该怎么判断是否有上面的条件成立呢?

    (dp[i][j]) 表示前 (i) 个缸能否取到模数为 (j) 的值,特别的,

    [dp[i][j]=egin{cases}0,&cannot make itcr1,&can make it without a_icr2,&a_i needed to make itcrend{cases} ]

    (dp[n][V\%k]) 即表示前 (n) 个缸能否取到模数为 (k) 的值。

    构造

    现在有了这个结论和中间记录的 (dp) 值,该怎么推出步骤呢?

    注意到,上面的记录过程提供给了我们 (p_1,p_2,...,p_t),即必须全部取的缸;而其余的缸,不妨记为 (q_1,q_2,...,q_s)

    则可行操作如下:

    1. (p1,...,p_{t-1}) 缸中的水全部舀入 (p_t) 中;
    2. (q1,...,q_{s-1}) 缸中的水全部舀入 (q_s) 中;
    3. (p_t)(q_s) 之间进行多退少补。

    注意几种 特殊情况,比如 (t=n)(s=n) 的情况:

    (t=n) 即所有的缸都需要,最后总量肯定只会超出,不会不够,并且超出的部分必然是 (k) 的整倍数。
    此时,将所有缸中的水都舀到某一个缸中,再将超出的部分舀到另一个缸中;
    (这种情况可以与一般情况合在一起统一处理)。

    (s=n) 即所有的缸都不需要,这是什么回事呢?当 (k|V) 时就会发生这种情况,即所需要的容积恰好可以用若干勺舀出。
    此时,将所有缸中的水都舀到某一个缸中,再将需要的部分舀到另一个缸中。

    // 很佩服粉兔啦%%%

    Code

    #include <bits/stdc++.h>
    #define maxn 5010
    using namespace std;
    typedef long long LL;
    int a[maxn], b[maxn], dp[maxn][maxn];
    bool flag[maxn];
    int main() {
        int n, k, v, sum = 0;
        scanf("%d%d%d", &n, &k, &v);
        for (int i = 1; i <= n; ++i) {
            scanf("%d", &a[i]);
            sum += (b[i] = a[i]), a[i] %= k;
        }
    
        if (sum < v) { puts("NO"); return 0; }
        dp[0][0] = 1;
        for (int i = 1; i <= n; ++i) {
            for (int j = 0; j < k; ++j) {
                if (dp[i-1][j]) {
                    dp[i][j] = 1;
                    if (!dp[i][(j+a[i])%k]) dp[i][(j+a[i])%k] = 2;
                }
            }
        }
        int tar = v % k;
        if (!dp[n][tar]) { puts("NO"); return 0; }
        puts("YES");
        int ans = 0, fnl1 = -1, fnl2 = -1;
        for (int i = n; i >= 1; --i) {
            if (dp[i][tar]==2) {
                flag[i] = true, (tar += k-a[i]) %= k, ans += b[i];
                if (fnl1==-1) fnl1 = i;
            }
            else if (fnl2 == -1) fnl2 = i;
        }
        if (fnl1==-1) {
            for (int i = 1; i < n; ++i) if (b[i]) {
                printf("%d %d %d
    ", (b[i] + k-1)/k, i, n);
            }
            if (v/k) printf("%d %d %d
    ", v/k, n, 1);
            return 0;
        }
        assert((v-ans) % k == 0);
        int rem = v - ans;
        int S1 = b[fnl1], S2 = b[fnl2];
        for (int i = 1; i <= n; ++i) {
            if (i == fnl1 || i == fnl2 || !b[i]) continue;
            if (!flag[i]) printf("%d %d %d
    ", (b[i]+k-1)/k, i, fnl2), S2 += b[i];
            else printf("%d %d %d
    ", (b[i]+k-1)/k, i, fnl1), S1 += b[i];
        }
        assert((v-S1) % k == 0);
        int cnt = (v-S1) / k;
        if (cnt>0) printf("%d %d %d
    ", cnt, fnl2, fnl1);
        else if (cnt<0) printf("%d %d %d
    ", -cnt, fnl1, 1);
        return 0;
    }
    
    
  • 相关阅读:
    10003 Cutting Sticks(区间dp)
    Cocos2d-x init() 和 onEnter() 区别
    HDU1181【有向图的传递闭包】
    空间参考系统与WKT解析
    面试经典-分金条
    uvalive 3971
    lua学习:使用Lua处理游戏数据
    面试经典--两个房间 每间房间三盏灯
    浙江大学PAT上机题解析之2-11. 两个有序链表序列的合并
    顺序队列之C++实现
  • 原文地址:https://www.cnblogs.com/kkkkahlua/p/8413054.html
Copyright © 2020-2023  润新知