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;
}
posted @ 2021-07-23 16:21  shuitiangong  阅读(182)  评论(0编辑  收藏  举报