USACO 2018 Open 題解
USACO 2018 Open Solution
目錄-
USACO 2018 Open Solution
- 更好的閱讀體驗戳此進入
- 題面 Luogu 連結
- LG-P4379 [USACO18OPEN]Lemonade Line S
- LG-P4380 [USACO18OPEN]Multiplayer Moo S
- LG-P4376 [USACO18OPEN]Milking Order G
-
LG-P4377 [USACO18OPEN] Talent Show G
- LG-P4378 [USACO18OPEN]Out of Sorts S
- LG-P4375 [USACO18OPEN]Out of Sorts G
- LG-P4372 [USACO18OPEN]Out of Sorts P
- LG-P4374 [USACO18OPEN]Disruption P
-
LG-P4373 [USACO18OPEN]Train Tracking P
- UPD
更好的閱讀體驗戳此進入
題面 Luogu 連結
LG-P4379 [USACO18OPEN]Lemonade Line S
題面
娛樂題,讀完題你們肯定也都秒了。。
有 $ n $ 頭牛,每頭有 $ w_i $ 表示最多可以忍受多少頭牛在其前面排隊,每頭牛都會嘗試排到隊尾一次,如果不可行便不會再去排,求一個嘗試排隊的順序以最小化最終佇列中的牛的數量,求最小值。
Examples
Input_1
5 7 1 400 2 2
Output_1
3
Solution
沒啥可說的,無腦貪心,排個序即可。。
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; vector < int > cow; int ans(0); int main(){ N = read(); for(int i = 1; i <= N; ++i)cow.emplace_back(read()); sort(cow.begin(), cow.end(), greater < int >()); for(auto it = cow.begin(); it != cow.end(); ++it)if(*it >= ans)++ans; else break; 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-P4380 [USACO18OPEN]Multiplayer Moo S
題面
存在一個 $ n \times n $ 網格圖,每格一個數,定義連通塊為四聯通的相同的數構成區域,求最大的連通塊的大小,以及有且僅有兩種數字的最大的連通塊的大小。
$ 1 \le n \le 250 $。
Examples
Input_1
4 2 3 9 3 4 9 9 1 9 9 1 7 2 1 1 9
Output_1
5 10
Solution
第一眼我就感覺這玩意和一個特別遠古的經典題《寬搜 海戰》特別像,那道題大概就是描述一個 $ 01 $ 矩陣然後讓你求四連通塊數量,然後發現確實差不多。。第一問的話就是記錄一下是否訪問過,然後遍歷每個點,沒訪問過的話就搜一下,搜這個點所在的整個連通塊,標記一下,然後記錄塊大小之類的,最後列舉一下取 $ \max $ 即可。。然後後來翻了一下題解才知道這玩意還有個名字叫 FloodFill,我一直以為這玩意就是樸素寬搜呢。。
然後對於第二問,我們可以考慮把每塊縮一下,然後相鄰的不同塊建邊,在這裡列舉所有塊,再列舉其相連的塊,然後跑一個類似寬搜的東西,無腦列舉還有哪些塊是連通的,每次算完取 $ \max $ 即可,當然這玩意的時空複雜度都很玄學,中間很多判重,可以考慮用 set
或 unorderer_set
加手寫雜湊實現,然後發現這玩意會 $ \texttt{TLE} $,各種卡常之後依然寄,於是嘗試思考為什麼被卡,顯然如果想要卡滿演算法應該讓每個塊大小均為 $ 1 $ 且隔一個塊之後又是自己相同的顏色,這樣第二問每次都會搜滿圖,然後列舉也是 $ O(n^2) $ 級別的,最後差不多 $ O(n^4) $,所以會寄。於是我們考慮,如果是這種型別的圖一定會在很大部分均為最大值,所以可以不需要列舉完所有點對,加個卡時即可,很難被卡掉,即使被卡了也可以通過 rand
取點並判重,可以讓出解率極高。
當然上面都是我口糊的,正解似乎可以通過 map
判重之類的實現,反正也算是暴力列舉加剪枝過的。。
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 IN_RANGE(x, y) (1 <= x && x <= N && 1 <= y && y <= N)
template< typename T = int >
inline T read(void);
int N;
int mp[300][300];
int belong[300][300];
// bitset < 90000 > exist[90000];
struct HashPair{
size_t operator() (const pair < int, int > &x)const{
// auto hash1 = hash < int >{}(x.first);
// auto hash2 = hash < int >{}(x.second);
// return hash1 ^ hash2;
return (ll)x.first * x.second % (1000000007);
}
};
unordered_set < pair < int, int >, HashPair > exist;
int mx1(-1), mx2(-1);
int cnt(0);
pair < int, int > blk[110000];
bitset < 90000 > vis[1];
int ver(0);
int dx[10] = {0, -1, 0, 1, 0};
int dy[10] = {0, 0, 1, 0, -1};
struct Edge{
Edge* nxt;
int to;
OPNEW;
}ed[910000];
ROPNEW(ed);
Edge* head[210000];
void bfs(int idx, int val, int px, int py){
queue < pair < int, int > > cur;
cur.push({px, py});
belong[px][py] = idx;
++blk[idx].second;
while(!cur.empty()){
auto tp = cur.front(); cur.pop();
for(int i = 1; i <= 4; ++i){
int tx = tp.first + dx[i], ty = tp.second + dy[i];
if(!IN_RANGE(tx, ty))continue;
if(mp[tx][ty] == val && !belong[tx][ty])++blk[idx].second, belong[tx][ty] = idx, cur.push({tx, ty});
}
}
}
int main(){
N = read();
for(int i = 1; i <= N; ++i)for(int j = 1; j <= N; ++j)mp[i][j] = read();
for(int i = 1; i <= N; ++i)
for(int j = 1; j <= N; ++j)
if(!belong[i][j]){
blk[++cnt].first = mp[i][j];
bfs(cnt, mp[i][j], i, j);
}
for(int i = 1; i <= N; ++i)
for(int j = 1; j <= N; ++j)
for(int k = 1; k <= 4; ++k){
int tx = i + dx[k], ty = j + dy[k];
if(IN_RANGE(tx, ty) && belong[tx][ty] != belong[i][j]){
int t = belong[tx][ty], s = belong[i][j];
if(exist.find(make_pair(s, t)) == exist.end()){
exist.insert({s, t});
head[s] = new Edge{head[s], t};
head[t] = new Edge{head[t], s};
}
}
}
// for(int i = 1; i <= cnt; ++i)printf("blk[%d] = %d\n", i, blk[i].second);
for(int i = 1; i <= cnt; ++i)mx1 = max(mx1, blk[i].second);
// printf("Belong:\n");
// for(int i = 1; i <= N; ++i)for(int j = 1; j <= N; ++j)printf("%d%c", belong[i][j], j == N ? '\n' : ' ');
// for(int p = 1; p <= cnt; ++p){
// printf("Father: %d: ", p);
// for(auto i = head[p]; i; i = i->nxt){
// printf("%d ", SON);
// }printf("\n");
// }
queue < int > tmp;
queue < int > undel;
for(int p = 1; p <= cnt; ++p)
for(auto t = head[p]; t; t = t->nxt){
if((double)clock() / CLOCKS_PER_SEC > 0.9)printf("%d\n%d\n", mx1, mx2), exit(0);
while(!undel.empty()){
int tp = undel.front(); undel.pop();
vis[ver][tp] = false;
}
int ans(0);
// ++ver;
// vis[ver].reset();
vis[ver][p] = vis[ver][t->to] = true;
undel.push(p), undel.push(t->to);
tmp.push(p), tmp.push(t->to);
ans += blk[p].second, ans += blk[t->to].second;
while(!tmp.empty()){
int tp = tmp.front(); tmp.pop();
for(auto i = head[tp]; i; i = i->nxt){
if(vis[ver][SON])continue;
if(blk[SON].first == blk[p].first || blk[SON].first == blk[t->to].first){
vis[ver][SON] = true;
undel.push(SON);
tmp.push(SON);
ans += blk[SON].second;
}
}
}
mx2 = max(mx2, ans);
}
printf("%d\n%d\n", mx1, mx2);
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-P4376 [USACO18OPEN]Milking Order G
題面
存在 $ n $ 頭犇,你需要給所有犇擠奶,給定 $ m $ 條限制,每條限制有序列 $ c_{n'} $,表示犇 $ c_i $ 需要在 $ c_{i + 1} $ 之前被擠奶。你需要在滿足前 $ k $ 條限制的條件下求出字典序最小的給所有犇擠奶的順序序列。最大化 $ k $ 並輸出此時字典序最小的擠奶序列。
$ 1 \le n \le 10^5, 1 \le m \le 5 \times 10^4, 1 \le \sum n' \le 2 \times 10^5 $。
Examples
Input_1
4 3 3 1 2 3 2 4 2 3 3 4 1
Output_1
1 4 2 3
Solution
首先這個題意二分答案應該很顯然吧?二分 $ k $ 然後考慮驗證。顯然我們每次擠奶的時候是應該選擇不需要在任意犇之後擠奶的犇,這東西不難想到,把每個關係抽象成先擠奶的向後擠奶的連有向邊,這樣每次找的就是入度為 $ 0 $ 的犇,也就是拓樸排序。如果排到最後發現有環路了,那麼顯然說明矛盾,否則記錄一下當前答案為區域性最優解。然後注意需要輸出字典序最小的,那麼我們就把拓樸排序的時候的佇列改成優先佇列即可。最終複雜度 $ O((n + \sum n') \log m) $。
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, M;
struct Edge{
Edge* nxt;
int to;
OPNEW;
}ed[210000];
static Edge* P = ed;
Edge* head[110000];
void* Edge::operator new(size_t){return P++;}
void Clear(void){
P = ed;
memset(head, 0, sizeof(Edge*) * (N + 10));
}
int ind[110000];
vector < int > rnk[51000];
vector < int > ans;
vector < int > tmp;
bool Check(int lim){
memset(ind, 0, sizeof(int) * (N + 10));
Clear();
tmp.clear();
for(int i = 1; i <= lim; ++i){
auto lst = rnk[i].begin();
for(auto it = next(rnk[i].begin()); it != rnk[i].end(); ++it)
++ind[*it], head[*lst] = new Edge{head[*lst], *it}, lst = it;
}
std::priority_queue < int, vector < int >, greater < int > > cur;
for(int i = 1; i <= N; ++i)if(!ind[i])cur.push(i);
while(!cur.empty()){
int tp = cur.top(); cur.pop();
tmp.emplace_back(tp);
for(auto i = head[tp]; i; i = i->nxt){
--ind[SON];
if(!ind[SON])cur.push(SON);
}
}
return (int)tmp.size() == N;
}
int main(){
N = read(), M = read();
for(int i = 1; i <= M; ++i){
int m = read();
while(m--)rnk[i].emplace_back(read());
}
int l = 1, r = M;
while(l <= r){
int mid = (l + r) >> 1;
if(Check(mid))ans.swap(tmp), l = mid + 1;
else r = mid - 1;
}
for(auto i : ans)printf("%d ", i);
printf("\n");
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-P4377 [USACO18OPEN] Talent Show G
題面
給定 $ n $ 頭牛存在重量 $ w_i $ 和才藝水平 $ t_i $,給定重量限制 $ W $,要求選出一些牛使得在 $ \sum w_i \ge W $ 的條件下最大化 $ \dfrac{\sum t_i}{\sum w_i} $,具體地,輸出最大化後的 $ \lfloor ans \times 10^3 \rfloor $。
$ 1 \le n \le 250, 1 \le W \le 10^3, 1 \le w_i \le 10^6, 1 \le t_i \le 10^3 $,保證 $ \sum_{i = 1}^n w_i \ge W $。
Examples
Input_1
3 15 20 21 10 11 30 31
Output_1
1066
Solution
完全可以算是 01分數規劃 的板子了,然後再套個 01揹包即可。具體地,通過 01分數規劃,轉化一下有 $ \sum t_i - ans \times \sum w_i \ge 0 $,二分 $ ans $,然後驗證通過 01揹包 實現,讓價值 $ c_i = t_i - ans \times w_i $,對於 $ j + w_i \ge W $ 直接轉化到 $ dp(W) $,最後判斷一下 $ dp(W) $ 是否大於等於 $ 0 $ 即可。注意邊界 $ dp(0) = 0 $ 和列舉 $ W $ 的那一維需要到 $ 0 $。最終複雜度沒算錯的話大概是 $ O(nW \log(\sum t_i \times eps^{-1})) $,也就是 $ 2e5 \times \log 2e9 $。
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 EPS (double)(1e-7)
template< typename T = int >
inline T read(void);
int N, W;
int wei[300], val[300];
double c[300];
double dp[1100];
bool Check(double lim){
for(int i = 1; i <= 1010; ++i)dp[i] = -114514.0;
for(int i = 1; i <= N; ++i)c[i] = (double)val[i] - lim * (double)wei[i];
for(int i = 1; i <= N; ++i)for(int j = W; j >= 0; --j)
j + wei[i] >= W
? dp[W] = max(dp[W], dp[j] + c[i])
: dp[j + wei[i]] = max(dp[j + wei[i]], dp[j] + c[i]);
return dp[W] >= 0.0;
}
int main(){
N = read(), W = read();
double l(0.0), r(0.0);
for(int i = 1; i <= N; ++i)wei[i] = read(), val[i] = read(), r += (double)val[i];
int ans;
while(l + EPS <= r){
double mid = (l + r) / 2.0;
if(Check(mid))ans = (int)floor(mid * 1000.0), l = mid;
else r = mid;
}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);
int 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-P4378 [USACO18OPEN]Out of Sorts S
題面
給定你氣泡排序的 “奶牛碼” 實現,特別地,定義語句 moo
為輸出 moo
,對於給定的序列求該 “奶牛碼” 會輸出幾次 moo
。
sorted = false
while (not sorted):
sorted = true
moo
for i = 0 to N-2:
if A[i+1] < A[i]:
swap A[i], A[i+1]
sorted = false
$ 1 \le n \le 10^5, 1 \le a_i \le 10^9 $。
Examples
Input_1
5 1 5 3 8 2
Output_1
4
Solution
離散化,樹狀陣列求逆序對個數,沒了。確實沒了,分沒了。。。
放學前五分鐘糊了一個 BIT + 離散化然後交上去直接 WA,回來掃了一眼題目才發現我是**。
觀察發現這玩意不是在每次交換的時候 moo
,而是每一趟嘗試交換的時候叫一下。那麼這東西就需要考慮一下氣泡排序的本質了,發現氣泡排序每次都是掃一遍給所有數的逆序對減少一個,這個應該比較好理解吧,每掃一遍的過程可以認為是把某些數往後移動一段,每次移動都會把這個數對跨過的這段數的 $ 1 $ 的貢獻抵消,也就是減少一個逆序對。那麼我們的答案就是對於每一位數,最大的逆序對個數然後再 $ +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;
int a[110000];
vector < int > data;
class BIT{
private:
int tr[110000];
public:
int lowbit(int x){return x & -x;}
void Modify(int x, int v = 1){while(x <= N)tr[x] += v, x += lowbit(x);}
int Query(int x){int ret(0); while(x)ret += tr[x], x -= lowbit(x); return ret;}
}bit;
int main(){
N = read();
for(int i = 1; i <= N; ++i)a[i] = read(), data.emplace_back(a[i]);
sort(data.begin(), data.end());
data.erase(unique(data.begin(), data.end()), data.end());
for(int i = 1; i <= N; ++i)a[i] = (int)data.size() - distance(data.begin(), lower_bound(data.begin(), data.end(), a[i]) + 1) + 1;
int ans(0);
for(int i = 1; i <= N; ++i)ans = max(ans, bit.Query(a[i] - 1)), bit.Modify(a[i]);
printf("%d\n", ans + 1);
fprintf(stderr, "Time: %.6lf\n", (double)clock() / CLOCKS_PER_SEC);
return 0;
}
template < typename T >
inline T read(void){
T ret(0);
int 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-P4375 [USACO18OPEN]Out of Sorts G
題面
與上題題意及範圍不變,奶牛碼變成如下:
sorted = false
while (not sorted):
sorted = true
moo
for i = 0 to N-2:
if A[i+1] < A[i]:
swap A[i], A[i+1]
for i = N-2 downto 0:
if A[i+1] < A[i]:
swap A[i], A[i+1]
for i = 0 to N-2:
if A[i+1] < A[i]:
sorted = false
Examples
Input_1
5 1 8 5 3 2
Output_1
2
Solution
可以嘗試不用 BIT。。可以用 Treap,可以用 LCT。。
考慮和上一題有何不同,上一題是每個數之前的數對其貢獻的逆序對取 $ \max $,因為每次會把數前面的一個大於它的數移到其之後。考慮本題,不難想到每次先把一個大於它的丟到之後,再把一個小於它的丟到之前,考慮對於一個具體的有序的位置 $ i $,不難想到是每次從 $ [1, i] $ 中丟出去一個不屬於這段裡的並且同時丟進來一個屬於這段的,也就是說位置 $ i $ 的答案就是 $ [1, i] $ 中不符合的數量(注意這裡的位置是在排序之後的有序位置)。所以本題和位置相關,我們把權值排個序,然後丟下標進 BIT 求逆序對即可。記得答案需要和 $ 1 $ 取 $ \max $。
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;
pair < int, int > a[110000];
class BIT{
private:
int tr[110000];
public:
int lowbit(int x){return x & -x;}
void Modify(int x, int v = 1){while(x <= N)tr[x] += v, x += lowbit(x);}
int Query(int x){int ret(0); while(x)ret += tr[x], x -= lowbit(x); return ret;}
}bit;
int main(){
N = read();
int ans(1);
for(int i = 1; i <= N; ++i)a[i] = {read(), i};
sort(a + 1, a + N + 1);
for(int i = 1; i <= N; ++i)
bit.Modify(a[i].second),
ans = max(ans, i - bit.Query(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);
int 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-P4372 [USACO18OPEN]Out of Sorts P
題面
存在一個奇怪的快速排序,大致過程是每次通過氣泡排序找到分割點,定義分割點為其左側的數均小於它,右側均大於它。找到分割點之後將原序列分割並遞迴處理被分出來的序列。求最終 work_counter
的值。
主程式奶牛碼為:
quickish_sort (A) {
if length(A) = 1, return
do { // Main loop
work_counter = work_counter + length(A)
bubble_sort_pass(A)
} while (no partition points exist in A)
divide A at all partition points; recursively quickish_sort each piece
}
對於氣泡排序為:
bubble_sort_pass (A) {
for i = 0 to length(A)-2
if A[i] > A[i+1], swap A[i] and A[i+1]
}
Examples
Input_1
7 20 2 3 4 9 8 7
Output_1
12
Solution
不難發現題意就是要我們求每次氣泡排序的長度求和,然後發現這東西也不太好求,於是再次轉化為求每個數進行了多少次氣泡排序,次數求和顯然也是原來的值。不難想到,對於一個數,其不會再被進行氣泡排序當且僅當其左右兩個數均為分割點,所以我們的問題就再次轉化為求每個點在幾次氣泡排序後才會變成分割點,令其為 $ tim_i $,那麼答案就是 $ \sum_{i = 1}^n \max(tim_i, tim_{i - 1}) $。
考慮維護這玩意,不難想到,每次氣泡排序都會把這個數相對所有其後面的小於它的數的距離減 $ 1 $,所以答案也就是距離其最遠的那個不合法的位置和這個數的位置的距離,記得和 $ 1 $ 取 $ \max $。
對於具體的維護方式用單調佇列或者雙指標之類的東西弄一下即可,注意需要離散化,且這個離散化和掉進兔子洞那題差不多,對於相同的值離散化之後的值應該是不同的,這樣我們直接倒序遍歷,然後維護一個指標,指向第一個滿足 $ a_i \lt i $ 的,求個距離即為答案。
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;
int a[110000];
vector < int > data;
int tim[110000];
int cnt[110000];
int main(){
N = read();
for(int i = 1; i <= N; ++i)data.emplace_back(a[i] = read());
sort(data.begin(), data.end()); //data.erase(unique(data.begin(), data.end()), data.end());
for(int i = 1; i <= N; ++i)a[i] = distance(data.begin(), lower_bound(data.begin(), data.end(), a[i]) + 1), a[i] += cnt[a[i]]++;
int lst(N);
for(int i = N; i >= 1; --i){
while(a[lst] > i)--lst;
tim[i] = max(lst - i, 1);
}ll ans(0);
for(int i = 1; i <= N; ++i)ans += max(tim[i - 1], tim[i]);
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);
int 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-P4374 [USACO18OPEN]Disruption P
題面
給定一棵 $ n $ 節點無權樹,額外地,給定 $ m $ 條有權無向邊,連結在 $ n $ 個節點之間。對於原始的樹上 $ n - 1 $ 條邊的每一條,求出若刪去這條邊,新增 $ m $ 條額外邊的哪一條可以在使樹再次連通的基礎上最小化權值,求該權值。
$ 2 \le n, m \le 5 \times 10^4 $。
Examples
Input_1
6 3 1 2 1 3 4 1 4 5 6 5 2 3 7 3 6 8 6 4 5
Output_1
7 7 8 5 5
Solution
依然在題解裡先偷個圖。。
顯然考慮對於我們每一條連的額外邊,如 $ 5 \Longleftrightarrow 12 $,顯然只會對樹上兩點之間的路徑上的邊產生貢獻,這個比較容易想吧。。隨便畫一下就好,比如顯然如果把原樹上非該路徑上的邊刪掉,即使連上這條額外邊也無濟於事。然後對於某一條原樹邊,顯然答案就是所有貢獻取個 $ \min $。說到這一個最顯而易見的做法已經出來了,樹剖轉成序列,然後線段樹上求區間 $ \min $。
然後我們發現,如果把額外邊降序排序,顯然後面的邊更優,那麼每次操作實際上就是覆蓋原來的值,那麼這東西很顯然就是個 assign
,而且只有 assign
沒有任何其它操作,所以可以考慮直接上個 樹剖 + ODT 即可,理論上對於只有 assign
的操作 ODT 應該更快,但是可能常數太大了,所以最終表現似乎不如線段樹?
然後在此之上,我們又可以想到一個做法,把額外邊升序排列,這樣前面的一定更優,打個標記,對於已經更新過的不去更新即可,然後用倍增或者 Tarjan 直接的求 LCA 找樹上路徑之類的,可以省掉一個樹剖。
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 idx;
OPNEW;
}ed[110000];
ROPNEW(ed);
Edge* head[51000];
int ans[51000], nod_idx[51000];
int N, M;
int dep[51000], dfn[51000], idx[51000], top[51000], siz[51000], fa[51000], hson[51000];
struct Node{
int l, r;
mutable int val;
friend const bool operator < (const Node &a, const Node &b){
return a.l < b.l;
}
};
class ODT{
private:
set < Node > tr;
public:
auto Insert(Node x){return tr.insert(x);}
auto Split(int p){
auto it = tr.lower_bound(Node{p});
if(it != tr.end() && it->l == p)return it;
--it;
// printf("%d %d %d\n", it->l, it->r, it->val);
int l = it->l, r = it->r, v = it->val;
tr.erase(it), Insert(Node{l, p - 1, v});
return Insert(Node{p, r, v}).first;
}
void Assign(int l, int r, int v){
if(l > r)return;
// printf("assigning %d~%d, %d\n", l, r, v);
auto itR = Split(r + 1), itL = Split(l);
tr.erase(itL, itR);
Insert(Node{l, r, v});
}
void SetAns(void){
for(auto nod : tr)
for(int i = nod.l; i <= nod.r; ++i)
ans[nod_idx[idx[i]]] = nod.val;
}
}odt;
struct Edges{
int s, t;
int val;
friend const bool operator < (const Edges &a, const Edges &b){
return a.val > b.val;
}
};
vector < Edges > es;
void dfs_pre(int p = 1, int ffa = 0){
fa[p] = ffa;
dep[p] = dep[ffa] + 1;
siz[p] = 1;
for(auto i = head[p]; i; i = i->nxt){
if(SON == ffa)continue;
nod_idx[SON] = i->idx;
dfs_pre(SON, p);
siz[p] += siz[SON];
if(siz[SON] > siz[hson[p]])hson[p] = SON;
}
}
void dfs(int p = 1, int tp = 1){
static int cdfn(0);
top[p] = tp;
dfn[p] = ++cdfn;
idx[cdfn] = p;
if(hson[p])dfs(hson[p], tp);
for(auto i = head[p]; i; i = i->nxt){
if(SON == fa[p] || SON == hson[p])continue;
dfs(SON, SON);
}
}
void AssignRange(int s, int t, int val){
while(top[s] != top[t]){
if(dep[top[s]] < dep[top[t]])swap(s, t);
odt.Assign(dfn[top[s]], dfn[s], val);
s = fa[top[s]];
}if(dep[s] < dep[t])swap(s, t);
odt.Assign(dfn[t] + 1, dfn[s], val);
}
int main(){
N = read(), M = read();
for(int i = 1; i <= N - 1; ++i){
int s = read(), t = read();
head[s] = new Edge{head[s], t, i};
head[t] = new Edge{head[t], s, i};
}dfs_pre(), dfs();
// for(int i = 1; i <= N; ++i)printf("[%d]: dfn: %d, fa: %d, dep: %d, top: %d, hson: %d\n", i, dfn[i], fa[i], dep[i], top[i], hson[i]);
// for(int i = 1; i <= N; ++i)printf("nodidx[%d]: %d\n", i, nod_idx[i]);
odt.Insert(Node{1, N, -1});
for(int i = 1; i <= M; ++i){
int s = read(), t = read(), v = read();
es.emplace_back(Edges{s, t, v});
}sort(es.begin(), es.end());
for(auto e : es)AssignRange(e.s, e.t, e.val);
odt.SetAns();
for(int i = 1; i <= N - 1; ++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);
int 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-P4373 [USACO18OPEN]Train Tracking P
題面
給定長度為 $ n $ 的序列 $ c_n $,按序求出其中每段長度為 $ k $ 的子串中的最小值。特別地,整個過程中在全域性你只能使用一個長度為 $ 5.5 \times 10^3 $ 的 int
型別靜態陣列,且存取次數和不能超過 $ 2.5 \times 10^7 $ 次,僅在處理序列中單獨某個數時,或者說在 helpBessie()
中對於區域性變數無空間限制,且你可以獲取 $ 2 $ 次整個序列。
題目通過互動實現,具體地,對於序列中每個值都會呼叫一次 helpBessie()
,你需要實現該函式,函式中對於區域性非靜態變數空間無限制(或者說 256MiB
),在函式中你可以通過呼叫互動庫來獲取本次的基本資訊和存取數,即禁止使用靜態變數或全域性變數。對於具體的實現方式請參考 原題面。
$ 1 \le k \le n \le 10^6, 1 \le c_i \le 10^9 $。
Examples
Input_1
10 3 5 7 9 2 0 1 7 4 3 6
Output_1
5 2 0 0 0 1 3 3
Tips:輸入輸出不代表互動過程。
Solution
顯然如果沒有空間的限制的話我們直接寫個滑動視窗,或者說單調佇列即可很簡單的求解。但是發現這個單調佇列的空間是 $ O(n) $ 的肯定存不下,然後看一下空間的大小,發現空間限制是 $ O(\sqrt{n}) $ 的,於是不難想到分塊實現。
因為我們一共可以獲取兩次序列的值,所以可以考慮先將序列分成 $ \sqrt{n} $ 塊,然後在第一次的時候對於每一塊維護一個。。。
然後先跳了,沒啥好題解,僅有的兩個中文題解沒能太明白,程式碼也沒看懂,如果 NOIP 沒退役的話就回來補這題。。
Tips:對於互動題的除錯,我們實際上可以將互動庫中的函式手動模擬實現,然後寫個主函式呼叫對應的函式 “模擬” 一遍互動的過程,然後對比結果。如 void set(int index, int value){a[index] = val;}
。
Code
//TODO
UPD
update-2022_11_15 初稿