1788D - Moving Dots (思维 + 计数)

原题链接: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=xjxi,要保证 [ x i − d , x i ] [x_i-d, x_i] [xid,xi] 的范围内没有别的点,否则点 i i i 将会向左移动。同理要保证 [ x j , x j + d − 1 ] [x_j, x_j+d-1] [xj,xj+d1] 范围内没有别的点,否则 j j j 会向右移动。

我们可以通过二分求出 [ 1 , x i − d − 1 ] [1,x_i-d- 1] [1,xid1] [ 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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值