A 二分,尺取
對於一個區間\([l,r]\)如果區間內的某個數的個數大於\(x\),那么這個區間的值必然大於等於\(x\),將這個區間向兩邊擴展,擴展后的區間的值也必然大於\(x\)。
我們二分第k大區間的值\(x\),在判定答案的時候,先枚舉左端點,求一個滿足區間內的某個數大於\(x\)的最小的右端點,不難發現,隨着左端點向右推移,右端點一定隨着左端點的增大非嚴格單調遞增,也就是說左端點移動的時候右端點也具有單調性,可以用尺取法來計數。
const int maxn = 1e5+10;
const int maxm = 1e6+10;
int a[maxn], c[maxn];
int n; ll k;
vector<int> b;
int f(int x) {
return lower_bound(b.begin(), b.end(), x)-b.begin();
}
bool check(int x) {
clr(c, 0);
int l = 1;
ll sum = 0;
for (int i = 1; i<=n; ++i) {
++c[a[i]];
while (c[a[i]]>=x) {
sum += n-i+1;
--c[a[l++]];
}
}
return sum>=k;
}
int main(void) {
IOS;
cin >> n >> k;
for (int i = 1; i<=n; ++i) {
cin >> a[i];
b.push_back(a[i]);
}
sort(b.begin(), b.end());
b.erase(unique(b.begin(), b.end()), b.end());
for (int i = 1; i<=n; ++i) a[i] = f(a[i]);
int l = 1, r = n;
while(l<r) {
int mid = (l+r+1)>>1;
if (check(mid)) l = mid;
else r = mid-1;
}
cout << l << endl;
return 0;
}
B 尺取
尺取法的簡單應用, 枚舉右端點,使用一個變量\(cnt\)進行計數, \(Q\)中的數第一次出現的時候\(cnt\)++,當\(cnt\)的值等於\(Q\)中不同的數字的個數的時候,說明這是一個合法區間。移動左指針的時候如果\(Q\)中的數的出現次數變為\(0\)就\(cnt\)--。
const int maxn = 1e5+10;
const int maxm = 1e6+10;
int n, q, a[maxn], c[maxn], f[maxn];
int main(void) {
IOS;
while(cin >> n >> q && (n||q)) {
for (int i = 1; i<=n; ++i) cin >> a[i];
while(q--) {
int m, s = 0; cin >> m;
for (int i = 1, num; i<=m; ++i) {
cin >> num;
if (!f[num]) ++s;
f[num] = 1;
}
int l = 1, ans = INF, cnt = 0;
for (int i = 1; i<=n; ++i) {
if (f[a[i]] && ++c[a[i]]==1) ++cnt;
while(cnt>=s) {
ans = min(ans, i-l+1);
if (!f[a[l]]) ++l;
else if (--c[a[l++]]==0) --cnt;
}
}
cout << ans << endl;
clr(c, 0); clr(f, 0);
}
}
return 0;
}
C 二分
本題要求所有區間長度不小於k的最大的區間中位數。我們可以二分區間的中位數\(x\), 那么大於\(x\)的區間中位數肯定不少於\(k\),可以發現隨着\(x\)的變化,大於\(x\)的區間中位數的個數變化是有單調性的。
如果用二分來做的話,如何\(check(mid)\)呢?將序列預處理一下,大於等於二分的值\(x\)的數字設成1,反之設成\(-1\),如果一個區間的和大於\(0\),那么它的中位數必定不小於\(x\),問題就變成了是否有區間的區間和大於0,也相當於判斷序列內長度不小於\(k\)的最大的區間和是否大於0,通過枚舉右端點,減去一個距離它大於\(k\)的左端點中的最小值,求其中的最大值即可。
const int maxn = 2e5+10;
const int maxm = 1e6+10;
int n, m, a[maxn], c[maxn];
bool check(int x) {
clr(c, 0);
for (int i = 1; i<=n; ++i) {
if (a[i]>=x) c[i] = 1;
else c[i] = -1;
c[i] += c[i-1];
}
int minn = 0, maxx = -INF;
for (int i = m; i<=n; ++i) {
minn = min(minn, c[i-m]);
maxx = max(maxx, c[i]-minn);
}
return maxx>0;
}
int main(void) {
IOS;
cin >> n >> m;
for (int i = 1; i<=n; ++i) cin >> a[i];
int l = 1, r = n;
while(l<r) {
int mid = (l+r+1)>>1;
if (check(mid)) l = mid;
else r = mid-1;
}
cout << l << endl;
return 0;
}
D 二進制枚舉,折半枚舉
本題詢問的是子集問題,因為\(n\)最大為\(35\),所以我們直接把\(n\)拆成兩部分,預處理其中的一半,然后枚舉另一半找其在預處理的結果中的前驅和后繼即可。
const int maxn = 1e6+10;
const int maxm = 1e6+10;
ll a[maxn];
P b[maxn];
int tot = 0;
ll aabs(ll x) {
return x>=0 ? x:-x;
}
int main(void) {
int n;
while(cin >> n && n) {
tot = 0;
for (int i = 1; i<=n; ++i) cin >> a[i];
int n1 = n/2, n2 = n-n1;
P ans = P(1e18, 1e18);
for (int i = 1; i<1<<n1; ++i) {
++tot;
b[tot] = {0, 0};
for (int j = 0; j<n1; ++j)
if (i>>j&1) b[tot].x += a[j+1], ++b[tot].y;
ans = min(ans, P(aabs(b[tot].x), b[tot].y));
}
sort(b+1, b+tot+1);
for (int i = 1; i<1<<n2; ++i) {
ll sum = 0; int cnt = 0;
for (int j = 0; j<n2; ++j)
if (i>>j&1) sum += a[j+1+n1], ++cnt;
ans = min(ans, P(aabs(sum), cnt));
int p = lower_bound(b+1, b+tot+1, P(-sum, cnt))-b;
P res1 = P(b[p].x+sum, b[p].y+cnt);
ans = min(ans, P(aabs(res1.x), res1.y));
if (--p>0) {
P res2 = P(b[p].x+sum, b[p].y+cnt);
ans = min(ans, P(aabs(res2.x), res2.y));
}
}
cout << ans.x << ' ' << ans.y << endl;
}
return 0;
}
E 二分
單調性顯然。通過枚舉\(b_i\),二分求\(a_i\)中和\(b_i\)的乘積大於二分的數的數量即可。
const int maxn = 5e5+10;
const int maxm = 1e6+10;
int n, k;
ll a[maxn], b[maxn];
bool check(ll x) {
ll sum = 0;
for (int i = 1; i<=n; ++i) {
sum += n-(lower_bound(a+1, a+n+1, (x+b[i]-1)/b[i])-a)+1;
}
return sum>=k;
}
int main(void) {
IOS;
cin >> n >> k;
for (int i = 1; i<=n; ++i) cin >> a[i] >> b[i];
sort(a+1, a+n+1);
ll l = 1, r = 2e18;
while(l<r) {
ll mid = (l+r+1)>>1;
if (check(mid)) l = mid;
else r = mid-1;
}
cout << l << endl;
return 0;
}
F 二分
暴力枚舉\(2,3,5\)的冪次乘積, 然后二分。
const int maxn = 1e6+10;
ll ls[maxn], tot;
int main(void) {
IOS;
__int128 m = 2e18;
for (__int128 i = 1; i<m; i*=2)
for (__int128 j = 1; i*j<m; j*=3LL)
for (__int128 k = 1; i*j*k<m; k*=5LL)
ls[tot++] = (ll)i*j*k;
sort(ls, ls+tot);
int __; cin >> __;
while(__--) {
ll n; cin >> n;
cout << *lower_bound(ls+1, ls+tot, n) << endl;
}
return 0;
}
G 歸並排序求逆序數
const int maxn = 5e5+10;
const int maxm = 1e6+10;
ll ans;
int a[maxn], b[maxn];
void msort(int l, int r) {
if (l>=r) return;
int mid = (l+r)>>1;
msort(l, mid);
msort(mid+1, r);
int l1 = l, l2 = mid+1, tot = l;
while(l1<=mid || l2<=r) {
if (l2>r || (l1<=mid && a[l1]<a[l2])) b[tot++] = a[l1++];
else {
ans += mid-l1+1;
b[tot++] = a[l2++];
}
}
for (int i = l; i<=r; ++i) a[i] = b[i];
}
int main(void) {
IOS;
int n;
while(cin >> n && n) {
ans = 0;
for (int i = 1; i<=n; ++i) cin >> a[i];
msort(1, n);
cout << ans << endl;
}
return 0;
}