1. 程式人生 > 其它 >題解 CF618F Double Knapsack

題解 CF618F Double Knapsack

首先,這個要求的解的形式太過自由,選擇一個子集不是很好處理。於是我們可以套路地對其加以限制,變成選擇兩段區間,使他們和相同。

令 $sa_i$ 和 $sb_i$ 分別表示 $a$ 和 $b$ 序列的字首和,此處我們假設 $sa_n\le sb_n$。

那麼我們可以列舉 $i$,找到一個最大的滿足 $sb_j\le sa_i$ 的 $j$,由於 $sa_n\le sb_n$,所以容易發現,$sa_i-sb_j \in [0,n-1]$。對於 $n+1$ 種 $sa_i-sb_j$ 只有 $n$ 種取值,根據抽屜原理可知肯定有兩對 $sa_i-sb_j$ 是相同的:$sa_{i_1}-sb_{j_1}=sa_{i_2}-sb_{j_2}$,可以得到 $sa_{i_2}-sa_{i_1}=sb_{j_2}-sb_{j_1}$ 所以所求答案的區間即為 $a[i_1+1...i_2]$ 和 $b[j_1+1...j_2]$。

#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define fi first
#define se second
const int N = 1e6 + 5;
int n, a[N], b[N], buki[N], bukj[N], ok;
ll sa[N], sb[N];

int main() {
    memset(buki, -1, sizeof(buki));
    memset(bukj, -1, sizeof(bukj));
    scanf("%d", &n);
    
for(int i = 1; i <= n; i++) scanf("%d", &a[i]), sa[i] = sa[i - 1] + a[i]; for(int i = 1; i <= n; i++) scanf("%d", &b[i]), sb[i] = sb[i - 1] + b[i]; if(sa[n] > sb[n]) { ok = 1; for(int i = 1; i <= n; i++) swap(a[i], b[i]), swap(sa[i], sb[i]); }
for(int i = 0, j = 0; i <= n; i++) { while(j + 1 <= n && sa[i] >= sb[j + 1]) j++; if(buki[sa[i] - sb[j]] != -1) { if(!ok) { printf("%d\n", i - buki[sa[i] - sb[j]]); for(int k = buki[sa[i] - sb[j]] + 1; k <= i; k++) printf("%d ", k); printf("\n%d\n", j - bukj[sa[i] - sb[j]]); for(int k = bukj[sa[i] - sb[j]] + 1; k <= j; k++) printf("%d ", k); printf("\n"); } else { printf("%d\n", j - bukj[sa[i] - sb[j]]); for(int k = bukj[sa[i] - sb[j]] + 1; k <= j; k++) printf("%d ", k); printf("\n%d\n", i - buki[sa[i] - sb[j]]); for(int k = buki[sa[i] - sb[j]] + 1; k <= i; k++) printf("%d ", k); printf("\n"); } return 0; } buki[sa[i] - sb[j]] = i, bukj[sa[i] - sb[j]] = j; } return 0; }