【二分单个位置 前缀和】 防线
传送门
题意
一维的防线,有\(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();
}