(这四个题目难度并列,相差不大)
T1 DiceGames
题目
描述
你有
n
n
n个骰子,并打算用这些骰子发明一个新游戏。为了发明游戏,你需要首先知道扔骰子有多少种结果。我们定义骰子上显示的值集合为一种结果,而不考虑顺序。因此,
{
1
,
1
,
2
}
\{1,1,2\}
{1,1,2},
{
1
,
2
,
1
}
\{1,2,1\}
{1,2,1}和
{
2
,
1
,
1
}
\{2,1,1\}
{2,1,1}都被视为相同的扔骰子结果,而
{
1
,
1
,
2
}
\{1,1,2\}
{1,1,2},
{
1
,
2
,
2
}
\{1,2,2\}
{1,2,2}和
{
1
,
1
,
3
}
\{ 1,1,3\}
{1,1,3}都是不同的结果。请注意,即使两个骰子可能具有不同数量的面的数量,但在这个游戏中,我们只考虑骰子显示的值。给定一个int
数组
s
i
d
e
s
sides
sides,数组中的第
i
i
i个数
s
i
d
e
s
i
sides_i
sidesi表示第
i
i
i个骰子有多少个面。一个有
n
n
n面的骰子能够显示
[
1
,
n
]
[1,n]
[1,n]的数。求一共有多少种扔骰子结果。
数据范围
骰子的数量
n
n
n满足
1
≤
n
≤
32
1≤n≤32
1≤n≤32。
每个骰子的面数
s
i
d
e
s
i
sides_i
sidesi满足
1
≤
s
i
d
e
s
i
≤
32
1≤sides_i≤32
1≤sidesi≤32。
输入格式
第
1
1
1行:
1
1
1个整数
n
n
n
第
2
2
2行:
n
n
n个正整数
输出格式
第
1
1
1行:
1
1
1个整数,表示答案
输入样例 1
1 1 1
4
输出样例 1
4
输入样例 2
2
4 4
输出样例 2
10
分析
考试的时候想用背包(母函数)做,即统计每个点数出现的次数 c n t [ i ] cnt[i] cnt[i],最后从每个 c n t [ i ] cnt[i] cnt[i]中选一些,使其和为 n n n,写的时候就发现有问题(・`ω´・),但是没举出反例,其实反例很简单:
4
3 4 5 6
这样统计方案时会统计到1 5 5 6
等,但是能提供5
和6
的只有两个骰子,不可能出现3个5
或6
。
正解是先排序,然后DP时保证后来的不比先来的小,即可找到所有的方案且不重复,因为大的骰子一定可以投出小骰子的点数,但如果大骰子都不能投出,小骰子在后面就一定也不能投出这个点数。
就这样了,我太弱了= =
代码
#include<set>
#include<map>
#include<queue>
#include<cmath>
#include<cstdio>
#include<vector>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<functional>
using namespace std;
int read(){
int x=0,f=1;char c=getchar();
while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
while(c>='0'&&c<='9')x=(x<<3)+(x<<1)+(c^48),c=getchar();
return x*f;
}
#define MAXN 32
int N;
int A[MAXN+5];
long long dp[MAXN+5][MAXN+5];
//前i个骰子 最后一个投了j点的方案数
int main(){
freopen("dicegames.in" ,"r", stdin);
freopen("dicegames.out","w",stdout);
N=read();
for(int i=1;i<=N;i++)
A[i]=read();
sort(A+1,A+N+1);
dp[0][0]=1;
for(int i=1;i<=N;i++)
for(int j=1;j<=A[i];j++)
for(int k=0;k<=j;k++)
dp[i][j]+=dp[i-1][k];
long long Ans=0;
for(int i=1;i<=MAXN;i++)
Ans+=dp[N][i];
printf("%lld",Ans);
}
T2 WordNumber
题目
描述
用字母表中的前
α
\alpha
α个小写字母构造单词,把所有能构造出的单词按照以下排序规则排序:
- 如果两个单词长度不同,那么长度更短的会排在前面
- 如果两个单词长度相同,那么字典序更小的会排在前面
例如,如果我们仅使用前
3
3
3个字母a
,b
和c
,那么这些单词就会被这样排序:a
、b
、c
、aa
、ab
、ac
、ba
、bb
……其中a
是第
1
1
1个单词,bb
是第
8
8
8个单词。求按照这个排序规则,第
n
n
n个单词是什么。
数据范围
可以使用的小写字母的种类数
α
\alpha
α满足
2
≤
α
≤
26
2≤\alpha≤26
2≤α≤26。
所求单词的序号
n
n
n满足
1
≤
n
≤
2
×
1
0
9
1≤n≤2\times10^9
1≤n≤2×109。
输入格式
第 1 行:1 个整数 alpha
第 2 行:1 个整数 n
输出格式
第 1 行:1 个字符串
输入样例 1
3
5
输出样例 1
ab
输入样例 2
26
2000000000
输出样例 2
flhomvx
分析
还是凑数游戏啊啊啊,考试的时候题意理解的有问题直接 α \alpha α进制凑数结果居然过给的了 7 7 7个样例,最后跑出来居然总共过了 25 25 25(共 62 62 62)个样例,,,,,,,,,
先是等比数列找出长度,然后除和模的时候都很玄妙(+1
-1
要仔细想临界状态才知道),多调试,这种凑数题我真的找不出什么适用的办法,我太弱了= =
其实对于第 d d d( d = 2 k d=2k d=2k)层,涂两种颜色的排列数是 C d k C_{d}^{k} Cdk,对于第 d d d( d = 3 k d=3k d=3k层),涂三种颜色的排列数是 C d k × C d − k k C_d^k\times C_{d-k}^k Cdk×Cd−kk,,,,,,
代码
#include<set>
#include<map>
#include<queue>
#include<cmath>
#include<cstdio>
#include<vector>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<functional>
using namespace std;
int read(){
int x=0,f=1;char c=getchar();
while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
while(c>='0'&&c<='9')x=(x<<3)+(x<<1)+(c^48),c=getchar();
return x*f;
}
long long A,N;
long long Pow(long long x,int y){
long long ret=1;
while(y){
if(y&1)
ret*=x;
x*=x;
y>>=1;
}
return ret;
}
int main(){
freopen("wordnumber.in" ,"r", stdin);
freopen("wordnumber.out","w",stdout);
A=read(),N=read();
int len=1;
while(Pow(A,len)<N)
N-=Pow(A,len++);
while(len){
putchar('a'+(N-1)/Pow(A,--len));
N-=(N-1)-(N-1)%Pow(A,len);//这两句要自己摸索很久才打得出来
}
return 0;
}
T3 ChristmasTree
题目
描述
你正在装饰你的圣诞树。树有
n
n
n层,将层数从上到下编号为
1
1
1到
n
n
n。你有许多红、绿、蓝这三种颜色的装饰品,你决定以下面的方式挂起它们:在第
k
k
k层,你将挂起一排正好
k
k
k个装饰品。对于某一层的装饰品用到的颜色,在这一层中这几种颜色的装饰品数量相等。例如,我们用R
代表红色装饰品,G
代表绿色装饰品,B
代表蓝色装饰品,考虑以下两棵树:
左边的树符合我们的要求,但是右边的树不符合,因为第三层有
1
1
1个蓝色装饰品但是有
2
2
2个红色装饰品。已知红、绿、蓝三种颜色的装饰品分别有
r
e
d
red
red、
g
r
e
e
n
green
green、
b
l
u
e
blue
blue个,求有多少种符合要求的装饰圣诞树的方法。对于两种圣诞树,如果树上某一个位置挂的装饰品颜色不同,那么认为是两种装饰方法。
数据范围
树的层数
n
n
n满足
1
≤
n
≤
10
1≤n≤10
1≤n≤10。
红色装饰品的数量
r
e
d
red
red满足
0
≤
r
e
d
≤
50
0≤red≤50
0≤red≤50。
绿色装饰品的数量
g
r
e
e
n
green
green满足
0
≤
g
r
e
e
n
≤
50
0≤green≤50
0≤green≤50。
蓝色装饰品的数量
b
l
u
e
blue
blue满足
0
≤
b
l
u
e
≤
50
0≤blue≤50
0≤blue≤50。
输入格式
第
1
1
1行:
1
1
1个整数
n
n
n
第
2
2
2行:
1
1
1个整数
r
e
d
red
red
第
3
3
3行:
1
1
1个整数
g
r
e
e
n
green
green
第
4
4
4行:
1
1
1个整数
b
l
u
e
blue
blue
输出格式
第
1
1
1行:
1
1
1个整数
输入样例 1
2
1
1
1
输出样例 1
6
输入样例 2
8
1
15
20
输出样例 2
197121
分析
显然对于第 d d d层:
- 可全涂一种颜色
- 若 d = 2 k d=2k d=2k,可涂两种颜色,每种 k k k个 (顺序可以有多种)
- 若 d = 3 k d=3k d=3k,可涂三种颜色,每种 k k k个 (顺序可以有多种)
记忆化搜索即可。
考试的时候做到这,花了10分钟写完记忆化搜索发现过不了第二个样例,想了几万年,才发现顺序可以有多种,于是又想了半天不知道怎么算,最后用next_permutation
打表才过了o(`ω´*)o我还是太弱了= =
代码
#include<set>
#include<map>
#include<queue>
#include<cmath>
#include<cstdio>
#include<vector>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<functional>
using namespace std;
int read(){
int x=0,f=1;char c=getchar();
while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
while(c>='0'&&c<='9')x=(x<<3)+(x<<1)+(c^48),c=getchar();
return x*f;
}
#define MAXN 10
#define MAXM 50
int N,R,G,B;
long long fac[MAXN+5];
long long dp[MAXN+2][MAXM+2][MAXM+2][MAXM+2];
const long long Table2[10]={0,6,90,1680};
const long long Table1[10]={0,2,6,20,70,252};
//打的表
//例如Table1[2]是1122的排列数,Table2[3]是111222333的排列数
long long dfs(int d,int r,int g,int b){
if(r<0||g<0||b<0)
return 0;
long long &ret=dp[d][r][g][b];
if(ret!=-1)
return ret;
if(d==N+1)
return ret=1;
ret=dfs(d+1,r-d,g,b)+dfs(d+1,r,g-d,b)+dfs(d+1,r,g,b-d);
//涂一种颜色
if(d%2==0)
ret+=(dfs(d+1,r-d/2,g-d/2,b)+dfs(d+1,r-d/2,g,b-d/2)+dfs(d+1,r,g-d/2,b-d/2))*Table1[d/2];
//涂两种颜色
if(d%3==0)
ret+=dfs(d+1,r-d/3,g-d/3,b-d/3)*Table2[d/3];
//涂三种颜色
return ret;
}
int main(){
freopen("christmastree.in" ,"r", stdin);
freopen("christmastree.out","w",stdout);
N=read(),R=read(),G=read(),B=read();
memset(dp,-1,sizeof dp);
printf("%lld",dfs(1,R,G,B));
}
T4 MuddyRoad2
题目
描述
有
n
n
n个点,序号分别从
0
0
0到
n
−
1
n-1
n−1。点
i
i
i和点
i
+
1
i+1
i+1之间有一条边。现在你要从点
0
0
0前往点
N
−
1
N-1
N−1。你每次可以向前走一步或两步,即如果你在点
i
i
i,那么你可以走到点
i
+
1
i+1
i+1或走到
i
+
2
i+2
i+2(不用经过
i
+
1
i+1
i+1)已知
0
0
0号点和
N
−
1
N-1
N−1号点是安全的,其他的
n
−
2
n-2
n−2个点中有恰好
m
u
d
d
y
C
o
u
n
t
muddyCount
muddyCount个点是不安全的,你不能经过不安全的点。从
0
0
0号点出发到达
n
−
1
n-1
n−1号点有多种走法。如果两种走法某一步的长度不同,那么被视为两种走法。求这
m
u
d
d
y
C
o
u
n
t
muddyCount
muddyCount个点有多少种情况使得走法有偶数种,对
555555555
555555555
555555555取模。两种情况中,如果某个点的安全状态不一样,那么这两种状况被视为不同的状况。
数据范围
点的个数
n
n
n满足
2
≤
n
≤
555
2≤n≤555
2≤n≤555。
不安全的点的个数
m
u
d
d
y
C
o
u
n
t
muddyCount
muddyCount满足
0
≤
m
u
d
d
y
C
o
u
n
t
≤
n
−
2
0≤muddyCount≤n-2
0≤muddyCount≤n−2。
输入格式
第
1
1
1行:
1
1
1个整数
n
n
n
第
2
2
2行:
1
1
1个整数
m
u
d
d
y
C
o
u
n
t
muddyCount
muddyCount
输出格式
第
1
1
1行:
1
1
1个整数
输入样例 1
5
2
输出样例 1
2
输入样例 2
314
78
输出样例 2
498142902
分析
显然,如果第 i i i格有石头,那你必须从 i − 1 i-1 i−1跳到 i + 1 i+1 i+1,只有一种走法。所以对于一种石头的放法,走法的数量就是石头隔开的每段平路的走法的数量之积,也就是说,只要有一段平路的走法数是偶数,这种放石头的方法就是符合要求的。
对于一段平路,走法是多少?由于一次可以跳一格或两格,所以有递推式 f [ i ] = f [ i − 1 ] + f [ i − 2 ] f[i]=f[i-1]+f[i-2] f[i]=f[i−1]+f[i−2],这是斐波拉契数列,根据“奇+奇=偶”“奇+偶=偶”,斐波拉契数列的奇偶:奇、奇、偶、奇、奇、偶……也就是说有 3 3 3的倍数格时,有偶数种走法。
然后就可以DP了:
(为了方便,格子编号
1
1
1到
n
n
n,在
n
+
1
n+1
n+1处放一块石头,一共放
m
+
1
m+1
m+1个石头)
d
p
[
i
]
[
j
]
[
0
]
dp[i][j][0]
dp[i][j][0]表示跳到第
i
−
1
i-1
i−1个点,
1
1
1到
i
i
i格中放了
j
j
j个石头(第
i
i
i格必放), 前面还没有区间是偶数种走法的方案数;
d
p
[
i
]
[
j
]
[
1
]
dp[i][j][1]
dp[i][j][1]表示跳到第
i
−
1
i-1
i−1个点,
1
1
1到
i
i
i格中放了
j
j
j个石头(第
i
i
i格必放), 前面已经有区间是偶数种走法的方案数,就有转移:
d
p
[
i
]
[
j
]
[
0
]
=
∑
k
=
0
i
−
1
{
d
p
[
k
]
[
j
−
1
]
[
0
]
(
从
k
+
1
跳
到
i
−
1
有
奇
数
种
走
法
)
0
(
从
k
+
1
跳
到
i
−
1
有
偶
数
种
走
法
)
dp[i][j][0]=\sum\limits_{k=0}^{i-1} \begin{cases} dp[k][j-1][0] & (从k+1跳到i-1有奇数种走法)\\ 0 & (从k+1跳到i-1有偶数种走法) \end{cases}
dp[i][j][0]=k=0∑i−1{dp[k][j−1][0]0(从k+1跳到i−1有奇数种走法)(从k+1跳到i−1有偶数种走法)
d
p
[
i
]
[
j
]
[
1
]
=
∑
k
=
0
i
−
1
{
d
p
[
k
]
[
j
−
1
]
[
1
]
(
从
k
+
1
跳
到
i
−
1
有
奇
数
种
走
法
)
d
p
[
k
]
[
j
−
1
]
[
1
]
+
d
p
[
k
]
[
j
−
1
]
[
0
]
(
从
k
+
1
跳
到
i
−
1
有
偶
数
种
走
法
)
dp[i][j][1]=\sum\limits_{k=0}^{i-1} \begin{cases} dp[k][j-1][1] & (从k+1跳到i-1有奇数种走法)\\ dp[k][j-1][1]+dp[k][j-1][0] & (从k+1跳到i-1有偶数种走法) \end{cases}
dp[i][j][1]=k=0∑i−1{dp[k][j−1][1]dp[k][j−1][1]+dp[k][j−1][0](从k+1跳到i−1有奇数种走法)(从k+1跳到i−1有偶数种走法)
但是这样的话,即使利用前面的斐波拉契数列奇偶性判断也是
O
(
n
3
)
O(n^3)
O(n3),会T
。
由于当 i − 1 − k ≡ 0 m o d    3 i-1-k\equiv0\mod3 i−1−k≡0mod3时从 k + 1 k+1 k+1跳到 i − 1 i-1 i−1有偶数种跳法,移项得 i − 1 ≡ k m o d    3 i-1\equiv k\mod3 i−1≡kmod3时从 k + 1 k+1 k+1跳到 i − 1 i-1 i−1有偶数种跳法,于是以除以 3 3 3的余数作为下标,前缀和优化即可。
考试的时候都没有考虑过这道题,我太弱了= =
代码
这是无优化(开O2
后)大约过
3
4
\dfrac{3}{4}
43点的DP:
#include<set>
#include<map>
#include<queue>
#include<cmath>
#include<cstdio>
#include<vector>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<functional>
using namespace std;
int read(){
int x=0,f=1;char c=getchar();
while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
while(c>='0'&&c<='9')x=(x<<3)+(x<<1)+(c^48),c=getchar();
return x*f;
}
#define MAXN 555
#define MOD 555555555
int N,M;
long long dp[MAXN+5][MAXN+5][2];
//dp[i][j][1/0] 跳到第i-1个点 放j个石头(第i个点必放) 前面有/没有区间是偶数种方法 的方案数
void Add(long long &A,long long B){
A=(A+B)%MOD;
}
int main(){
freopen("muddyroad2.in" ,"r", stdin);
freopen("muddyroad2.out","w",stdout);
N=read(),M=read();
dp[0][0][0]=1;
//1和N不能放 N+1必放(一共放M+1个)
for(int i=1;i<=N+1;i++){
if(i==1||i==N)
continue;
for(int j=1;j<=min(M+1,i-1);j++){
for(int k=0;k<i;k++){
if((i-k)%3==1)
Add(dp[i][j][1],dp[k][j-1][0]);
else
Add(dp[i][j][0],dp[k][j-1][0]);
Add(dp[i][j][1],dp[k][j-1][1]);
}
}
}
printf("%lld",dp[N+1][M+1][1]);
}
这是前缀和优化过的DP:
#include<set>
#include<map>
#include<queue>
#include<cmath>
#include<cstdio>
#include<vector>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<functional>
using namespace std;
int read(){
int x=0,f=1;char c=getchar();
while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
while(c>='0'&&c<='9')x=(x<<3)+(x<<1)+(c^48),c=getchar();
return x*f;
}
#define MAXN 555
#define MOD 555555555
int N,M;
long long Sum[MAXN+5][3][2];
long long dp[MAXN+5][MAXN+5][2];
//Sum[j][k][0/1] 前面的i中 (i-1)%3=k的 dp[i][j][0/1]之和
//dp[i][j][1/0] 跳到第i-1个点 放j个石头(第i个点必放) 前面有/没有区间是偶数种方法 的方案数
void Add(long long &A,long long B){
A=(A+B)%MOD;
}
int main(){
freopen("muddyroad2.in" ,"r", stdin);
freopen("muddyroad2.out","w",stdout);
N=read(),M=read();
dp[0][0][0]=1;
Sum[0][0][0]=1;
//1和N不能放 N+1必放(一共放M+1个)
for(int i=1;i<=N+1;i++){
if(i==1||i==N)
continue;
for(int j=1;j<=min(M+1,i-1);j++){
Add(dp[i][j][0],Sum[j-1][i%3][0]),
Add(dp[i][j][0],Sum[j-1][(i+1)%3][0]);
Add(dp[i][j][1],Sum[j-1][i%3][1]),
Add(dp[i][j][1],Sum[j-1][(i-1)%3][0]);
Add(dp[i][j][1],Sum[j-1][(i-1)%3][1]),
Add(dp[i][j][1],Sum[j-1][(i+1)%3][1]);
}
for(int j=1;j<=min(M+1,i-1);j++){
Add(Sum[j][i%3][0],dp[i][j][0]);
Add(Sum[j][i%3][1],dp[i][j][1]);
}
}
printf("%lld",dp[N+1][M+1][1]);
}