1. 程式人生 > 其它 >4.11省選模擬

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); 
}