xjj 的多面骰子

第一次出题,纪念一下

x j j xjj xjj 的多面骰子

时间限制:1000ms

内存限制:512 mb

226 226 226 小分队平时非常喜欢投骰子来决定谁去拿外卖 ,他们的规则是谁扔得最小谁去,这时候 x j j xjj xjj 就很不讲武德,啪的一下就拿出来他收藏的多面骰子,很快啊。

现在 x j j xjj xjj 手上有 n n n 颗多面骰子,多面骰子不是常见的 6 6 6 面骰子,而是 33 33 33 面骰子 、 100 100 100 面骰子、 1000 1000 1000 面骰子 ⋯ ⋯ 10000 \cdots\cdots10000 10000 面骰子。 i i i 面骰子每个面上的点数分别是 1 , 2 , 3 ⋯ ⋯ i 1, 2, 3\cdots\cdots i 123i
x j j xjj xjj 手上的 n n n 颗骰子的面数可能并不相同,他想知道掷出 n n n 颗骰子的所有情况中, n n n 颗骰子的点数之和出现最多次数是几点。

如果存在多个点数之和出现次数相同的情况,则按点数之和从小到大顺序输出。(保证出现次数不超过 2 63 − 1 2^{63}-1 2631)

输入格式

第一行输入一个整数 T ( 1 ≤ T ≤ 10 ) T(1\leq T \leq 10) T(1T10)

对于每一组数据,第一行输入一个整数 n ( 1 ≤ n ≤ 10 ) n(1\leq n \leq 10) n(1n10)

第二行输入 n n n 个数字, a 1 , a 2 ⋯ ⋯ a n ( 4 ≤ a i ≤ 10000 ) a_1,a_2\cdots \cdots a_n(4\leq a_i\leq 10000) a1,a2an(4ai10000) 分别表示 n n n 颗骰子各自的面数。

输出格式

对于每一组数据输出若干行,每行输出一个整数。

样例输入

1
2
4 5

样例输出

5
6

提示

骰子投出来有以下二十种情况:

1 + 1 = 2 1 + 2 = 3 1 + 3 = 4 1 + 4 = 5 1 + 5 = 6 1+1=2\quad 1+2=3\quad 1+3=4\quad 1+4=5\quad 1+5=6 1+1=21+2=31+3=41+4=51+5=6

2 + 1 = 3 2 + 2 = 4 2 + 3 = 5 2 + 4 = 6 2 + 5 = 7 2+1=3\quad 2+2=4\quad 2+3=5\quad 2+4=6\quad 2+5=7 2+1=32+2=42+3=52+4=62+5=7

3 + 1 = 4 3 + 2 = 5 3 + 3 = 6 3 + 4 = 7 3 + 5 = 8 3+1=4\quad 3+2=5\quad 3+3=6\quad 3+4=7\quad 3+5=8 3+1=43+2=53+3=63+4=73+5=8

4 + 1 = 5 4 + 2 = 6 4 + 3 = 7 4 + 4 = 8 4 + 5 = 9 4+1=5\quad 4+2=6\quad 4+3=7\quad 4+4=8\quad 4+5=9 4+1=54+2=64+3=74+4=84+5=9

可以看出 5 5 5 6 6 6 的出现次数最多且都是 4 4 4 次。




解题思路

对于单独 1 1 1 x x x 面骰子,能投出 [ 1 , x ] [1,x] [1,x]

在此基础上第二个 y y y 面骰子就是在这个区间的基础上再加 [ 1 , y ] [1,y] [1,y]

 for(int i = 1;i <= x;i++) {
 	for(int j = 1;j <= y;j++) {
 		ans[i + j]++;
 	}
 }

此时这个可能的值域区间就变成 [ 2 , x + y ] [2,x+y] [2,x+y]

再加上第三个 z z z 面骰子就是在这个区间的基础上再加 [ 1 , z ] [1,z] [1,z]

 for(int i = 2;i <= x + y;i++) {
 	for(int j = 1;j <= z;j++) {
 		ans[i + j]++;
 	}
 }

此时这个可能的值域区间就变成 [ 3 , x + y + z ] [3,x+y+z] [3,x+y+z]

以此类推 已经投了 n n n 个骰子的值域区间为 [ n , ∑ i = 1 n a [ i ] ] [n,\sum_{i=1}^{n}a[i]] [n,i=1na[i]],也就是 n n n 个骰子都为 1 1 1 组成最小值, n n n 个骰子都为最大值组成整体最大值。

n n n 为骰子个数 m m m 为骰子的面数

long long ans[n][n * m];  // 二维数组 第一维表示骰子个数 第二维可能出现的值
int maxn = 0;  // 骰子投出的最大可能值   类似上面的 (x + y + z)
long long maxm = 0;  // 出现次数
for(int i = 1;i <= n;i++) {
	for(int j = i - 1;j <= maxn;j++) {  // maxn为到当前第i个骰子可能的最大值
		for(int k = 1;k <= a[i];k++) {  // 遍历这个骰子的a[i]种可能,加上贡献
			ans[i][j + k] += ans[i - 1][j];
			if(ans[i][j + k] > maxm) maxm = ans[i][j + k]; // 更新出现次数的最大值
		}
	}
	maxn += a[i];     // 取和
}

然后可以看出内部 k k k 遍历 a [ i ] a[i] a[i] 时 增加的值都是 a n s [ i − 1 ] [ j ] ans[i - 1][j] ans[i1][j] ,因此用差分维护从而减少一维 m m m 的时间复杂度。

暴力做法时间复杂度 O ( n 2 m 2 ) T L E O(n^{2}m^{2})\quad TLE O(n2m2)TLE

差分优化时间复杂度 O ( n 2 m ) A C O(n^{2}m)\quad AC O(n2m)AC

s t d T L E O ( n 2 m 2 ) std\quad TLE\quad O(n^2m^2) stdTLEO(n2m2)

#include<stdio.h>
long long ans[15][100005];
int a[15], n, T;

int main() {
    scanf("%d", &T);
    while(T--) {
        scanf("%d", &n);
        for(int i = 0;i <= n;i++) {
            for(int j = 0;j < 100005;j++) {
                ans[i][j] = 0;
            }
        }
        for(int i = 1;i <= n;i++) scanf("%d", &a[i]);
        long long maxn = 0, maxm = 0;
        ans[0][0] = 1;
        for(int i = 1;i <= n;i++) {
            for(int j = i - 1;j <= maxn;j++) {
                for(int k = 1;k <= a[i];k++) {
                    ans[i][j + k] += ans[i - 1][j];
                    if(ans[i][j + k] > maxm) maxm = ans[i][j + k];
                }
            }
            maxn += a[i];
        }
        for(int i = n;i <= maxn;i++) {
            if(ans[n][i] == maxm) {
                printf("%d\n", i);
            }
        }
    }
    return 0;
}

s t d A C O ( n 2 m ) std\quad AC\quad O(n^2m) stdACO(n2m) 差分优化

#include<stdio.h>
long long ans[15][100005];
long long c[100005];
int a[15], n, T;

int main() {
    scanf("%d", &T);
    while(T--) {
        scanf("%d", &n);
        for(int i = 0;i <= n;i++) {
            for(int j = 0;j < 100005;j++) {
                ans[i][j] = 0;
            }
        }
        for(int i = 1;i <= n;i++) scanf("%d", &a[i]);
        long long maxn = 0;
        ans[0][0] = 1;
        for(int i = 1;i <= n;i++) {
            for(int j = i - 1;j <= maxn;j++) {
                c[j + 1] += ans[i - 1][j];
                c[j + a[i] + 1] -= ans[i - 1][j];
            }
            maxn += a[i];
            for(int j = i;j <= maxn;j++) {
                ans[i][j] = ans[i][j - 1] + c[j];
                c[j] = 0;
            }
            c[maxn + 1] = 0;
        }
        long long maxm = 0;
        for(int i = n;i <= maxn;i++) {
            if(ans[n][i] > maxm) maxm = ans[n][i];
        }
        for(int i = n;i <= maxn;i++) {
            if(ans[n][i] == maxm) printf("%d\n", i);
        }
    }
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

he_69

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值