这题是2020百度之星初赛第三场1005
题意描述
一个长度为n的棋盘( 1 ≤ n ≤ 1000 1\leq n\leq1000 1≤n≤1000),恰好放置m个传送门( 0 ≤ m ≤ 1000 0\leq m\leq1000 0≤m≤1000),传送门可以向之前的任意一点传送且如传送到的位置有传送门可连续传送,每次选手投掷骰子可以掷出1~11,即如果当前在位置 y 且投出数字 x,那棋子将会跳到位置 y+x。问有多少种设置棋盘的方式可以让选手从起点1走到终点n?
题目分析
1.起点1和终点n不能放置传送门
2.当前位置x放置传送门可传送到1~x-1位置,共x-1种传送门
3.因为掷骰子最大为11,连续放置传送门数量最大为10。
4.若传送门放不下,则输出-1。比如样例23 21,长度为23的棋盘最大放置20个传送门OXXXXXXXXXXOXXXXXXXXXXO,就像这样。
解题思路
1.简化问题第一步
当没有传送门时,问题转化为上楼梯问题,求方案数。动态转移方程: d p [ i ] = d p [ i − 1 ] + d p [ i − 2 ] + . . . + d p [ i − 11 ] ; dp[i]=dp[i-1]+dp[i-2]+...+dp[i-11]; dp[i]=dp[i−1]+dp[i−2]+...+dp[i−11];
2.简化问题第二步
当传送门随意放置时,仅变化动态转移方程:
d
p
[
i
]
=
d
p
[
i
−
1
]
;
dp[i]=dp[i-1];
dp[i]=dp[i−1];
d
p
[
i
]
=
d
p
[
i
]
+
d
p
[
i
−
2
]
∗
(
i
−
2
)
+
.
.
.
+
d
p
[
i
−
x
]
∗
(
i
−
2
)
!
(
i
−
x
−
1
)
!
+
.
.
.
+
d
p
[
i
−
11
]
∗
(
i
−
2
)
!
(
i
−
12
)
!
;
dp[i]=dp[i]+dp[i-2]*(i-2)+...+dp[i-x]*\frac{(i-2)!}{(i-x-1)!}+...+dp[i-11]*\frac{(i-2)!}{(i-12)!};
dp[i]=dp[i]+dp[i−2]∗(i−2)+...+dp[i−x]∗(i−x−1)!(i−2)!+...+dp[i−11]∗(i−12)!(i−2)!;
第一个方程解释:这里代表当前状态为未添加传送门的状态,未添加传送门时,为了避免重复计算,我们只让其从前一个位置的方案数转移过来,这样上一个位置可以从上上个位置转移,也就是说连续不放置传送门的时候方案数计算并不发生错误。
第二个方程解释:当放置传送门时,我们可以从
[
i
−
2
,
i
−
11
]
[i-2,i-11]
[i−2,i−11]这些位置转移过来,中间经过的位置放置满传送门,这时第
(
i
−
x
)
(i-x)
(i−x)个位置到第
(
i
)
(i)
(i)个位置中间有
(
x
−
1
)
(x-1)
(x−1)个空位,可以放置
(
x
−
1
)
(x-1)
(x−1)个传送门,而每个位置
(
y
)
(y)
(y)可以放置传送门的种类数为
(
y
−
1
)
(y-1)
(y−1)。这样也就是第
(
i
−
1
)
(i-1)
(i−1)个可以放置
(
i
−
2
)
(i-2)
(i−2)种传送门,从
(
i
)
(i)
(i)位置到
(
x
)
(x)
(x)位置共有
[
i
−
1
,
i
−
x
+
1
]
[i-1,i-x+1]
[i−1,i−x+1]这些个空位,传送门数量为
(
i
−
2
)
!
(
i
−
x
−
1
)
!
\frac{(i-2)!}{(i-x-1)!}
(i−x−1)!(i−2)!。此时需要将
d
p
dp
dp转移从
(
i
−
x
)
(i-x)
(i−x)位置转移到
(
i
)
(i)
(i)位置即可,动态转移方程写作
d
p
[
i
]
=
d
p
[
i
]
+
d
p
[
i
−
x
]
∗
(
i
−
2
)
!
(
i
−
x
−
1
)
!
dp[i]=dp[i]+dp[i-x]*\frac{(i-2)!}{(i-x-1)!}
dp[i]=dp[i]+dp[i−x]∗(i−x−1)!(i−2)!,而
[
i
−
2
,
i
−
11
]
[i-2,i-11]
[i−2,i−11]这些位置都需要进行转移,即所见方程二。
3.问题求解:
d
p
[
当
前
位
置
]
[
已
放
置
传
送
门
数
量
]
dp[当前位置][已放置传送门数量]
dp[当前位置][已放置传送门数量],当走到位置i时,已放置j个传送门时,动态转移方程:
d
p
[
i
]
[
j
]
=
d
p
[
i
−
1
]
[
j
]
;
dp[i][j]=dp[i-1][j];
dp[i][j]=dp[i−1][j];
d
p
[
i
]
[
j
]
=
d
p
[
i
]
[
j
]
+
d
p
[
i
−
2
]
[
j
−
1
]
∗
(
i
−
2
)
+
.
.
.
+
d
p
[
i
−
x
]
[
j
−
(
i
−
x
−
1
)
]
∗
(
i
−
2
)
!
(
i
−
x
−
1
)
!
+
.
.
.
+
d
p
[
i
−
11
]
[
j
−
10
]
∗
(
i
−
2
)
!
(
i
−
12
)
!
;
dp[i][j]=dp[i][j]+dp[i-2][j-1]*(i-2)+...+dp[i-x][j-(i-x-1)]*\frac{(i-2)!}{(i-x-1)!}+...+dp[i-11][j-10]*\frac{(i-2)!}{(i-12)!};
dp[i][j]=dp[i][j]+dp[i−2][j−1]∗(i−2)+...+dp[i−x][j−(i−x−1)]∗(i−x−1)!(i−2)!+...+dp[i−11][j−10]∗(i−12)!(i−2)!;
这里注意为了避免重复计算,在计算中间不放置传送门时,只能从前一个位置转移方案数过来(这里并不会漏计算,从x到y,一格一格跳过来和从x直接跳到y方案数是一样的)。
而中间如果放置传送门时,从x到y,中间必须放满传送门,方案数用“x位置的方案数*中间每个位置能放置传送门的种类数”,注意要从x位置减去中间放置传送门数的那种方案来乘,也就是要从
d
p
[
x
]
[
j
−
(
y
−
x
−
1
)
]
dp[x][j-(y-x-1)]
dp[x][j−(y−x−1)]位置转移。
4.注意细节
注意无解情况,最多放置传送门需要让两个能走的位置中间不超过10个,用公式判断 ( m + ( m − 1 ) / 10 > n − 2 ) (m+(m-1)/10>n-2) (m+(m−1)/10>n−2)。
附赠奇怪样例
input1
3
1 0
23 20
23 21
23 1000
output1
1
6622482
-1
-1
input2
5
5 0
5 1
5 2
5 3
5 4
output2
1
6
11
6
-1
input3
6
6 0
6 1
6 2
6 3
6 4
6 5
output3
1
10
35
50
24
-1
感谢胡同学写的暴力帮我找到WA点
#include <bits/stdc++.h>
#include <stdio.h>
#include <iostream>
#include <string.h>
#include <stack>
#include <algorithm>
#include <queue>
#include <map>
#include <cmath>
#define eps 0.00000001
#define pi P_MI
#define ll long long
using namespace std;
struct point {
int x,y;
bool operator < (const point &a) const {
return x>a.x;//最小值优先
}
};
priority_queue <int> que;
ll a[1100][1100];
int main () {
int T;
scanf("%d",&T);
while (T--) {
int n,m;
scanf("%d%d",&n,&m);
memset(a,0,sizeof(a));
if (m+(m-1)/10>n-2){
if (n==1 && m==0) printf("1\n");
else printf("-1\n");
continue;
}
a[1][0]=1;
m=min(m,max(n-2,0));
for (int i=2; i<=n; i++) {
int mm=min(i-2,m);
for (int j=0; j<=mm; j++) {
a[i][j] = a[i-1][j];
for (int k= max( i-j-1, max(1,i-11) ); k<=i-2; k++) {
ll sum=1;
for (ll l=k; l<=i-2; l++){
sum = sum * l % 1000000007;
}
a[i][j] = (a[i][j] + sum*a[k][j-(i-k-1)]) % 1000000007;
}
}
}
a[n][m]=a[n][m]%1000000007;
printf("%lld\n",a[n][m]);
}
return 0;
}
// freopen("1.in","r",stdin);
// freopen("1.out","w",stdout);