USACO 2018 Jan 題解
USACO 2018 Jan Solution
目錄-
USACO 2018 Jan Solution
- 更好的閱讀體驗戳此進入
- 題面 Luogu 連結
- LG-P4188 [USACO18JAN]Lifeguards S
- LG-P4182 [USACO18JAN]Lifeguards P
- LG-P4181 [USACO18JAN]Rental Service S
- LG-P6111 [USACO18JAN]MooTube S
- LG-P4185 [USACO18JAN]MooTube G
- LG-P4184 [USACO18JAN]Sprinklers P
- LG-P4187 [USACO18JAN]Stamp Painting G
- LG-P4186 [USACO18JAN]Cow at Large G
-
LG-P4183 [USACO18JAN]Cow at Large P
- UPD
更好的閱讀體驗戳此進入
題面 Luogu 連結
LG-P4188 [USACO18JAN]Lifeguards S
題面
給定 $ n $ 個左閉右開的區間,在範圍 $ [0, 10^{9}] $(Luogu 上的翻譯寫錯了,多寫了個 $ 0 $)內,最大化刪去其中一個區間後剩下區間並起來的長度,求最大值。
$ 1 \le n \le 10^5 $。
Input_1
3 5 9 1 4 3 7
Output_1
7
Solution
估計你們讀完題之後也能想到線段樹的做法,線段樹做法十分顯然,離散化之後建個權值線段樹,然後所有線段插進去再每次刪一個區間求 $ \ge 1 $ 的對應的真實區間長度和,常數比較大但是是妥妥的 $ O(n \log n) $ 能過,不過有一個更好寫的做法,不難想到可以轉化為求哪個線段獨立覆蓋的範圍最小,於是考慮建立一個差分陣列,把每段區間糊上去,差分加一下之後再求個字首和,注意這裡的字首和是若 $ d_i = 1 $ 那麼加上 $ i $ 到 $ i + 1 $ 對應的真實區間的長度,否則加 $ 0 $,這樣就可以查出來每個線段的獨立覆蓋範圍,然後取最優即可,離散化是 $ O(n \log n) $,求解是線性的,常數極小。
Code
#define _USE_MATH_DEFINES
#include <bits/extc++.h>
#define PI M_PI
#define E M_E
#define npt nullptr
#define SON i->to
#define OPNEW void* operator new(size_t)
#define ROPNEW(arr) void* Edge::operator new(size_t){static Edge* P = arr; return P++;}
using namespace std;
using namespace __gnu_pbds;
mt19937 rnd(random_device{}());
int rndd(int l, int r){return rnd() % (r - l + 1) + l;}
bool rnddd(int x){return rndd(1, 100) <= x;}
typedef unsigned int uint;
typedef unsigned long long unll;
typedef long long ll;
typedef long double ld;
template< typename T = int >
inline T read(void);
struct Line{
int l, r;
friend const bool operator < (const Line &a, const Line &b){return a.l < b.l;}
}a[110000];
vector < int > pos;
int N;
int d[210000];
int sum[210000];
int main(){
N = read();
for(int i = 1; i <= N; ++i)a[i].l = read(), a[i].r = read(), pos.emplace_back(a[i].l), pos.emplace_back(a[i].r);
sort(pos.begin(), pos.end());
auto endpos = unique(pos.begin(), pos.end());
int siz = distance(pos.begin(), endpos);
for(int i = 1; i <= N; ++i)
a[i].l = distance(pos.begin(), upper_bound(pos.begin(), endpos, a[i].l)),
a[i].r = distance(pos.begin(), upper_bound(pos.begin(), endpos, a[i].r));
for(int i = 1; i <= N; ++i)d[a[i].l]++, d[a[i].r]--;
int tot(0);
for(int i = 1; i < siz; ++i){
d[i] += d[i - 1];
if(d[i])tot += pos.at(i + 1 - 1) - pos.at(i - 1);
if(d[i] == 1)sum[i] += pos.at(i + 1 - 1) - pos.at(i - 1);
sum[i] += sum[i - 1];
}int mx(-1);
for(int i = 1; i <= N; ++i)mx = max(mx, tot - (sum[a[i].r - 1] - sum[a[i].l - 1]));
printf("%d\n", mx);
fprintf(stderr, "Time: %.6lf\n", (double)clock() / CLOCKS_PER_SEC);
return 0;
}
template < typename T >
inline T read(void){
T ret(0);
short flag(1);
char c = getchar();
while(c != '-' && !isdigit(c))c = getchar();
if(c == '-')flag = -1, c = getchar();
while(isdigit(c)){
ret *= 10;
ret += int(c - '0');
c = getchar();
}
ret *= flag;
return ret;
}
LG-P4182 [USACO18JAN]Lifeguards P
題面
加強版來咯
給定 $ n $ 個左閉右開的區間,在範圍 $ [0, 10^{9}] $ 內,最大化刪去其中 $ k $ 個區間後剩下區間並起來的長度,求最大值。
$ 1 \le n \le 10^5, 1 \le k \le \min(100, n) $。
Examples
Input_1
3 2 1 8 7 15 2 14
Output_1
12
Solution
首先不難想到對於區間 $ [l_1, r_1), [l_2, r_2) $ 且 $ l_1 \le l_2 \land r_2 \le r_1 $ 那麼刪去 $ [l_2, r_2] $ 答案一定不變,於是可以直接全部刪去,並將 $ k \leftarrow k - 1 $。
然後 DP 顯然,也很好想到一個狀態 $ dp(i, k) $ 表示考慮前 $ i $ 個區間,刪去其中的 $ k $ 個的最優值,然後發現這玩意沒有固定具體怎麼刪沒法轉移,然後就想到在原來的狀態基礎上欽定必須選擇第 $ i $ 個區間,然後需要把區間優先按左端點,再按右端點排序,這樣我們就會發現最開始去掉那些區間的作用了,去掉之後現在我們剩下的區間排序後一定滿足對於 $ l $ 的升序一定也滿足 $ r $ 升序,不滿足的已經被我們刪掉了。於是此時轉移比較顯然:$ dp(i, k) = \max{ dp(j, k - (i - j - 1)) + r_i - \max(l_i, r_j) }, j \in [i - k - 1, i - 1] $。這玩意複雜度顯然是 $ O(nk^2) $ 的,顯然不正確。
考慮優化,狀態沒什麼可優化的,於是考慮優化轉移,我們嘗試提出來與轉移時的 $ j $ 無關的項,令 $ \xi = i - k - 1 $,於是有 $ dp(i, k) = \max{ dp(j, j - \xi) + r_i - \max(l_i, r_j) }, j \in [i - k - 1, i - 1] $。也就是說我們要在 $ \xi = i - k - 1 $ 的前提下,換句話說就是以前的 $ dp $ 中前後索引的差為 $ \xi $ 的值中找到後面式子的最大值進行轉移,這個東西簡單分類討論一下就是如果 $ l_i \ge r_j $(或者理解為區間無交)那麼就是要最大化 $ dp(j, j - \xi) $。反之需要最大化 $ dp(j, j - \xi) - r_j $。
所以我們直接對前者維護一個最大值(因為我們的區間之間使有序的,所以如果對於之前的區間已經無交了,對於現在的一定也無交),後者則維護一個單調佇列單調棧單調雙端佇列,每次處理時先把對應單調雙端佇列隊頭中所有不合法的,也就是不交的彈出然後更新不交區間裡面的最大值。然後用交的和不交的裡的最大值更新當前 $ dp $,每次處理完當前的 $ dp $ 就把當前的丟到對應的單調雙端佇列裡,這時我們會按照雙端佇列直接把隊尾不優於當前的直接丟掉而不是保留下來等待不交時更新不交的最大值,這個的原因我想了很久,感性地證明了一下,如下:
如果你仔細想一下就會發現這個演算法有一點問題,首先我們在最初的找單調雙端佇列裡面的隊頭不合法的值的時候,我們時按照 $ val $ 的大小,也就是 $ dp(j, j - \xi) - r_j $ 的大小維護的單調雙端佇列,但是我們更新不交最大值的時候需要的是與其不交區間裡的最大值(廢話),難道就不會存在一種情況,即隊頭的區間 $ val $ 更大,但是區間靠右,然而隊頭之後某個區間 $ val $ 較小卻區間靠左,也就是存在一種情況單調優先佇列的後面已經存在了不交的區間但是卻被隊頭的相交區間阻擋了,這樣的話我們難道不會少更新一些值嗎?還有就是在把當前的 $ dp $ 壓入單調雙端佇列的時候,我們會把隊尾不優於當前值的直接踢掉,這個時候卻沒有考慮到在後面更新時這些區間可能會從相交變成不交,從而可能性地去更新不交的最大值,但是現在這被我們丟掉了,難道不會使答案更劣嗎?
這幾個問題困擾了我很久,完全沒有頭緒,直到發現了一個性質我才大概能感性理解這個貪心的正確性。
顯然當我們轉移的時候,如果轉移的這個 $ j $ 是與 $ i $ 交的,那麼最優的 $ j $ 一定是 $ l_j $ 最小的 $ j $,當然經過我們的處理之後同時也是 $ r_j $ 最小的。這個不難理解,因為我們處理過包含的區間,所以 $ i, j $ 相交那麼最終這兩段組成的區間一定是 $ [l_j, r_i) $,那麼 $ l_j $ 儘量小一定更優,所以 $ j + 1, j + 2, \cdots, i - 1 $ 的區間都是沒必要選的,選了也不會有任何貢獻。
此時我們再回到剛才的考慮,我們的單調優先佇列維護的是最大值,而最大值一定是 $ l $ 和 $ r $ 最小的,那麼我們實際上也可以感性地認為更左的區間在單調雙端佇列裡也更靠前,如果這個成立那麼我們剛才的所有問題也就很顯然是不需要考慮得了,這樣的話我們彈出的隊頭一定是最先變得不交的,被丟掉的區間也一定是更劣的。
於是這個貪心(應該)是正確的。
當然上面這一大段證明都完全是我口糊的,不保證正確性,也不夠理性和嚴謹,期待一個更嚴謹的證明。
Code
#define _USE_MATH_DEFINES
#include <bits/extc++.h>
#define PI M_PI
#define E M_E
#define npt nullptr
#define SON i->to
#define OPNEW void* operator new(size_t)
#define ROPNEW(arr) void* Edge::operator new(size_t){static Edge* P = arr; return P++;}
using namespace std;
using namespace __gnu_pbds;
mt19937 rnd(random_device{}());
int rndd(int l, int r){return rnd() % (r - l + 1) + l;}
bool rnddd(int x){return rndd(1, 100) <= x;}
typedef unsigned int uint;
typedef unsigned long long unll;
typedef long long ll;
typedef long double ld;
template< typename T = int >
inline T read(void);
struct Line{
int l, r;
friend const bool operator < (const Line &a, const Line &b){if(a.l == b.l)return a.r < b.r; return a.l < b.l;}
}line[110000];
list < Line > _line;
int N, K;
int cnt(0);
struct Status{int idx; int val;};
deque < Status > bst[110000];
int mx[110000];
int dp[110000][110];
int main(){
N = read(), K = read();
for(int i = 1; i <= N; ++i){
int l = read(), r = read();
_line.emplace_back(Line{l, r});
}_line.sort();
for(auto it = next(_line.begin()); it != _line.end();)
if(it->r <= prev(it)->r)it = _line.erase(it), --K; else ++it;
for(auto ln : _line)line[++cnt] = ln;
N = cnt; K = max(0, K);
for(int i = 1; i <= N; ++i){
for(int k = 0; k <= min(i - 1, K); ++k){
int xi = i - k - 1;
while(!bst[xi].empty() && line[bst[xi].front().idx].r < line[i].l){
auto tp = bst[xi].front(); bst[xi].pop_front();
mx[xi] = max(mx[xi], tp.val + line[tp.idx].r);
}
dp[i][k] = max({
dp[i][k],
mx[xi] + line[i].r - line[i].l,
bst[xi].empty() ? -1 : bst[xi].front().val + line[i].r
});
int val = dp[i][k] - line[i].r;
int pos = i - k;
while(!bst[pos].empty() && bst[pos].back().val < val)bst[pos].pop_back();
bst[pos].push_back(Status{i, val});
}
}int ans(-1);
for(int i = N - K; i <= N; ++i)ans = max(ans, dp[i][K - (N - i)]);
printf("%d\n", ans);
fprintf(stderr, "Time: %.6lf\n", (double)clock() / CLOCKS_PER_SEC);
return 0;
}
template < typename T >
inline T read(void){
T ret(0);
short flag(1);
char c = getchar();
while(c != '-' && !isdigit(c))c = getchar();
if(c == '-')flag = -1, c = getchar();
while(isdigit(c)){
ret *= 10;
ret += int(c - '0');
c = getchar();
}
ret *= flag;
return ret;
}
LG-P4181 [USACO18JAN]Rental Service S
題面
sssmzy 有 $ n $ 包彩虹犇,彩虹犇可以產生 zpair 最愛的彩虹糖。在一天中,$ n $ 只彩虹犇有 $ c_i $ 表示其當天可以產生 $ c_i $ 包彩虹糖。有 $ m $ 只 zpair 想要買彩虹糖,每隻 zpair 有 $ q_i $ 和 $ p_i $,表示第 $ i $ 只 zpair 當天最多可以按 $ p_i $ 元每包購買 $ q_i $ 包彩虹糖(可以分裂開賣,也就是可以賣 $ \lt q_i $ 包,價格不變)。還存在 $ r $ 只 zpar,每隻 zpar 有 $ r_i $,表示其會以 $ r_i $ 元的價格租用一天任意一隻彩虹犇,請你幫 sssmzy 最大化得到的錢數並輸出錢數。
$ 1 \le n, m, r \le 10^5, 1 \le c_i, p_i, q_i, r_i \le 10^6 $。
$ n, m, r $
$ c_1 $
$ c_2 $
$ \cdots $
$ q_1, p_1 $
$ q_2, p_2 $
$ \cdots $
$ r_1 $
$ r_2 $
$ \cdots $
Examples
Input_1
5 3 4 6 2 4 7 1 10 25 2 10 15 15 250 80 100 40
Output_1
725
Solution
大水題,貪心 + 模擬,且貪心也很顯然,只是寫起來比較噁心,有語法題那味了,開始以為是我實現太差了,翻了一下題解區也都挺長。
顯然幾個貪心策略就是:
- 如果要賣彩虹犇,一定優先賣給 $ r_i $ 最高的 zpar。
- 如果要賣彩虹糖,一定優先賣給 $ p_i $ 最高的 zpair。(賣的過程可能類似 ODT 的 split?)
- 如果要保留彩虹犇,一定優先保留日產彩虹糖更多的犇。
然後就沒啥可說的了,精細實現就可以了,有很多需要判斷的地方比如 $ r $ 和 $ n $ 取 $ \min $ 之類的,很簡單,寫起來需要用點腦子而已。
複雜度 $ O(n) $,記得開 long long
。
Code
#define _USE_MATH_DEFINES
#include <bits/extc++.h>
#define PI M_PI
#define E M_E
#define npt nullptr
#define SON i->to
#define OPNEW void* operator new(size_t)
#define ROPNEW(arr) void* Edge::operator new(size_t){static Edge* P = arr; return P++;}
using namespace std;
using namespace __gnu_pbds;
mt19937 rnd(random_device{}());
int rndd(int l, int r){return rnd() % (r - l + 1) + l;}
bool rnddd(int x){return rndd(1, 100) <= x;}
typedef unsigned int uint;
typedef unsigned long long unll;
typedef long long ll;
typedef long double ld;
#define int ll
template< typename T = int >
inline T read(void);
int N, M, R;
int c[110000];
struct Milk{
int pri, siz;
friend const bool operator < (const Milk &a, const Milk &b){
if(a.pri == b.pri)return a.siz > b.siz;
return a.pri > b.pri;
}
}mlk[110000];
int buy[110000];
int cur(0);
signed main(){
N = read(), M = read(), R = read();
for(int i = 1; i <= N; ++i)c[i] = read();
for(int i = 1; i <= M; ++i)mlk[i].siz = read(), mlk[i].pri = read();
for(int i = 1; i <= R; ++i)buy[i] = read();
sort(c + 1, c + N + 1, greater < int >());
sort(mlk + 1, mlk + M + 1);
sort(buy + 1, buy + R + 1, greater < int >());
int unsel(0), sel(N);
int curus(0);
int curs(N);
int lftmlk(0);
int curmlkn(0);
int mx(-1);
for(int i = 1; i <= min(N, R); ++i)cur += buy[i];
do{
while(curus < unsel)lftmlk += c[++curus];
while(lftmlk && curmlkn < M){
++curmlkn;
if(lftmlk >= mlk[curmlkn].siz)
cur += mlk[curmlkn].siz * mlk[curmlkn].pri,
lftmlk -= mlk[curmlkn].siz;
else
cur += lftmlk * mlk[curmlkn].pri,
mlk[curmlkn].siz -= lftmlk,
lftmlk = 0,
--curmlkn;
}
while(curs > sel){
if(curs <= R)cur -= buy[curs];
--curs;
}
mx = max(mx, cur);
// printf("sel:%lld, unsel:%lld, cur=%lld\n", sel, unsel, cur);
--sel, ++unsel;
}while(unsel <= N || sel >= 0);
printf("%lld\n", mx);
fprintf(stderr, "Time: %.6lf\n", (double)clock() / CLOCKS_PER_SEC);
return 0;
}
template < typename T >
inline T read(void){
T ret(0);
short flag(1);
char c = getchar();
while(c != '-' && !isdigit(c))c = getchar();
if(c == '-')flag = -1, c = getchar();
while(isdigit(c)){
ret *= 10;
ret += int(c - '0');
c = getchar();
}
ret *= flag;
return ret;
}
LG-P6111 [USACO18JAN]MooTube S
題面
給定 $ n $ 個點的樹形結構,有邊權,$ q $ 次詢問每次給定 $ k_i, v_i $,求 $ v_i $ 節點到其它所有節點中路徑上邊權最小值不小於 $ k_i $ 的點的數量。
我知道你很急,但是你別急,我知道你有更優的方法,但是你別優,看看資料範圍,想點普及難度的做法。
$ 1 \le n, q \le 5 \times 10^3 $,其它資料均在 $ 10^9 $ 以內,$ 2s $ 時限。
Examples
Input_1
4 3 1 2 3 2 3 2 2 4 4 1 2 4 1 3 1
Output_1
3 0 2
Solution
來個最無腦的做法,每個點開個 vector
dfs 一遍維護到其它 $ n - 1 $ 個點路徑邊權最小值,然後排個序的話就可以 $ O(\log n) $ 查詢,不排序就是 $ O(n) $ 查詢,最終複雜度 $ O(n^2 \log n + q \log n) $ 或 $ O(n(n + q)) $,兩秒時限隨便過。
Code
#define _USE_MATH_DEFINES
#include <bits/extc++.h>
#define PI M_PI
#define E M_E
#define npt nullptr
#define SON i->to
#define OPNEW void* operator new(size_t)
#define ROPNEW(arr) void* Edge::operator new(size_t){static Edge* P = arr; return P++;}
using namespace std;
using namespace __gnu_pbds;
mt19937 rnd(random_device{}());
int rndd(int l, int r){return rnd() % (r - l + 1) + l;}
bool rnddd(int x){return rndd(1, 100) <= x;}
typedef unsigned int uint;
typedef unsigned long long unll;
typedef long long ll;
typedef long double ld;
template< typename T = int >
inline T read(void);
struct Edge{
Edge* nxt;
int to;
int val;
OPNEW;
}ed[11000];
ROPNEW(ed);
Edge* head[5100];
vector < int > vert[5100];
int N, Q;
void dfs(int rt, int p, int fa = 0, int cur = 0){
if(cur)vert[rt].emplace_back(cur);
for(auto i = head[p]; i; i = i->nxt)
if(SON != fa)dfs(rt, SON, p, cur ? min(cur, i->val) : i->val);
}
int main(){
N = read(), Q = read();
for(int i = 1; i <= N - 1; ++i){
int s = read(), t = read(), val = read();
head[s] = new Edge{head[s], t, val};
head[t] = new Edge{head[t], s, val};
}
for(int i = 1; i <= N; ++i)dfs(i, i), sort(vert[i].begin(), vert[i].end());
while(Q--){
int dis = read(), p = read();
printf("%d\n", (int)distance(lower_bound(vert[p].begin(), vert[p].end(), dis), vert[p].end()));
}
fprintf(stderr, "Time: %.6lf\n", (double)clock() / CLOCKS_PER_SEC);
return 0;
}
template < typename T >
inline T read(void){
T ret(0);
short flag(1);
char c = getchar();
while(c != '-' && !isdigit(c))c = getchar();
if(c == '-')flag = -1, c = getchar();
while(isdigit(c)){
ret *= 10;
ret += int(c - '0');
c = getchar();
}
ret *= flag;
return ret;
}
LG-P4185 [USACO18JAN]MooTube G
題面
題意與上一題不變,資料範圍加大了。
給定 $ n $ 個點的樹形結構,有邊權,$ q $ 次詢問每次給定 $ k_i, v_i $,求 $ v_i $ 節點到其它所有節點中路徑上邊權最小值不小於 $ k_i $ 的點的數量。
$ 1 \le n, q \le 10^5 $,其它資料均在 $ 10^9 $ 以內,$ 1s $ 時限。
Examples
Input_1
4 3 1 2 3 2 3 2 2 4 4 1 2 4 1 3 1
Output_1
3 0 2
Solution
還是挺人類智慧的,全離線下來就行了。
不難想到對於 $ k $ 比較小的一定可以包含可以抵達的所有比當前 $ k $ 更大的 $ k $,所以考慮把詢問離線下來按 $ k $ 降序,然後把每條邊也離線下來按邊權降序排列,然後對於每個詢問的 $ k $,把所有不小於 $ k $ 的邊權加進真正的圖裡,用並查集維護即可,至於查詢的答案就是點所在並查集的大小減 $ 1 $。複雜度 $ O(n \log n + q \log q) $。
Code
#define _USE_MATH_DEFINES
#include <bits/extc++.h>
#define PI M_PI
#define E M_E
#define npt nullptr
#define SON i->to
#define OPNEW void* operator new(size_t)
#define ROPNEW(arr) void* Edge::operator new(size_t){static Edge* P = arr; return P++;}
using namespace std;
using namespace __gnu_pbds;
mt19937 rnd(random_device{}());
int rndd(int l, int r){return rnd() % (r - l + 1) + l;}
bool rnddd(int x){return rndd(1, 100) <= x;}
typedef unsigned int uint;
typedef unsigned long long unll;
typedef long long ll;
typedef long double ld;
template< typename T = int >
inline T read(void);
class UnionFind{
private:
int siz[110000];
int fa[110000];
public:
UnionFind(void){for(int i = 1; i <= 101000; ++i)fa[i] = i, siz[i] = 1;}
int Find(int x){return x == fa[x] ? x : fa[x] = Find(fa[x]);}
void Union(int origin, int son){
if(Find(origin) == Find(son))return;
siz[Find(origin)] += siz[Find(son)];
fa[Find(son)] = Find(origin);
}
int GetSiz(int x){return siz[Find(x)] - 1;}
}uf;
struct Edge{
int s, t;
int val;
friend const bool operator < (const Edge &a, const Edge &b){return a.val < b.val;}
};std::priority_queue < Edge > ed;
int ans[110000];
struct Query{
int v, k;
int idx;
friend const bool operator < (const Query &a, const Query &b){return a.k > b.k;}
}qs[110000];
int N, Q;
int main(){
N = read(), Q = read();
for(int i = 1; i <= N - 1; ++i){
int s = read(), t = read(), val = read();
ed.push(Edge{s, t, val});
}
for(int i = 1; i <= Q; ++i){
int k = read(), v = read();
qs[i] = Query{v, k, i};
}sort(qs + 1, qs + Q + 1);
for(int i = 1; i <= Q; ++i){
while(!ed.empty() && ed.top().val >= qs[i].k)
uf.Union(ed.top().s, ed.top().t), ed.pop();
ans[qs[i].idx] = uf.GetSiz(qs[i].v);
}
for(int i = 1; i <= Q; ++i)printf("%d\n", ans[i]);
fprintf(stderr, "Time: %.6lf\n", (double)clock() / CLOCKS_PER_SEC);
return 0;
}
template < typename T >
inline T read(void){
T ret(0);
short flag(1);
char c = getchar();
while(c != '-' && !isdigit(c))c = getchar();
if(c == '-')flag = -1, c = getchar();
while(isdigit(c)){
ret *= 10;
ret += int(c - '0');
c = getchar();
}
ret *= flag;
return ret;
}
LG-P4184 [USACO18JAN]Sprinklers P
題面
$ n \times n $ 的網格圖($ n $個格點,從 $ (0, 0) $ 到 $ (n - 1, n - 1) $),給定 $ n $ 個點的座標 $ (i, j) $,保證任意兩個座標的橫或縱均不相等,每個點會對 $ \forall(x, y), x \in [i, n - 1], y \in [j, n - 1] $ 進行灑水,對 $ \forall(x, y), x \in [0, i], y \in [0, j] $ 進行施肥,要求選出一片矩形,使得其中每個格點都既澆水又施肥,求方案數。
或者也可以描述為,給定 $ n $ 個關鍵點,要求選一個矩形使得其中左上右下各自至少有一個關鍵點,求方案數。
$ 1 \le n \le 10^5 $。
Examples
Input_1
5 0 4 1 1 2 2 3 0 4 3
Output_1
21
Solution
一道奇怪的題,最終可以轉化為無腦推柿子。
首先借一個題解區的圖:
不難發現一個妙妙性質,即在第 $ i $ 行裡,我們假設可行的矩形的左右端點為 $ [l_i, r_i] $,那麼 $ l_i $ 即為在其不在其下面的所有關鍵點橫座標的最小值,$ r_i $ 為不在其上面的最大值,按照這個規律我們也可以處理出來對於每一列縱座標可行的最小值 $ up_i $,然後可以寫一個 $ O(n^4) $ 的類似二位數點矩形的東西,然後化簡。令 $ (i, j) $ 為右下端點,$ (x, y) $ 為左上端點,有答案為:
然後我們令 $ sum1_n = \sum_{i = 1}^{n}up_i $,再令 $ sum2_n = \sum_{i = 1}^{n}sum1_i $,有:
\[\begin{aligned} &\sum_{i = 1}^{n}\left( i\left( \dfrac{1}{2}(r_i - l_i)(r_i - l_i + 1) \right) - \sum_{j = l_i}^{r_i} \sum_{y = l_i}^{j - 1}up_y \right) \\ =& \sum_{i = 1}^{n}\left( i\left( \dfrac{1}{2}(r_i - l_i)(r_i - l_i + 1) \right) - \sum_{j = l_i}^{r_i}(sum1_{j - 1} - sum1_{l_i - 1}) \right) \\ =& \sum_{i = 1}^{n}\left( i\left( \dfrac{1}{2}(r_i - l_i)(r_i - l_i + 1) \right) - \sum_{j = l_i}^{r_i}sum1_{j - 1} + (r_i - l_i + 1)sum1_{l_i - 1} \right) \\ =& \sum_{i = 1}^{n}\left( i\left( \dfrac{1}{2}(r_i - l_i)(r_i - l_i + 1) \right) - \sum_{j = l_i - 1}^{r_i - 1}sum1_{j} + (r_i - l_i + 1)sum1_{l_i - 1} \right) \\ =& \sum_{i = 1}^{n}\left( i\left( \dfrac{1}{2}(r_i - l_i)(r_i - l_i + 1) \right) - sum2_{r_i - 1} + sum2_{l_i - 2} + (r_i - l_i + 1)sum1_{l_i - 1} \right) \end{aligned} \]然後就成功從 $ O(n^4) $ 推到 $ O(n) $ 了。
Code
#define _USE_MATH_DEFINES
#include <bits/extc++.h>
#define PI M_PI
#define E M_E
#define npt nullptr
#define SON i->to
#define OPNEW void* operator new(size_t)
#define ROPNEW(arr) void* Edge::operator new(size_t){static Edge* P = arr; return P++;}
using namespace std;
using namespace __gnu_pbds;
mt19937 rnd(random_device{}());
int rndd(int l, int r){return rnd() % (r - l + 1) + l;}
bool rnddd(int x){return rndd(1, 100) <= x;}
typedef unsigned int uint;
typedef unsigned long long unll;
typedef long long ll;
typedef long double ld;
#define MOD (ll)(1e9 + 7)
#define GetSum1(x) ((x) > 0 ? sum1[x] : 0)
#define GetSum2(x) ((x) > 0 ? sum2[x] : 0)
template< typename T = int >
inline T read(void);
int N;
int y[110000];
int l[110000], r[110000], up[110000];
ll sum1[110000];
ll sum2[110000];
int main(){
N = read();
for(int i = 1; i <= N; ++i){
int rx = read() + 1, ry = read() + 1;
y[rx] = ry;
}l[0] = INT_MAX; r[N + 1] = -1;
for(int i = 1; i <= N; ++i)l[i] = min(l[i - 1], y[i]);
for(int i = N; i >= 1; --i)r[i] = max(r[i + 1], y[i]);
int cur = r[1];
for(int i = 1; i <= N; ++i)while(cur && cur >= l[i])up[cur] = i, --cur;
for(int i = 1; i <= N; ++i)sum1[i] = (sum1[i - 1] + up[i]) % MOD;
for(int i = 1; i <= N; ++i)sum2[i] = (sum2[i - 1] + sum1[i]) % MOD;
ll ans(0);
for(int i = 1; i <= N; ++i){
ans = (ans + ((ll)r[i] - l[i]) * (r[i] - l[i] + 1) / 2ll % MOD * i % MOD) % MOD;
ans = (ans - GetSum2(r[i] - 1) + GetSum2(l[i] - 2) + MOD) % MOD;
ans = (ans + ((ll)r[i] - l[i] + 1) * GetSum1(l[i] - 1) % MOD) % MOD;
}printf("%lld\n", ans);
fprintf(stderr, "Time: %.6lf\n", (double)clock() / CLOCKS_PER_SEC);
return 0;
}
template < typename T >
inline T read(void){
T ret(0);
short flag(1);
char c = getchar();
while(c != '-' && !isdigit(c))c = getchar();
if(c == '-')flag = -1, c = getchar();
while(isdigit(c)){
ret *= 10;
ret += int(c - '0');
c = getchar();
}
ret *= flag;
return ret;
}
LG-P4187 [USACO18JAN]Stamp Painting G
題面
給定 $ m $ 種顏色的長度為 $ k $ 的圖章,塗一個長度為 $ n $ 的畫布,每次可以塗一段長為 $ k $ 的區間,把這一段全變成對應的顏色,最後必須要塗滿畫布,求有多少種最終狀態。
$ 1 \le n, m, k \le 10^6 $。
Examples
Input_1
3 2 2
Output_1
6
Solution
我們發現這樣塗的最終狀態一定為至少有一段長度為 $ k $ 的顏色相同的區間,然後其它位置可以是任何狀態。發現直接求很難,考慮正難則反,令 $ dp(i) $ 表示考慮前 $ i $ 的長度有多少種沒有連續 $ k $ 顏色相同區間的狀態,顯然有轉移若 $ i \lt k \(,\) dp(i) = dp(i - 1) \times m $。若 $ i \ge k \(,\) dp(i) = \sum_{j = i - k + 1}^{i - 1}dp(j) \times (m - 1) $,後面這個柿子拿字首和優化一下就行了,最終把總方案 $ m^n $ 減去 $ dp(n) $ 即為答案,複雜度 $ O(n) $。
這真的是紫色的題嗎。。
Code
#define _USE_MATH_DEFINES
#include <bits/extc++.h>
#define PI M_PI
#define E M_E
#define npt nullptr
#define SON i->to
#define OPNEW void* operator new(size_t)
#define ROPNEW(arr) void* Edge::operator new(size_t){static Edge* P = arr; return P++;}
using namespace std;
using namespace __gnu_pbds;
mt19937 rnd(random_device{}());
int rndd(int l, int r){return rnd() % (r - l + 1) + l;}
bool rnddd(int x){return rndd(1, 100) <= x;}
typedef unsigned int uint;
typedef unsigned long long unll;
typedef long long ll;
typedef long double ld;
#define MOD (ll)(1e9 + 7)
template< typename T = int >
inline T read(void);
int N, M, K;
ll dp[1100000];
ll sum[1100000];
ll qpow(ll a, ll b){
ll ret(1), mul(a);
while(b){
if(b & 1)ret = ret * mul % MOD;
b >>= 1;
mul = mul * mul % MOD;
}return ret;
}
int main(){
N = read(), M = read(), K = read();
dp[0] = sum[0] = 1;
for(int i = 1; i <= N; ++i)
dp[i] = i < K ? dp[i - 1] * M % MOD : (sum[i - 1] - sum[i - K] + MOD) % MOD * (M - 1) % MOD,
sum[i] = (sum[i - 1] + dp[i]) % MOD;
printf("%lld\n", (qpow(M, N) - dp[N] + MOD) % MOD);
fprintf(stderr, "Time: %.6lf\n", (double)clock() / CLOCKS_PER_SEC);
return 0;
}
template < typename T >
inline T read(void){
T ret(0);
short flag(1);
char c = getchar();
while(c != '-' && !isdigit(c))c = getchar();
if(c == '-')flag = -1, c = getchar();
while(isdigit(c)){
ret *= 10;
ret += int(c - '0');
c = getchar();
}
ret *= flag;
return ret;
}
LG-P4186 [USACO18JAN]Cow at Large G
題面
給定 $ n $ 個節點的樹,zpair 初始在 $ S $ 節點上,對於樹上每個度數為 $ 1 $ 的節點我們認為其為出口,zpair 如果到達出口即為成功逃脫,你可以在任意一些出口處各放置一隻 sssmzy,每次 zpair 走到一個相鄰節點,每隻 sssmzy 也一樣。如果 zpair 和 sssmzy 在某一時刻處於同一個節點或邊上我們認為 zpair 被抓住了。求最少放幾隻 sssmzy 可以讓 zpair 一定無法逃脫。
$ 2 \le n \le 10^5 $。
Examples
Input_1
7 1 1 2 1 3 3 4 3 5 4 6 5 7
Output_1
3
Solution
考慮當 zpair 走到某個節點時,若有至少一隻 sssmzy 距離該店的路程不大於 zpair,那麼 zpair 走到此處時一定可以被截住並無法往下走,所以我們考慮儘可能早地堵死 zpair 的所有可能路徑。
考慮 DFS 預處理每個點深度 $ dep_i $,每個點所在子樹中葉子節點最小深度 $ mn_i $,然後再次 DFS 對於每個節點如果 $ dep_i - 1 \ge mn_i - dep_i $ 那麼在這裡面子樹裡深度最淺的葉子節點放一個 sssmzy 一定可以在當前點堵死 zpair,此時 $ ans \leftarrow ans + 1 $,然後不再搜下去,這樣即可統計出最終答案。
Code
#define _USE_MATH_DEFINES
#include <bits/extc++.h>
#define PI M_PI
#define E M_E
#define npt nullptr
#define SON i->to
#define OPNEW void* operator new(size_t)
#define ROPNEW(arr) void* Edge::operator new(size_t){static Edge* P = arr; return P++;}
using namespace std;
using namespace __gnu_pbds;
mt19937 rnd(random_device{}());
int rndd(int l, int r){return rnd() % (r - l + 1) + l;}
bool rnddd(int x){return rndd(1, 100) <= x;}
typedef unsigned int uint;
typedef unsigned long long unll;
typedef long long ll;
typedef long double ld;
template< typename T = int >
inline T read(void);
int N, S;
int ans(0);
struct Edge{
Edge* nxt;
int to;
OPNEW;
}ed[210000];
ROPNEW(ed);
Edge* head[110000];
int mn[110000];
int dep[110000];
int dfs_pre(int p = S, int fa = 0){
dep[p] = dep[fa] + 1;
if(!head[p]->nxt)mn[p] = dep[p];
for(auto i = head[p]; i; i = i->nxt)
if(SON != fa)
mn[p] = min(mn[p], dfs_pre(SON, p));
return mn[p];
}
void dfs(int p = S, int fa = 0){
if(dep[p] > mn[p] - dep[p])return ++ans, void();
for(auto i = head[p]; i; i = i->nxt)if(SON != fa)dfs(SON, p);
}
int main(){
memset(mn, 0x3f, sizeof mn);
N = read(), S = read();
for(int i = 1; i <= N - 1; ++i){
int s = read(), t = read();
head[s] = new Edge{head[s], t};
head[t] = new Edge{head[t], s};
}if(!head[S]->nxt)printf("1\n"), exit(0);
dfs_pre();
dfs();
printf("%d\n", ans);
fprintf(stderr, "Time: %.6lf\n", (double)clock() / CLOCKS_PER_SEC);
return 0;
}
template < typename T >
inline T read(void){
T ret(0);
short flag(1);
char c = getchar();
while(c != '-' && !isdigit(c))c = getchar();
if(c == '-')flag = -1, c = getchar();
while(isdigit(c)){
ret *= 10;
ret += int(c - '0');
c = getchar();
}
ret *= flag;
return ret;
}
LG-P4183 [USACO18JAN]Cow at Large P
題面
加強版又來咯
給定 $ n $ 個節點的樹,zpair 初始在某個節點上,對於樹上每個度數為 $ 1 $ 的節點我們認為其為出口,zpair 如果到達出口即為成功逃脫,你可以在任意一些出口處各放置一隻 sssmzy,每次 zpair 走到一個相鄰節點,每隻 sssmzy 也一樣。如果 zpair 和 sssmzy 在某一時刻處於同一個節點或邊上我們認為 zpair 被抓住了。求在 zpair 在 $ 1 $ 到 $ n $ 節點上時,最少放幾隻 sssmzy 可以讓 zpair 一定無法逃脫。
$ 1 \le n \le 7 \times 10^4 $。
Examples
Input_1
7 1 2 1 3 3 4 3 5 4 6 5 7
Output_1
3 1 3 3 3 1 1
Solution
好題,澱粉質。
首先延續一下上一題的結論,對於一個能堵住 sssmzy 的節點應該是符合 $ dis_{(u, root)} \ge dis_{(u, mn_u)} $,然後我們不難發現這東西對於一個深度最淺的滿足條件的點,其子樹內節點也滿足,但是我們不想要考慮他們。在上一題我們是通過從根節點搜一下遇到第一個被標記的點就不繼續搜下去了,但是我們這個時候這麼搞肯定會寄,所以簡單考慮一下不難發現要找到的點即為滿足 $ dis_{(u, root)} \ge dis_{(u, mn_u)}, dis_{(fa, root)} \lt dis_{(fa, mn_u)} $,也就是父親不滿足此條件,對於每個 $ u $,找到所有滿足此條件的點統計一下數量即為 $ u $ 的答案。
以上這些還都是比較好想的,後面就要開始人類智慧了。以上這個東西我們不可以想到,如果讓符合條件的 $ u $ 的子樹權值和為 $ 1 $,可能會更好統計一些,所以可能需要讓很多點權之間互相抵消。這個時候會有一個很人類智慧的想法,使每個點節點權值為 $ 1 - \texttt{兒子數量} $,或者說 $ 2 - deg_u $。
比如這個圖,我們顯然這個 $ u $ 即為我們要計算的唯一的 $ u $,而其所有的子樹也都滿足條件,此時把所有權值求和剛好為 $ 1 $,這樣的話我們就不需要判斷是否為當前分支深度最低的滿足的點,直接無腦加和即可。所以現在我們求的答案就是對於每個根節點 $ u $ 的:
\[\sum_{u \mid dis_{(u, root)} \ge dis_{(u, mn_u)}} 2 - deg_u \]然後我們發現這個 $ dis $ 在以某個點為根的時候就是其 $ dep $,所以轉化一下就是 $ dep_{root} \ge dep_{mn_u} - dep_u $,此時的 $ \sum_u 2 - deg_u $ 也就是 $ root $ 的答案了,這個時候就已經完全是點分治的形狀了,分別維護左右兩邊的,按照值排序,然後對於每一個 $ dep_{root} $ 顯然 $ dep_{mn_u} - dep_u $ 是一個字首,隨便維護一下即可,剩下的都是完全的點分治模板,你們肯定都會。
Code
#define _USE_MATH_DEFINES
#include <bits/extc++.h>
#define PI M_PI
#define E M_E
#define npt nullptr
#define SON i->to
#define OPNEW void* operator new(size_t)
#define ROPNEW(arr) void* Edge::operator new(size_t){static Edge* P = arr; return P++;}
using namespace std;
using namespace __gnu_pbds;
mt19937 rnd(random_device{}());
int rndd(int l, int r){return rnd() % (r - l + 1) + l;}
bool rnddd(int x){return rndd(1, 100) <= x;}
typedef unsigned int uint;
typedef unsigned long long unll;
typedef long long ll;
typedef long double ld;
template< typename T = int >
inline T read(void);
int N;
struct Edge{
Edge* nxt;
int to;
OPNEW;
}ed[210000];
ROPNEW(ed);
Edge* head[110000];
int ans[110000];
bool vis[110000];
int deg[110000], dep[110000], dleaf[110000];
void dfs_pre(int p = 1, int fa = 0){
if(deg[p] == 1)dleaf[p] = 0;
for(auto i = head[p]; i; i = i->nxt){
if(SON == fa)continue;
dfs_pre(SON, p);
dleaf[p] = min(dleaf[p], dleaf[SON] + 1);
}
}
void dfs_pre_from_root(int p = 1, int fa = 0){
for(auto i = head[p]; i; i = i->nxt){
if(SON == fa)continue;
dleaf[SON] = min(dleaf[SON], dleaf[p] + 1);
dfs_pre_from_root(SON, p);
}
}
int mx[110000];
int siz[110000];
int rt(0);
void ResetRoot(void){rt = 0, mx[rt] = N;}
void dfs_rt(int p = 1, int fa = 0, int Siz = N){
siz[p] = 1; mx[p] = 0;
for(auto i = head[p]; i; i = i->nxt){
if(SON == fa || vis[SON])continue;
dfs_rt(SON, p, Siz);
siz[p] += siz[SON];
mx[p] = max(mx[p], siz[SON]);
}mx[p] = max(mx[p], Siz - siz[p]);
if(mx[p] < mx[rt])rt = p;
}
struct Status{
int dep, val;
friend const bool operator < (const Status &a, const Status &b){return a.dep < b.dep;}
}idx[110000], nod[110000];
int cnt(0);
void dfs_dep(int p, int fa, int cur = 0){
dep[p] = cur;
for(auto i = head[p]; i; i = i->nxt){
if(SON == fa || vis[SON])continue;
dfs_dep(SON, p, cur + 1);
}
}
void dfs_upd(int p, int fa){
idx[++cnt] = Status{dep[p], p},
nod[cnt] = Status{dleaf[p] - dep[p], 2 - deg[p]};
for(auto i = head[p]; i; i = i->nxt)if(SON != fa && !vis[SON])dfs_upd(SON, p);
}
void Cal(int p, int fa, int flag){
cnt = 0;
dfs_upd(p, fa);
sort(idx + 1, idx + cnt + 1), sort(nod + 1, nod + cnt + 1);
int sum(0), sp(0);
for(int i = 1; i <= cnt; ++i){
while(sp <= cnt - 1 && nod[sp + 1].dep <= idx[i].dep)sum += nod[++sp].val;
ans[idx[i].val] += flag * sum;
}
}
void Make(int p){
vis[p] = true;
dfs_dep(p, 0);
Cal(p, 0, 1);
for(auto i = head[p]; i; i = i->nxt){
if(vis[SON])continue;
Cal(SON, p, -1);
ResetRoot();
dfs_rt(SON, p, siz[SON]);
Make(rt);
}
}
int main(){
// freopen("P4183_3.in", "r", stdin);
N = read();
for(int i = 1; i <= N - 1; ++i){
int s = read(), t = read();
head[s] = new Edge{head[s], t};
head[t] = new Edge{head[t], s};
++deg[s], ++deg[t];
}memset(dleaf, 0x3f, sizeof(dleaf));
dfs_pre(); dfs_pre_from_root();
ResetRoot(); dfs_rt(); /*dfs_rt(rt);*/ Make(rt);
for(int i = 1; i <= N; ++i)printf("%d\n", deg[i] == 1 ? 1 : ans[i]);
fprintf(stderr, "Time: %.6lf\n", (double)clock() / CLOCKS_PER_SEC);
return 0;
}
template < typename T >
inline T read(void){
T ret(0);
short flag(1);
char c = getchar();
while(c != '-' && !isdigit(c))c = getchar();
if(c == '-')flag = -1, c = getchar();
while(isdigit(c)){
ret *= 10;
ret += int(c - '0');
c = getchar();
}
ret *= flag;
return ret;
}
UPD
update-2022_11_07 初稿