zzuli暑假集训专题赛1
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;
}