题意:
给定一个区间
[
0
,
n
]
[0,n]
[0,n] ,区间内由一些圆,圆的半径和圆心都是整数,且圆上所有点都在区间内,每对圆的交点个数不超过
1
1
1 个,圆的半径不超过
5
5
5 ,求有多少种合法的方法加入新的圆。
思考:
这道题,一开始看着就觉得是个区间 dp ,但是又感觉不是那么好处理。但其实这道题就是一个最基础的区间 dp 题。
这道题的答案满足区间 dp 的一个普遍特征,最后由一个个大圆不相交的构成。
借助这道题,来总结一下区间 dp 的一般做法:
- 区间 dp 一般是按照区间长度从小到大进行合并。
- 去除掉非法区间后,考虑一个区间 [ l , r ] [l,r] [l,r] 的答案该怎么统计,一类是由 [ l , r − 1 ] [l,r-1] [l,r−1] 或者 [ l + 1 , r ] [l+1,r] [l+1,r] 这种区间不进行任何操作转移过来,另一类是由两个区间分别操作然后再合并。
在这道题中,我们观察一下性质:
首先,只有不超过一个交点等价于不能相交。
然后,按照上面的一般做法,从小到大依次枚举区间长度。
令
f
[
l
]
[
r
]
f[l][r]
f[l][r] 表示区间
[
l
,
r
]
[l,r]
[l,r] 合法的方案数,
g
[
l
]
[
r
]
g[l][r]
g[l][r] 表示区间
[
l
,
r
]
[l,r]
[l,r] 在存在外接圆的情况下的方案数。
那么,
f
[
l
]
[
r
]
f[l][r]
f[l][r] 该怎么转移呢?考虑上面的一般做法第二条,可以直接由
f
[
l
]
[
r
−
1
]
f[l][r-1]
f[l][r−1] 和
f
[
l
+
1
]
[
r
]
f[l+1][r]
f[l+1][r] 转移过来,但是这样会有重复,中间部分会被统计两次,需要容斥掉,还需要加上包含整个区间的圆,比较麻烦。
我们不妨换个思路,考虑只由
f
[
l
,
r
−
1
]
f[l,r-1]
f[l,r−1] 转移过来,那么还少了部分是包含点
r
r
r 的圆,这个怎么统计呢?可以直接枚举以点
r
r
r 为右端点的圆的半径。然后由
[
l
,
p
0
]
[l, p0]
[l,p0] 和
[
p
0
,
r
]
[p0, r]
[p0,r] 两个区间合并起来转移过来。
最后,再考虑覆盖整个区间的圆是否已经存在。
题解:
先预处理出来,哪些区间已经存在圆了,哪些区间不能有圆覆盖。由于半径的大小只有
5
5
5 ,这部分可以直接暴力预处理。
直接枚举区间长度,考虑区间
[
l
,
r
]
[l,r]
[l,r] 的转移。
f
[
l
,
r
]
=
f
[
l
]
[
r
−
1
]
+
∑
p
0
f
[
l
]
[
p
0
]
∗
g
[
p
0
]
[
r
]
f[l,r] = f[l][r-1] + \sum_{p_0}{f[l][p0] * g[p0][r]}
f[l,r]=f[l][r−1]+∑p0f[l][p0]∗g[p0][r] ;
g
[
l
,
r
]
=
g
[
l
]
[
r
−
1
]
+
∑
p
0
f
[
l
]
[
p
0
]
∗
g
[
p
0
]
[
r
]
g[l,r] = g[l][r-1] + \sum_{p_0}{f[l][p0] * g[p0][r]}
g[l,r]=g[l][r−1]+∑p0f[l][p0]∗g[p0][r] ,
(
r
−
l
)
m
o
d
2
=
0
(r-l)\mod2=0
(r−l)mod2=0
最后,如果
[
l
,
r
]
[l,r]
[l,r] 不存在初始的圆覆盖,那么可以存在
[
l
,
r
]
[l,r]
[l,r] 的圆,也可以不存在,即
f
[
l
]
[
r
]
f[l][r]
f[l][r] 答案要翻倍。不过这里要注意区间长度不能超过
10
10
10 。
#include<bits/stdc++.h>
#define For(aa, bb, cc) for(int aa = (bb); aa <= (int)(cc); ++aa)
using namespace std;
typedef long long LL;
const int maxn = 1e3 + 10;
const int mod = 1e9 + 7;
int n, k;
int f[maxn][maxn], g[maxn][maxn];
// [l, r] : ans, cover circle
bool cov[maxn][maxn], ban[maxn][maxn];
LL fadd(LL x, LL y) {
return x + y > mod ? x + y - mod : x + y;
}
int main() {
#ifndef ONLINE_JUDGE
freopen("in.txt", "r", stdin);
freopen("out.txt", "w", stdout);
#endif
ios::sync_with_stdio(false);
cin.tie(NULL);
cin >> n >> k;
for(int i = 1; i <= k; ++i) {
int c, r;
cin >> c >> r;
cov[c - r][c + r] = 1;
for(int j = 0; j < c - r; ++j) {
for(int l = c - r + 1; l < c + r; ++l) {
ban[j][l] = 1;
}
}
for(int j = c - r + 1; j < c + r; ++j) {
for(int l = c + r + 1; l <= n; ++l) {
ban[j][l] = 1;
}
}
}
for(int i = 0; i + 1 <= n; ++i) f[i][i + 1] = 1;
for(int i = 0; i + 2 <= n; ++i) {
if(ban[i][i + 2]) continue;
if(cov[i][i + 2]) f[i][i + 2] = 1;
else f[i][i + 2] = 2;
g[i][i + 2] = 1;
}
for(int d = 3; d <= n; ++d) {
for(int l = 0; l + d <= n; ++l) {
int r = l + d;
if(ban[l][r]) continue;
f[l][r] = f[l][r - 1];
if((r - l) % 2 == 0) g[l][r] = f[l][r - 1];
for(int i = r - 2; i >= max(r - 10, l + 1); i -= 2) {
f[l][r] = fadd(f[l][r], 1ll * f[l][i] * g[i][r] % mod);
if((r - l) % 2 == 0) {
g[l][r] = fadd(g[l][r], 1ll * f[l][i] * g[i][r] % mod);
}
}
if(!cov[l][r] && (r - l) % 2 == 0 && (r - l) <= 10) {
f[l][r] = 2 * f[l][r] % mod;
}
}
}
/*
For(l, 0, n) {
For(r, l + 2, n) {
cout<<l<<" "<<r<<" "<<f[l][r]<<" "<<g[l][r]<<endl;
}
}
*/
printf("%d\n", f[0][n]);
return 0;
}
题意:
构造一个数列
a
n
{a_n}
an ,使得其满足:
- a i > = 0 a_i >= 0 ai>=0
- ∑ i a i = s \sum_{i}{a_i} = s ∑iai=s
- 对 i > 1 i > 1 i>1 ,满足 a i − a i + 1 = k a_i - a_{i+1} = k ai−ai+1=k 或者 a i + 1 − a i = 1 a_{i+1}-a_i =1 ai+1−ai=1
思考:
看到这种题的第一反应,就是首先先排除掉一个条件,构造出一个序列,然后再用第二个条件进行调整。
(理论上的第一反应应该是,构造出最小的或者最大的)
然后,我在考场上的时候,先排除掉
a
i
−
a
i
+
1
=
k
a_i - a_{i+1} = k
ai−ai+1=k ,然后构造了一个等差数列,发现每次用一次这个条件,就会使后面所有的权值减少
k
+
1
k+1
k+1 ,那么可以得到以下不定方程:
n ∗ a 0 + n ∗ ( n − 1 ) 2 − ( k + 1 ) ∗ y = s n * a_0 + \frac{n*(n-1)}{2} - (k+1)*y = s n∗a0+2n∗(n−1)−(k+1)∗y=s
这里
y
∈
[
0
,
n
∗
(
n
−
1
)
2
]
y \in[0, \frac{n*(n-1)}{2}]
y∈[0,2n∗(n−1)] ,然后可以调整的有
a
0
a_0
a0 和
y
y
y ,也就是一个标准的不定方程。移一下项,可以发现:
a
0
=
s
−
n
∗
(
n
−
1
)
2
+
(
k
+
1
)
∗
y
n
a_0 = \frac{s- \frac{n*(n-1)}{2} + (k+1)*y}{n}
a0=ns−2n∗(n−1)+(k+1)∗y
随
y
y
y 的增大,
a
0
a_0
a0 也在增大,但是由于后面的每个都要减去
k
+
1
k+1
k+1 ,导致
a
i
>
=
0
a_i >=0
ai>=0 不能得到很好的保证。(这一点直接观察不定方程也能看出来)
and then? 陷入瓶颈了。
那就换个构造方法,如果这样减法构造不好保证
a
i
>
=
0
a_i >= 0
ai>=0,那么不如考虑一下加法构造。(这也是值最小的构造方案)
先按照下面方法构造一个序列:
0
,
1
,
.
.
.
,
k
,
0
,
1
,
.
.
.
,
k
,
.
.
.
0, 1, ... , k, 0, 1, ... , k, ...
0,1,...,k,0,1,...,k,...
可以按照顺序依次对除了值为
k
k
k 的点进行增加
(
k
+
1
)
(k + 1)
(k+1) 的操作。如果值为
k
k
k 的点两侧的点都已经增加过了,那么它也可以增加。
假设上面的值求和为
y
y
y ,如果
(
s
−
y
)
m
o
d
(
k
+
1
)
=
0
(s-y) \mod (k+1) = 0
(s−y)mod(k+1)=0 ,则原来的有解,然后再一个一个去放置,就可以构造出一组解。