POJ 3046 Ant Counting

POJ 3046题目大意如下:

指定蚂蚁家族有T个,每个家族的蚂蚁数量最多为100只,最少当然为1了,总共有A只蚂蚁,每只蚂蚁唯一的区别就是它所对应的家族编号,相同编号的蚂蚁看成没有差别。现在将这些蚂蚁分成不同的集合,指定每次分割是的集合大小(每次分割出来的集合大小都是统一的)。现在指定集合大小的范围,求出对于不同集合大小的分割方法的总和。

这是一个递推问题当然也是DP,所以重要的不是自己去模拟分类,而是找出一个合理的关系式,再加上合理的初始化,就可以解决问题了。如果有必要,可以在空间使用率上再作优化。这里我们这样定义DP表达式:dp[i+1][j]表示使用前i个家庭的蚂蚁分解成集合长度为j的所有方法数。那么就有以下关系:

dp[i + 1][j] = Sigma(0, min{num[i + 1], j})dp[i][j - k] = Sigma(0, min(num[i + 1], j - 1})dp[i + 1][j - 1 - k] + dp[i][j] = dp[i + 1][j - 1] + dp[i][j];

这里还要注意的就是还有一项要分情况进行减法:如果j - 1 - num[i + 1] >= 0。那么对于最开始的式子,有dp[i][j - 1 - num[i + 1]]是多加了的,所以要减去。同时这里还要注意的是会溢出,题目也只要求取最后六位整数,所以要进行取模。其实取模会花比较多的时间,所以还是建议用循环减代替取模,这样会好点。

然后就是空间的优化了,因为题目给的数据太大,数组的花销有点大,但是这里的第一维其实只需要2就可以了因为总是在i+1和i之间转换。说到这里,结果已经出来了,但遗憾的是,代码居然wrong 了,看了好久没看出什么端倪来,和网上其他人对比了一下,我感觉一样。下面虽然会贴上没有AC的代码,然而今天这篇就当作算法说明吧,可以参考修改,主要算法还是没有错的,代码如下:

#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cstring>

using namespace std;
const int maxn1 = 1000;
const int maxn2 = 100000;
int ants[maxn1 + 2];
int dp[2][maxn2 + 1];
int kind[maxn1 + 2] = {0};

int T, A, S, B;

void solve() {
    memset(dp, 0, sizeof(dp));
    for (int i = 1; i <= T; i++) kind[i] = kind[i - 1] + ants[i];
    dp[0][0] = dp[1][0] = 1;
    
    for (int i = 0; i < T; i++) {
        for (int j = 1; j <= kind[i + 1] && j <= B; j++) {
            if (j - 1 - ants[i + 1] >= 0)
                dp[(i + 1) % 2][j] = (dp[(i + 1) % 2][j - 1] + dp[i % 2][j] - dp[i % 2][j - 1 - ants[i + 1]]) % 1000000;
            else dp[(i + 1) % 2][j] = (dp[(i + 1) % 2][j - 1] + dp[i % 2][j]) % 1000000;
        }
    }
    int t = T % 2, ans = 0;
    for (int i = S; i <= B; i++) ans = (ans + dp[t][i]) % 1000000;
    printf("%d\n", ans);
}

int main(int argc, const char * argv[]) {
    // insert code here...
    int data;
    scanf("%d%d%d%d", &T, &A, &S, &B);
    for (int i = 0; i < A; i++) {
        scanf("%d", &data);
        ants[data]++;
    }
    solve();
    return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值