4.11省选模拟

4.11省选模拟

鸽子来更博客了。昨天太累的就咕了,什么时候有时间再补上吧。。

T1 分头行动

我的 dp 状态有点奇怪,导致根本没有优化的空间,相当于是把自己送进了坟墓。。

首先一个很显然的性质,最后的序列一定是一个是前缀的最大值,另一个是不确定的。

那么我们由 \(f(i,j)\) 表示前 \(i\) 个物品,一个序列的最后一项是 \(j\),另一个序列的最后一项是 \(s[i]\),即前缀最大值。

那么结合上面的性质,我们的转移可以分成下面几种。

  1. \(a[i]>s[i-1]\) 那么肯定是放在另一个序列里面的,所以 \(f[i][j] +=a[i]\)

  2. \(a[i] \le s[i-1]\) 又分为几种子情况

    1.\(a[i]\le j\),那么其实相当于一个区间加等差数列 \(f[i][j] += j\)

    2.\(a[i] \ge j\)\(f[i][a[i]]=\min(f[i-1][j]+a[i])\),其实相当于一个区间取反

    \(f[i][j]+=s[i-1]\),这个相当于是选另外的那个序列

然后我们接着进行观察这个东西,发现其实就是要求你进行区间加等差数列,区间取 min,区间加和。那么区间加等差数列的其实可以抽象成一条直线,然后维护这个凸包。利用分块接着进行维护即可。

//editor : DRYAYST
//Wo shi ge da SHA BI
#include<bits/stdc++.h>
#define g() getchar()
#define il inline
#define ull unsigned long long
#define eps 1e-10
#define ll long long
#define pa pair<int, int>
#define for_1(i, n) for(int i = 1; i <= (n); ++i)
#define for_0(i, n) for(int i = 0; i < (n); ++i)
#define for_xy(i, x, y) for(int i = (x); i <= (y); ++i)
#define for_yx(i, y, x) for(int i = (y); i >= (x); --i)
#define for_edge(i, x) for(int i = head[x]; i; i = nxt[i])
#define int long long
#define DB double
#define ls (p<<1)
#define rs (p<<1|1)
#define m_p make_pair
#define fi first
#define se second
using namespace std;
const int N = 71000, INF = 1e18, mod = 1e9 + 7;
il int qpow(int x, int k) {int ans = 1; while(k) {if(k & 1) ans = ans * x % mod; x = x * x % mod; k >>= 1; } return ans; }
il int Add(int x, int y) {return (x += y) %= mod;}
il int Del(int x, int y) {return (x = x - y + mod) % mod;}
il int Mul(int x, int y) {return x * y % mod;}
il int inv(int x) {return qpow(x, mod - 2); }
inline int re() {
    int x = 0, p = 1;
    char ch = getchar();
    while(ch > '9' || ch < '0') {if(ch == '-') p = -1; ch = getchar();}
    while(ch <= '9' and ch >= '0') {x = (x << 3) + (x << 1) + (ch ^ 48); ch = getchar();}
    return x * p;
}
const int B = 300, M = N/B; 

int n, top; 
int a[100000], f[100000], sta[10000][B], Min[10000];
int ad1[100000], ad2[100000], s[100000];

il DB Calck(int x, int y) {return ((f[x] - f[y]) * 1.0) / ((x - y) * 1.0);}
il void TZ(int x) {while(Min[x] > 1 and f[sta[x][Min[x]]] + sta[x][Min[x]] * ad2[x] >= f[sta[x][Min[x] - 1]] + sta[x][Min[x] - 1] * ad2[x]) Min[x]--; }

il void Build(int id) {//OK,对于权值分块,构建关于等差数列的凸包,最小值,所以是下凸壳
    int l = id * B, r = B * (id + 1) - 1, top = 0;
    for(int i = l; i <= r; ++i) {
        if(top > 0 and f[i] >= f[sta[id][top]]) continue;
        while(top > 1 and Calck(i, sta[id][top]) <= Calck(sta[id][top], sta[id][top - 1])) top--; 
        sta[id][++top] = i; 
    }
    Min[id] = top; 
}

signed main() { 
     freopen("separate.in","r",stdin); freopen("separate.out","w",stdout); 
    n = re(); for_1(i, n) a[i] = re(); 
    memset(f, 0x3f, sizeof(f)); f[0] =0;
//    f[0] = 0; 
    for(int i = 0; i <= n / B; ++i) Build(i); 
    for(int i = 1; i <= n; ++i) s[i] = max(s[i-1], a[i]); 
    for(int i = 1; i <= n; ++i) {
        if(a[i] > s[i-1]) for(int j = 0; j <= n / B; ++j) ad1[j] += a[i]; 
        else {
            int id = a[i] / B; int l = id * B, r = (id + 1) * B - 1; 
            for(int j = l; j <= r; ++j) f[j] += ad1[id] + ad2[id] * j;  int mi = INF; 
            ad1[id] = ad2[id] = 0; 
            for(int j = 0; j < id; ++j) mi = min(mi, f[sta[j][Min[j]]] + ad1[j] + ad2[j] * sta[j][Min[j]]); 
            for(int j = l; j <= a[i]; ++j) mi = min(mi, f[j]); 
            
            for(int j = a[i]; j <= r; ++j) f[j] += j;f[a[i]] = min(f[a[i]], mi + a[i]); 
            for(int j = id + 1; j <= n/B; ++j) ad2[j]++, TZ(j); 
            for(int j = l; j < a[i]; ++j) f[j] += s[i-1]; 
            for(int j = 0; j < id; ++j) ad1[j] += s[i-1]; 
            Build(id); 
        }
    }
    int ans = INF; 
    for(int i = 0; i <= n; ++i) ans = min(ans, f[i] + ad1[i/B] + ad2[i/B] * i);
    cout<<ans<<endl;
}
/*
19
15 19 9 16 3 15 5 4 6 8 8 15 10 2 14 16 11 14 17
*/

T2 乱数假文

这个东西比较的神奇。好了下一题。

T3 你的世界

是个题单里面的原题。但是我没做诶。

首先可以观察到的是这个每次配对的贡献如果看成一个函数的话,那么就是一个下凸的函数。因为一开始选择的点肯定是比较小的,后面的选择是越来越大的。因此这个函数具有凸性。

然后我们可以看到的是要求选 k 次,然后发现这个函数还是个凸性的,因此想到 wqs 二分进行判断。那么剩下的就是类似于一个二分图匹配的过程了,但是很明显这个复杂度是肯定过不去的。所以考虑模拟费用流,每次先选择最小的那个 a 和 b 进行配对,然后利用反悔贪心,把负的权值加进堆里面,这样就做完了。

//editor : DRYAYST
//Wo shi ge da SHA BI
#include<bits/stdc++.h>
#define g() getchar()
#define il inline
#define ull unsigned long long
#define eps 1e-10
#define ll long long
#define pa pair<int, int>
#define for_1(i, n) for(int i = 1; i <= (n); ++i)
#define for_0(i, n) for(int i = 0; i < (n); ++i)
#define for_xy(i, x, y) for(int i = (x); i <= (y); ++i)
#define for_yx(i, y, x) for(int i = (y); i >= (x); --i)
#define for_edge(i, x) for(int i = head[x]; i; i = nxt[i])
#define int long long
#define DB double
#define ls (p<<1)
#define rs (p<<1|1)
#define m_p make_pair
#define fi first
#define se second
using namespace std;
const int N = 1e6 + 10, INF = 0x7f7f7f7f, mod = 1e9 + 7;
il int qpow(int x, int k) {int ans = 1; while(k) {if(k & 1) ans = ans * x % mod; x = x * x % mod; k >>= 1; } return ans; }
il int Add(int x, int y) {return (x += y) %= mod;}
il int Del(int x, int y) {return (x = x - y + mod) % mod;}
il int Mul(int x, int y) {return x * y % mod;}
il int inv(int x) {return qpow(x, mod - 2); }
inline int re() {
    int x = 0, p = 1;
    char ch = getchar();
    while(ch > '9' || ch < '0') {if(ch == '-') p = -1; ch = getchar();}
    while(ch <= '9' and ch >= '0') {x = (x << 3) + (x << 1) + (ch ^ 48); ch = getchar();}
    return x * p;
}

int n, k, ans, sum, cnt; 
int a[N], b[N]; 
priority_queue<pa, vector<pa>, greater<pa> > q; 

il bool check(int x) {
    while(q.size()) q.pop(); cnt = sum = 0; 
    for(int i = 1; i <= n; ++i) {
        q.push(m_p(a[i], 0)); 
        if(q.top().first + b[i] + x <= 0) {
            sum += q.top().first + b[i] + x; 
            if(!q.top().second) cnt++; q.pop(); 
            q.push(m_p(-(b[i] + x), 1)); 
        }
    }
    return cnt >= k; 
}

signed main() {
     freopen("world.in","r",stdin);  freopen("world.out","w",stdout); 
    n = re(); k = re(); for_1(i, n) a[i] = re(); for_1(i, n) b[i] = re(); 
    int l = -INF, r = INF; 
    while(l <= r) { 
        int mid = (l + r) >> 1; 
        if(check(mid)) ans = mid, l = mid + 1; 
        else r = mid - 1; 
    }
    check(ans); cout<<(sum - k * ans); 
}
posted @ 2022-04-11 22:52  Zwaire  阅读(51)  评论(2)    收藏  举报