原题链接:1788D - Moving Dots
题意
给坐标轴上 n n n 个点的坐标 x i x_i xi。
每个点都朝它最近的点运动(如果某个点距离左右最近的点同样近,则向左走),直到和另一个点相撞,融合成一个点并停下来,最终平面上会剩余一些点。求这 n n n 个点的所有子集,运动结束后剩余点的个数的和。
n ∈ [ 2 , 3000 ] , x i ∈ [ 1 , 1 0 9 ] n\in[2, 3000], x_i\in[1, 10^9] n∈[2,3000],xi∈[1,109]。
分析
设想某个子集,点运动结束的情景。最终剩余的某一个点 t t t 可能由很多点融合而来,但一定是由两个点 i , j i,j i,j 最先碰撞并停下,再融合其他点。所以这个子集中,点 t t t 对最终答案的贡献,可以视为点对 ( i , j ) (i,j) (i,j) 在这个子集中对答案的贡献。
枚举所有的点对 ( i , j ) (i,j) (i,j) (其中 x i < x j x_i <x_j xi<xj),考虑点对 ( i , j ) (i,j) (i,j) 在多少个子集中对答案做出了贡献。下面我们考虑在什么样的子集中,开始运动后 ( i , j ) (i,j) (i,j) 会直接相撞停下来。
首先, ( i , j ) (i,j) (i,j) 之间不能有其他点,否则 ( i , j ) (i,j) (i,j) 必然不会直接碰撞,而是其一和中间的点碰撞之后停下来再和另一个点碰撞。
其次,要保证 ( i , j ) (i,j) (i,j) 都朝向彼此的方向运动。设 d = x j − x i d = x_j-x_i d=xj−xi,要保证 [ x i − d , x i ] [x_i-d, x_i] [xi−d,xi] 的范围内没有别的点,否则点 i i i 将会向左移动。同理要保证 [ x j , x j + d − 1 ] [x_j, x_j+d-1] [xj,xj+d−1] 范围内没有别的点,否则 j j j 会向右移动。
我们可以通过二分求出 [ 1 , x i − d − 1 ] [1,x_i-d- 1] [1,xi−d−1] 和 [ x j + d , ∞ ) [x_j+d,\infty) [xj+d,∞) 的点的个数,记为 x x x。那么 2 x 2^x 2x 就是点对 ( i , j ) (i,j) (i,j) 对答案的贡献。
#include <bits/stdc++.h>
using namespace std;
#define int long long
#define PII pair<int, int>
int Testnum = 1;
/********************** Core code begins **********************/
const int N = 5e3 + 7, MOD = 1e9 + 7;
int n;
int x[N], s[N];
void SolveTest() {
scanf("%lld", &n);
s[0] = 1;
for (int i = 1; i <= n; i++) {
scanf("%lld", &x[i]);
s[i] = s[i - 1] * 2 % MOD;
}
int res = 0;
for (int i = 1; i <= n; i++) {
for (int j = i + 1; j <= n; j++) {
int d = x[j] - x[i];
int x1 = lower_bound(x + 1, x + 1 + n, x[i] - d) - x - 1;
int x2 = lower_bound(x + 1, x + 1 + n, x[j] + d) - x;
x2 = n - x2 + 1;
res = (res + s[x1 + x2]) % MOD;
}
}
cout << res;
}
/********************** Core code ends ***********************/
signed main() {
#ifdef LOCAL
freopen("in.txt", "r", stdin);
#endif
// cin >> Testnum;
for (int i = 1; i <= Testnum; i++) {
SolveTest();
}
return 0;
}