<Atcoder - 091C> 掃描線
https://atcoder.jp/contests/abc091/tasks/arc092_a
題意:
有n個紅點,n個藍點,所選的紅點和藍點能夠成友好的條件是:紅點的橫縱座標分別小於藍點的橫縱座標。取過的點不能重複取,問最多能構成幾對友好。
思路:
定義結構體,將所有紅點藍點統一按照x升序排列。然從後往前掃,碰到藍點就傳入set,碰到紅點時,就利用set的upper_bound返回縱座標大於該紅點縱座標且離他最近的藍點,因爲x升序過了,故只看y,用過的藍點清除出set即可。
AC代碼:
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int maxx = 2e2 + 7;
const int Inf = 1 << 30;
const LL INF = 1LL << 60;
int n, ans;
set <int> sst;
struct PP {
int x, y;
bool color;
friend bool operator < (PP u, PP v) {
return u.x < v.x;
}
} p[maxx];
void Init() {
cin >> n;
for(int i = 1; i <= n; i++) {
cin >> p[i].x >> p[i].y;
p[i].color = 0;
}
for(int i = 1; i <= n; i++) {
cin >> p[i + n].x >> p[i + n].y;
p[i + n].color = 1;
}
sort(p + 1, p + 2 * n + 1);
}
void Scan() {
set <int>::iterator id;
for(int i = 2 * n; i >= 1; i--) {
if(p[i].color) sst.insert(p[i].y);
else {
id = sst.upper_bound(p[i].y);
if(id != sst.end()) {
ans++;
sst.erase(id);
}
}
}
cout << ans << endl;
}
int main() {
Init();
Scan();
return 0;
}
<Atcoder - 062D> 優先隊列 + 前後綴和
https://atcoder.jp/contests/abc062/tasks/arc074_b
題意:
從3n個數中精確刪除n項,使得剩下的2n個數前n項和與後n項和的差值最大,求這個最大差值。
思路:
這題類似於一個插板法,無論怎麼刪除,原來元素的相對位置是不發生改變的。原來3n個數的序列的前n項都不可能進入新的2n個數的序列的後n項,同理,原來3n個數的序列的後n項都不可能進入新的2n個數的序列的前n項。那麼不斷用中間的n項去置換兩邊的n項,便能達到刪除的目的,被中間的n項置換出來的數就相當於被刪除了。那麼置換條件顯然是把中間n項中大的數往前置換,小的數往後置換,這樣才能使得前n項和後n項的差值最大。那麼我們可以在原始的3n項的序列內,前n項壓進升序(即隊頭最小)的優先隊列que,後n項壓進降序(即隊頭最大)的優先隊列qua。記前n項的前綴和爲suml[n] = s1,後n項的後綴和爲sumr[n * 2 + 1] = s2。那麼我們研究中間的n項如何置換:
1)令l = n + 1,r = 2n,在 l <= r 內,碰到比que.top()大的,就發生置換,改變原來的隊頭(比原來那個隊頭大的不是直接成爲新的隊頭,把它壓進隊列,讓隊頭變成新的這n個數中一個新的最小值),改變前綴和s1。
2)令l = n + 1,r = 2n,在 l <= r 內,碰到比qua.top()小的,就發生置換,改變原來的隊頭(比原來那個隊頭小的不是直接成爲新的隊頭,把它壓進隊列,讓隊頭變成新的這n個數中一個新的最大值),改變後綴和s2。
對於中間的n項,由於不管怎麼刪除相對位置都不會改變(應該誰在誰前面,就誰在誰前面),一定是前x個置換進入前n項,後n-x個置換進入後n項(當然也可能不發生置換),這也是記錄前後綴和的原因。所以對於 n <= i <= 2n,相當於在 i 這個位置插一個隔板,隔板之前的元素向前發生置換,隔板之後的元素向後發生置換。維護前後綴和suml[i] - sumr[i + 1]的最大值就是答案。
AC代碼:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxx = 3e5 + 7;
const int Inf = 1 << 30;
const ll INF = 1ll << 60;
int n;
ll a[maxx];
ll suml[maxx], sumr[maxx];
ll s1, s2;
priority_queue <ll, vector<ll>, less<ll> > qua; //降序, 隊頭大
priority_queue <ll, vector<ll>, greater<ll> > que; //升序, 隊頭小
int main() {
scanf("%d", &n);
for(int i = 1; i <= n * 3; i++) {
scanf("%lld", &a[i]);
if(i <= n) {
s1 += a[i];
que.push(a[i]);
}
else if(i >= 2 * n + 1) {
s2 += a[i];
qua.push(a[i]);
}
}
suml[n] = s1; //前n項和
sumr[n * 2 + 1] = s2; //後n項和
int l = n + 1, r = 2 * n;
//只能精確刪除n項, 無論怎麼刪, 後n項都不可能進到前n項去
//故讓中間的n項去往兩邊置換
//中間的n項必然是前x個置換到前n項內, 後n-x個置換到後n項內(或者不發生置換)
//故分別維護前綴和、後綴和, 前綴和與後綴和的差維護一個最大值
while(l <= r) {
if(a[l] > que.top()) { //每次置換出最小的隊頭
s1 += a[l];
s1 -= que.top();
que.pop();
que.push(a[l]);
}
suml[l] = s1; //維護前綴和
l++;
}
l = n + 1, r = 2 * n;
while(l <= r) {
if(a[r] < qua.top()) { //每次置換出最大的隊頭
s2 += a[r];
s2 -= qua.top();
qua.pop();
qua.push(a[r]);
}
sumr[r] = s2; //維護後綴和
r--;
}
ll ans = -INF;
for(int i = n; i <= n * 2; i++) ans = max(ans, suml[i] - sumr[i + 1]);
printf("%lld\n", ans);
}