【二分单个位置 前缀和】 防线

传送门

题意

一维的防线,有\(n\)组防具,每组防具通过\(s、e、d\)来描述,即\(s,s+d,s+2d \dots , s+kd \leq e , s+(k+1)d>e\),如果某个位置上防具的个数是偶数个这个防线就是没有破绽的,0也是偶数,有奇数个位置有防具就是破绽,整条防线最多只有一个位置上有破绽,如果有破绽输出破绽位置和这个位置上的防具数量,否则的话输出\("There's\; no\; weakness"\),一共有\(t\)组测试数据.

数据范围

防具的总数不超过\(10^{8}\)
\(0<= s_{i}、k_{i}、d_{i} <= 2^{31}-1\)
\(n<=200000\)

题解

通过前缀和来使具有单调性,奇数最多只有一个,所以前缀和一定为奇数,具有了单调性就可以二分位置,当前前缀和为偶则左边一定全是偶,如果存在奇数的位置一定在右半区间
\(O(n)\)时间求出前缀和,二分的位置是\(mid\),枚举每一个等差数列,如果\(s\leq mid\)则存在交集,共同区间为\([s,min(e,mid) ]\),则该区间中包含这个数列的个数为

  • \(\left\lfloor\frac{(\min (e, mid)-S)}{d}\right\rfloor +1\)

Code

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define rep(i,a,n) for(int i=a;i<n;i++)
const int N=2e5+10;
ll n;
struct node
{
    ll s,e,d;
}a[N];
ll get(ll mid)
{
    ll ans=0;
    rep(i,1,n+1)
        if(a[i].s<=mid)
            ans+=(min(mid,a[i].e)-a[i].s)/a[i].d+1;
    return ans;
}
void solve()
{
    cin >> n;
    rep(i,1,n+1)
        cin >> a[i].s >> a[i].e >> a[i].d;

    ll l=0,r=1e9;

    while(l<r){
        ll mid=l+r>>1;
        if(get(mid)&1)
            r=mid;
        else
            l=mid+1;
    }
    ll ans=get(r)-get(r-1);
    if(ans&1)
        cout<<l<<' '<<ans<<endl;
    else
        puts("There's no weakness.");    
}
int main(){
    int t;
    cin>>t;
    while(t--) 
        solve();
}
posted @ 2020-05-26 09:30  Hyx'  阅读(144)  评论(0编辑  收藏  举报