SS241109B. tii(tii)
題意
給你一個 \(01\) 序列,長度為 \(n \le 5\times 10^5\)。給你一個小數 \(p\),要你找出一個區間滿足區間 \(1\) 的個數比區間長度和 \(p\) 最接近,輸出區間的左端點,如果有多個區間輸出左端點最小的那個。
思路
設 \(s\) 是原序列的字首和陣列,翻譯一下題面就是求 \(|p-\frac{s_r-s_l}{r-l}|\) 最小,輸出 \(l+1\)。
算一下式子:
\[|p-\frac{s_r-s_l}{r-l}|=|\frac{(pr-s_r)-(p_l-s_l)}{r-l}|
\]
這相當於兩點斜率絕對值最小,\((i,pi-s_i)\)。
觀察發現,\(s_i\) 每次可能增加 \(1\) 或者不變,\(pi\) 每次增加 \(p<1\),因此可以畫出函式 \(pi-s_i\) 的影像。
要使兩點間斜率最小,發現一定是選擇 \(y\) 座標相鄰的兩點。感性理解?
然後就對點排個序,再 \(O(n)\) 掃一遍更新答案即可。
時間複雜度是 \(O(n\log n)\),瓶頸在於排序。
code
程式碼很好寫啊。
注意精度問題,因此判斷相等和小於需要根據 eps 判斷,尤其注意小於的細節:
bool less (ld a,ld b) { return b-a>eps; }
#include<bits/stdc++.h>
#define sf scanf
#define pf printf
#define rep(x,y,z) for(int x=y;x<=z;x++)
#define per(x,y,z) for(int x=y;x>=z;x--)
using namespace std;
typedef long long ll;
namespace Vivid {
constexpr int N=5e5+7,base=1e6;
typedef long double ld;
constexpr ld inf=1e9,eps=1e-10;
int t,n;
ld p;
int b[N];
char a[N];
ld c[N];
int rk[N];
bool cmp (int a,int b) { return c[a]!=c[b] ? c[a]<c[b] : a<b; }
int ans;
ld mn;
ld abs(ld x) { return x>=0 ? x : -x; }
bool equal (ld a,ld b) { return abs(a-b)<=eps; }
bool less (ld a,ld b) { return b-a>eps; }
void main() {
sf("%d",&t);
while(t--) {
mn=inf;
ans=0;
sf("%d%Lf%s",&n,&p,a+1);
rk[0]=0;
rep(i,1,n) b[i]=b[i-1]+a[i]-'0', c[i]=p*i-b[i], rk[i]=i;
sort(rk,rk+n+1,cmp);
rep(i,1,n) {
ld x=abs((c[rk[i]]-c[rk[i-1]])/(rk[i]-rk[i-1]));
if(less(x,mn)) ans=min(rk[i-1],rk[i]), mn=x;
else if(equal(x,mn)) ans=min(ans,min(rk[i-1],rk[i]));
}
pf("%d\n",ans+1);
}
}
}
int main() {
#ifdef LOCAL
freopen("my.out","w",stdout);
#else
freopen("tii.in","r",stdin);
freopen("tii.out","w",stdout);
#endif
Vivid :: main();
}