一些很妙的思維題
一些很妙的思維題
P4643 [國家集訓隊]阿狸和桃子的遊戲
\(Describe\)
有一張 \(n\) 個點 \(m\) 條邊的圖,點有點權,邊有邊權。
先手後手輪流染黑白兩色,最後的得分是自己染的點權和 + 兩端均為自己的顏色的邊權和。
雙方都希望自己的得分 - 對手的得分最大,求結果。
\(1 \le n \le 10000, 0 \le m \le 100000\)
\(Solution\)
這題巨妙無比,真是妙蛙種子吃著妙脆角,秒進了米奇妙妙屋,秒到家了。
你可以考慮把每一條邊都分到他所連的 \(2\) 個點上,各一半。
然後對於每條邊,我們有 \(2\) 種可能,如下:
-
\(2\) 邊都是 \(A\)
-
如果 \(2\) 邊不一樣,那麼一邊都是一半,相互抵消。
\(Code\)
#include <iostream> #include <algorithm> #include <stdio.h> using namespace std; const int maxn = 10010; int n, m, u, v, w; double a[maxn]; double cmp(double x, double y) { return x >= y; } int main() { scanf("%d%d", &n, &m); for(int i = 1; i <= n; ++ i) { scanf("%lf", &a[i]); } for(int i = 1; i <= m; ++ i) { scanf("%d%d%d", &u, &v, &w); a[u] += (double) w / 2; a[v] += (double) w / 2; } sort(a + 1, a + n + 1, cmp); double ans = 0; for(int i = 1; i <= n; ++ i) { if(i % 2) { ans += a[i]; } else { ans -= a[i]; } } printf("%d", (int) ans); return 0; }
\(Mushroom\) 的區間
\(Describe\)
給你若干個區間 \([l_i, r_i]\) ,每次可以對一個區間進行整體異或操作。
區間初始全 \(0\),問你最後可以形成多少種不同的區間。
\(Solution\)
考慮什麼樣的區間是沒有貢獻的。
假設對於區間 \([l, r]\) ,我們考慮如果存在以下三個區間,那麼這個區間是無用的。
-
[i, l - 1]
-
[r + 1, j]
-
[i, j]
那麼通過不同的組合,有無區間 \([l, r]\) 是沒有意義的。
那麼用並查集維護端點就行了。
\(Code\)
#include <bits/stdc++.h> using namespace std; const int maxn = 100010; const int XRZ = 1e9 + 7; const int maxm = (1 << 21); int n, m, ans = 1, l, r, f[maxn]; int find(int x) { return f[x] == x ? x : f[x] = find(f[x]); } int main() { freopen("section.in", "r", stdin); freopen("section.out", "w", stdout); scanf("%d%d", &n, &m); for(int i = 1; i <= n; ++ i) { f[i] = i; } for(int i = 1; i <= m; ++ i) { scanf("%d%d", &l, &r); int x = find(l), y = find(r + 1); if(x == y) { continue; } f[x] = y; ans = (ans * 2) % XRZ; } printf("%d", ans); return 0; }
P3545 [POI2012]HUR-Warehouse Store
\(Describe\)
一共 \(n\) 天,每天上午會進 \(A_i\) 的物品,中午會有一個客人想要買走 \(B_i\) 的物品,當然你也可以選擇不買,問你最後最多可以交易多少次。
資料範圍 : \(1 <= n <= 250000, 0 <= A_i, B_i <= 10^9\)
\(Solution\)
首先,我們看到題目先會想到 \(01\) 揹包。是個正確的作法,但是肯定過不了。
然後,我們考慮到這一定是個貪心/推式子的題目。
但是推式子的題目一般的特性在這裡顯然不符合(推式子的題目一般不會讓你選擇)
那接下來考慮怎麼去貪心。
第一想法是我們選最小的肯定是最優的,因為就算選了這個最小的而導致其他的不可以選,那也最多隻能導致一個不可以選。
因為如果可以導致多個不可以選的話,選了次小的哪一個也會導致更多的不可以選。
所以選擇最小的策略是對的,\(sort\) 一般的複雜度我們也可以接受,但是問題來了,我們那些數可以取?
取完當前最小的,那麼它前面的一些數說不定不可以再去。
那麼處理這些的複雜度就不穩定了,可以構造資料卡掉。
接下來我們想每天上午進的貨物會對什麼有影響。
這個很顯然,它可以對他後面的所有數產生影響,因為到貨了就可以賣給別人了。
那是不是可以這麼考慮,從後往前去跑,每次去當前可以取的最小的。
因為從後完全就去除了它的後效性,所有正確性也是對的。
但是新增一個數 \(A_i\) 可能很大,一次可以處理多個數。
那你就必須保證後面整個序列是有序的,而且還要維護一個字尾和。
我們考慮維護這個有序數列的複雜度最優,那就是往前加一個數,就把這個數加入到數列裡面去。
這樣的複雜度最劣是 \(O(n^2)\) 的,還是會炸。
那我們是不是無法從一些順序上去找到優化的空間。
我們就可以考慮去莽,然後支援撤銷,這樣也可以保證正確性。
具體的話是這樣實現的:
從前往後讀入,每次可以取就取,並且把這些取過的記錄在單調佇列中。
如果當前這個放不下,我們就把他和佇列首的元素做比較,不斷更新即可。
根據我們的第二個想法,每個前面取過的如果彈出來,因為他的後效性,肯定是可以服務後面的。
那麼就結束了。
\(Code\)
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int maxn = 250010;
int n, sum, ans, book[maxn];
priority_queue<pair<int, int>, vector<pair<int, int> > > Q;
struct node {
int a, b, id;
} e[maxn];
int cmp(node x, node y) {
return x.b < y.b;
}
signed main() {
scanf("%lld", &n);
for(int i = 1; i <= n; ++ i) {
scanf("%lld", &e[i].a);
e[i].id = i;
}
for(int i = 1; i <= n; ++ i) {
scanf("%lld", &e[i].b);
}
for(int i = 1; i <= n; ++ i) {
sum += e[i].a;
if(e[i].b < sum) {
Q.push(make_pair(e[i].b, e[i].id));
++ ans;
book[i] = 1;
sum -= e[i].b;
}
else if(! Q.empty() && Q.top().first > e[i].b) {
book[Q.top().second] = 0;
book[i] = 1;
sum += e[Q.top().second].b - e[i].b;
Q.pop(); Q.push(make_pair(e[i].b, e[i].id));
}
}
printf("%lld\n", ans);
for(int i = 1; i <= n; ++ i) {
if(book[i]) {
printf("%d ", i);
}
}
return 0;
}