第一次出题,纪念一下
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
1,2,3⋯⋯i。
x
j
j
xjj
xjj 手上的
n
n
n 颗骰子的面数可能并不相同,他想知道掷出
n
n
n 颗骰子的所有情况中,
n
n
n 颗骰子的点数之和出现最多次数是几点。
如果存在多个点数之和出现次数相同的情况,则按点数之和从小到大顺序输出。(保证出现次数不超过 2 63 − 1 2^{63}-1 263−1)
输入格式
第一行输入一个整数 T ( 1 ≤ T ≤ 10 ) T(1\leq T \leq 10) T(1≤T≤10) 。
对于每一组数据,第一行输入一个整数 n ( 1 ≤ n ≤ 10 ) n(1\leq n \leq 10) n(1≤n≤10) 。
第二行输入 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,a2⋯⋯an(4≤ai≤10000) 分别表示 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[i−1][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;
}