【题录】ICPC2020 小米邀请赛决赛

B.

分治。每次分治一个区间的时候统计所有跨过左右区间的区间对答案的贡献。令一个区间的左右端点分别为 a, b。那么这个区间的答案为 max(m[a], m[b], f[a] + f[b])。其中 m[x] 为 x 到中点的最大子段和,f[x] 为 x 到中点的最大前缀和。使用两个指针向两边扫,每次挪动一个指针 a 的时候统计所有区间 [a, x] (x 属于 [mid + 1, b)) 的答案。这样每个区间一定会被统计到一次。每次选择挪动 m[x] 更小的指针 x ,可以保证 m[x] > m[y] (以 x 在左侧为例,y 属于 [mid + 1, b))。

#include <bits/stdc++.h>
using namespace std;
#define ULL unsigned long long
#define LL long long
#define maxn 250000
#define INF 1ll << 60
LL n, a[maxn], lsum[maxn], rsum[maxn], lmax[maxn], rmax[maxn];
ULL lsums[maxn], rsums[maxn];

int Getl(int l, int r, LL t) {
    if(lsum[l] < t) return l - 1;
    while(l < r) {
        int mid = (l + r) >> 1;
        if(mid + 1 <= r) mid += 1;
        if(lsum[mid] >= t) l = mid;
        else r = mid - 1;
    }
    return l;
}

int Getr(int l, int r, LL t) {
    if(rsum[r] < t) return r + 1;
    while(l < r) {
        int mid = (l + r) >> 1;
        if(rsum[mid] >= t) r = mid;
        else l = mid + 1;
    }
    return l;
}

ULL Divide(int l, int r) {
    if(l == r) return a[l];
    int mid = (l + r) >> 1;
    ULL ans = Divide(l, mid) + Divide(mid + 1, r);
    lmax[mid + 1] = -INF; lsum[mid + 1] = -INF;  lsums[mid + 1] = 0;
    LL sum = 0, maxs = 0;
    for(int i = mid; i >= l; i --) {
        maxs += a[i]; 
        lmax[i] = max(lmax[i + 1], maxs);
        maxs = max(0ll, maxs);
        sum += a[i];
        lsum[i] = max(lsum[i + 1], sum);
        lsums[i] = lsums[i + 1] + lsum[i];
    }
    sum = 0, maxs = 0;
    rmax[mid] = -INF, rsum[mid] = -INF; rsums[mid] = 0;
    for(int i = mid + 1; i <= r; i ++) {
        maxs += a[i]; 
        rmax[i] = max(rmax[i - 1], maxs);
        maxs = max(0ll, maxs);
        sum += a[i];
        rsum[i] = max(rsum[i - 1], sum);
        rsums[i] = rsums[i - 1] + rsum[i];
    }
    int ll = mid, rr = mid + 1;
    ULL ans1 = 0; 
    while(ll >= l || rr <= r) {
        if(rr > r || (ll >= l && lmax[ll] <= rmax[rr])) {
            int p = Getr(mid + 1, rr - 1, lmax[ll] - lsum[ll]);
            if(mid + 1 <= rr - 1) ans1 += (p - 1 - mid) * lmax[ll] + (rr - p) * lsum[ll] + rsums[rr - 1] - rsums[p - 1];
          ll --;
        }
        else if(ll < l || (rr <= r && rmax[rr] <= lmax[ll])) {
            int p = Getl(ll + 1, mid, rmax[rr] - rsum[rr]);
            if(ll + 1 <= mid) ans1 += (mid - p) * rmax[rr] + (p - ll) * rsum[rr] + lsums[ll + 1] - lsums[p + 1];
            rr ++;
        }
    }
    ans += ans1;
    return ans;
}

int main() {
    ios::sync_with_stdio(0); cin.tie(0);
    cin >> n;
    for(int i = 1; i <= n; i ++) cin >> a[i];
    cout << Divide(1, n) << endl;
    return 0;
}

G.

状压,每次枚举子集 S 为拓扑序最底层的点,判断是否满足条件。满足条件则计入答案。

#include <bits/stdc++.h>
using namespace std;
#define maxn 200000
int n, m, bits[maxn], mark[maxn], rec[maxn];
LL f[maxn];

int read() {
    int x = 0, k = 1;
    char c; c = getchar();
    while(c < '0' || c > '9') { if(c == '-') k = -1; c = getchar(); }
    while(c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar();
    return x * k;
}

struct edge {
    int cnp = 1, to[maxn], last[maxn], head[maxn];
    void add(int u, int v) {
        to[cnp] = v, last[cnp] = head[u], head[u] = cnp ++;
        to[cnp] = u, last[cnp] = head[v], head[v] = cnp ++;
    }
}E;

int main() {
    n = read(), m = read();
    for(int i = 1; i <= m; i ++) {
        int x = read(), y = read();
        E.add(x, y);
    }
    bits[0] = 1;
    for(int i = 1; i < n; i ++) bits[i] = bits[i - 1] << 1;
    for(int i = 0; i < bits[n]; i ++) {
        for(int j = 1; j <= m; j ++)
            if(bits[j - 1] & i) {
                for(int k = E.head[j]; k; k = E.last[k]) {
                    int v = E.to[k];
                    if(bits[v - 1] & i) mark[i] = 0;
                    else rec[i] |= bits[v - 1]; 
                } 
            }
    }
    f[0] = 1;
    for(int S = 1; i < bits[n]; i ++) 
        for(int S1 = (S - 1) & S; S1; S1 = (S1 - 1) & S) {
            if(!mark[S1]) continue;
            S2 = S ^ S1;
            if((S2 | rec[S1]) > rec[S1]) continue;
            f[S] += f[S2];
        }
    printf("%lld\n", f[bits[n] - 1]);
    return 0;
}

 

J.

书本不会掉下去的条件即为任何一本书,它上面的所有书的重心落在这本书上(对桌子也一样)。考虑让其中伸出去的书最远,要么是像例1一样一本一本的往外面叠,要么像例2一样用一些书压住另一本,再让这一本尽可能的往外伸。还是用状压,枚举最下面的一本书是哪一本,分以上两种情况讨论一下即可。

#include <bits/stdc++.h>
using namespace std;
#define db double
#define maxn 3000000
int n, l[maxn], bits[maxn], w[maxn], m[maxn];
db f[maxn];

int read() {
    int x = 0, k = 1;
    char c; c = getchar();
    while(c < '0' || c > '9') { if(c == '-') k = -1; c = getchar(); }
    while(c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar();
    return x * k;
}

db DP(int S) {
    if(f[S] >= 0) return f[S];
    if(!S) return f[S] = 0;
    for(int i = 1; i <= n; i ++) {
        if(!(bits[i - 1] & S)) continue;
        int S1 = S ^ bits[i - 1];
        db L = DP(S1);
        db l1 = (db) l[i] / 2 - (m[S1] * ((db) l[i] / 2) / m[S]);
        f[S] = max(f[S], l1 + L);
        l1 = (db) m[S1] * ((db) l[i] / 2) / m[S] + (db) l[i] / 2;
        f[S] = max(f[S], l1);
    }
    return f[S];
}

int main() {
    n = read();
    for(int i = 1; i <= n; i ++) l[i] = read();
    for(int i = 1; i <= n; i ++) w[i] = read();
    bits[0] = 1;
    for(int i = 1; i <= n; i ++) bits[i] = bits[i - 1] << 1;
    for(int i = 0; i < bits[n]; i ++) f[i] = -1;
    for(int i = 0; i < bits[n]; i ++) 
        for(int j = 1; j <= n; j ++)
            if(bits[j - 1] & i) m[i] += w[j];
    printf("%.10f\n", DP(bits[n] - 1));
    return 0;
}

 

posted @ 2021-01-04 11:25  Twilight_Sx  阅读(142)  评论(0编辑  收藏  举报