zoukankan      html  css  js  c++  java
  • 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;
    }
    
  • 相关阅读:
    常用的VI/VIM命令
    那些年学过的一些算法
    huffman编码
    好用java库(一):java date/time api:jodatime
    linux启动
    ubuntu学习方式
    地址
    各种各样的软件
    jquery文件
    C变量与数据
  • 原文地址:https://www.cnblogs.com/shuitiangong/p/15049396.html
Copyright © 2011-2022 走看看