GCJ2015FinalC
题意
给长度为 \(n\) 的 \(01\) 串 \(s\),和比例 \(r\)。
问 \(s\) 的子串中 \(|\frac{1 的个数}{长度} - r|\) 最小是多少,输出子串的开始位置,若有多个起始位置合法,输出最小的。
\(1\ \leq\ n\ \leq\ 10^5\)。
做法1
令 \(cnt1_i\) 表示 \(s[i...n]\) 中 \(1\) 的个数,\(len_i\) 表示 \(s[i...n]\) 的长度。
一个子串可以表示为两个后缀 \(i,\ j\) 相减得到,\(1\) 的比例为 \(\frac{cnt1_i\ -\ cnt1_j}{len_i\ -\ len_j}\)。
在坐标系中考虑,问题变成平面上 \(n\) 个点,两两连线,找最接近 \(r\) 的斜率的直线。按照 \(y\ =\ rx\ +\ k\) 的 \(k\) 值排序即可。
时间复杂度 \(O(n\ log\ n)\)。
代码
#include <bits/stdc++.h>
#define eps 1e-10
#define double long double
#ifdef __WIN32
#define LLFORMAT "I64"
#else
#define LLFORMAT "ll"
#endif
using namespace std;
struct Num {
long long zi, mu;
Num() {}
Num(long long zi, long long mu): zi(zi), mu(mu) {}
friend bool operator < (const Num &a, const Num &b) {
double t = (double) a.zi * b.mu - (double) a.mu * b.zi;
return t < -eps;
}
friend bool operator == (const Num &a, const Num &b) {
double t = (double) a.zi * b.mu - (double) a.mu * b.zi;
return t >= -eps && t <= eps;
}
};
int main() {
ios::sync_with_stdio(false);
int T;
cin >> T;
for (int i = 1; i <= T; ++i) {
cout << "Case #" << i << ": ";
int n;
string s;
double r;
cin >> n >> r >> s;
int R = 1000000 * r;
vector<int> ord(n + 1), x(n + 1, 0), y(n + 1, 0);
for (int i = 0; i <= n; ++i) ord[i] = i;
for (int i = n - 1; ~i; --i) {
x[i] = x[i + 1] + 1;
y[i] = y[i + 1] + (s[i] == '1');
}
sort(ord.begin(), ord.end(), [&](int i, int j) {
long long ki = (long long) 1000000 * y[i] - (long long) R * x[i];
long long kj = (long long) 1000000 * y[j] - (long long) R * x[j];
if(ki == kj) return i < j;
else return ki < kj;
});
Num mn(1, 1);
int ans;
for (int _ = 0; _ < n; ++_) {
bool output = 0;
if(i == 27) output = 1;
int i = ord[_], j = ord[_ + 1];
if(i > j) swap(i, j);
Num k(1000000ll * (y[i] - y[j]), 1000000ll * (x[i] - x[j]));
k.zi -= (long long) R * (x[i] - x[j]);
if(k.zi < 0) k.zi = -k.zi;
if(k == mn) ans = min(ans, i);
else if(k < mn) mn = k, ans = i;
}
cout << ans << endl;
}
return 0;
}