UVALIVE 7505 dp

题意:

题目链接:https://vjudge.net/problem/UVALive-7505
2015年EC-final的F题
这题是半年前做的,当时想了非常久,最近又拿出来回味一下。
N只蚂蚁在一个水平放置的长度为N+1的杆子上,其中第i只蚂蚁就在据左端点i的位置上,而且重量也为i,每只蚂蚁会随机等概率地选择移动方向,向左或者向右,移动过程中到达杆子的端点就会折返。蚂蚁的移动速度都是1,两只蚂蚁相遇的时候,重量大的蚂蚁会吃掉重量小的蚂蚁,如果两只蚂蚁重量相同,则左边的会吃掉右边的,获胜的蚂蚁会加上吃掉蚂蚁的重量,移动方向不变。问,第k只蚂蚁成为最后幸存的蚂蚁有多少种情况。

思路:

刚开始看这题毫无思路,但是仔细观察可以发现这种模型的一些现象。
1.每只蚂蚁的移动速度都一样,所以如果第i只蚂蚁往左边走,它一定会先和左边的i-1只蚂蚁先相遇,这i只蚂蚁竞争之后无论谁获胜,都会形成一只重量为(i+1)*i/2的蚂蚁。
2.不在端点的任何一只蚂蚁,都一定要选择先往左边走,否则不可能活下来。

通过以上两点的分析,第k只蚂蚁向左边走,找到这只蚂蚁左边的第一个也向左边走的蚂蚁i,那么左边的i只蚂蚁会先行成一个重量为(i+1)/2的蚂蚁,而蚂蚁i+1到k-1都是向右走的,所以i会变成一个重量为(k-1-i)(i+k)/2的蚂蚁,要想蚂蚁k活下来,就要使(k-1-i)*(i+k)/2 > (i+1)/2,我们可以通过二分预处理出这个位置pos。pos+1到k-1的所有蚂蚁都往右走,这样,pos以及之前的蚂蚁无论选择什么方向,都不会影响k吃掉他们。

这样蚂蚁k就会吃掉左边的所有蚂蚁,形成一个重量为k*(k+1)/2的蚂蚁。对于右边的情况,我们就需要dp来处理。
假设dp[i]表示到第i个蚂蚁,k的右边有多少种情况让k死掉,这样最后的答案就是(2^(n-k)-dp[n])*2^pos。
对于dp[i],首先如果到前i-1只蚂蚁k就死了,那么第i只蚂蚁无论选左选右,都不会影响,所以dp[i] += dp[i-1] * 2。
另外,还有一种情况是第i只蚂蚁的参与导致k死,如果想要这种情况发生,就必须要利用上面所说的二分方法求出的关于i的pos,这样pos+1到i-1都往右边走,保证这样形成的蚂蚁一定会吃掉前面pos只,这一段情况只有一种,为了保证这两种情况不重复,就要保证在k到pos这一段k不能死,情况数为(2^(pos-k)-dp[pos])
综上所述,状态转移方程为dp[i] = dp[i-1]*2 + (2^(pos-k)-dp[pos])
另外要注意对于第n个蚂蚁,无论向左还是向右对于第二种情况不影响结果,所以要乘2,很关键。

代码:

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long ll;

const ll MOD = 1e9 + 7;
const int MAXN = 1e6;

int pos[MAXN + 10];
ll two[MAXN + 10], t[MAXN + 10], dp[MAXN + 10];

void init() {
    for (int i = 1; i <= MAXN; i++)
        t[i] = (ll)i * i * 2 + i * 2;
    pos[1] = 1, pos[2] = 1;
    for (int i = 2; i <= MAXN; i++)
        pos[i] = lower_bound(t + 1, t + 1 + i, (ll) i * i + i) - t - 1;
    two[0] = 1;
    for (int i = 1; i <= MAXN; i++)
        two[i] = two[i - 1] * 2 % MOD;
}

int main() {
    //freopen("in", "r", stdin);
    init();
    int T, cs = 0;
    scanf("%d", &T);
    while (T--) {
        int n, k;
        scanf("%d%d", &n, &k);
        if (k == 1) {
            printf("Case #%d: 0\n", ++cs);
            continue;
        }
        if (k == n) {
            printf("Case #%d: %lld\n", ++cs, two[pos[k]] * 2 % MOD);
            continue;
        }
        dp[k] = 0;
        for (int i = k + 1; i < n; i++) {
            dp[i] = dp[i - 1] * 2 % MOD;
            if (pos[i] >= k)
                dp[i] = (dp[i] + (two[pos[i] - k] - dp[pos[i]] + MOD) % MOD) % MOD;
        }
        dp[n] = dp[n - 1] * 2 % MOD;
        if (pos[n] >= k)
            dp[n] = (dp[n] + 2 * (two[pos[n] - k] - dp[pos[n]] + MOD) % MOD) % MOD;
        ll ans = (two[n - k] - dp[n] + MOD) % MOD * two[pos[k]] % MOD;
        printf("Case #%d: %lld\n", ++cs, ans);
    }
    return 0;
}
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值