SS241109B. tii(tii)

liyixin發表於2024-11-09

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();
}