USACO 2021~2022 比賽記錄
前言
USACO 2020~2021 沒寫過這玩意,當時打到了金組,這個賽季來寫一下。
USACO 2021~2022 DEC Gold
T1
不會。
試圖搞出 \(T=1\) 但寫掛了,最后只過了樣例的 \(3/20\) 個點。
等同於啥都沒過就不放代碼了= =
T2
一開始(題還沒讀完的時候)莫名其妙有個念頭:整體二分。當然這顯然在胡扯。但是這個思路至關重要,使得我一上來就去想對於一個新的 \(p_i\) 對每個 \(x+0.5\) 會有什么貢獻,直接推出了正解;而不是像我的其他同學一樣去想對每個 \(x+0.5\) 分別求答案,最后做不出來。
開了個 Excel 手模樣例,發現對於一個新的 \(p_i\),假設 \(p_1\sim p_{i-1}\) 里詢問過的小於 \(p_i\) 的最大的數為 \(pre\),大於 \(p_i\) 的最小的數為 \(suc\)(即前驅后繼),那么 \(pre,p_i,suc\) 會將所有 \(x+0.5\) 分成四段:
- 若 \(x+0.5 < pre\):根據題意,這次詢問會因為可以根據 \(pre\) 的結果推出而被忽略,不會貢獻答案。
- 若 \(pre < x+0.5 < p_i\):這部分的詢問結果不能根據已有的結果推出,會進行詢問,並貢獻一個
HI
。 - 若 \(p_i < x+0.5 < suc\):這部分的詢問結果不能根據已有的結果推出,會進行詢問,並貢獻一個
LO
。 - 若 \(suc < x+0.5\):根據題意,這次詢問會因為可以根據 \(suc\) 的結果推出而被忽略,不會貢獻答案。
於是問題轉化為了設計一個數據結構,使得支持對於一個區間在字符串末尾添加 HI
或 LO
,並統計 HILO
個數。顯然可以通過打標記的線段樹維護。
過了 \(20/20\) 個點。
寫得不太好看的賽時 AC 代碼:
//By: Luogu@rui_er(122461)
#include <bits/stdc++.h>
#define rep(x,y,z) for(int x=y;x<=z;x++)
#define per(x,y,z) for(int x=y;x>=z;x--)
#define debug printf("Running %s on line %d...\n",__FUNCTION__,__LINE__)
#define fileIO(s) do{freopen(s".in","r",stdin);freopen(s".out","w",stdout);}while(false)
using namespace std;
typedef long long ll;
const int N = 2e5+5;
int n, p[N], pos[N], ps[N];
set<int> sps, sng;
template<typename T> void chkmin(T& x, T y) {if(x > y) x = y;}
template<typename T> void chkmax(T& x, T y) {if(x < y) x = y;}
enum Color {
UK = 0, HI, LO
};
struct Node {
int HILO;
Color tag;
};
struct SegTree {
Node t[N<<5];
#define lc(u) (u<<1)
#define rc(u) (u<<1|1)
void pushup(int u) {
t[u].HILO = t[lc(u)].HILO + t[rc(u)].HILO;
}
void pushdown(int u) {
if(t[u].HILO) {
t[lc(u)].HILO += t[u].HILO;
t[rc(u)].HILO += t[u].HILO;
t[u].HILO = 0;
}
if(t[u].tag == UK) return;
if(t[u].tag == HI) {
t[lc(u)].tag = HI;
t[rc(u)].tag = HI;
t[u].tag = UK;
}
else {
if(t[lc(u)].tag == HI) ++t[lc(u)].HILO;
if(t[rc(u)].tag == HI) ++t[rc(u)].HILO;
t[lc(u)].tag = LO;
t[rc(u)].tag = LO;
t[u].tag = UK;
}
}
void modify(int u, int l, int r, int ql, int qr, Color val) {
if(ql <= l && r <= qr) {
if(t[u].tag == HI && val == LO) {
++t[u].HILO;
t[lc(u)].tag = UK;
t[rc(u)].tag = UK;
}
t[u].tag = val;
return;
}
pushdown(u);
int mid = (l + r) >> 1;
if(ql <= mid) modify(lc(u), l, mid, ql, qr, val);
if(qr > mid) modify(rc(u), mid+1, r, ql, qr, val);
// pushup(u);
}
void print(int u, int l, int r) {
if(l == r) {
printf("%d\n", t[u].HILO);
return;
}
pushdown(u);
int mid = (l + r) >> 1;
print(lc(u), l, mid);
print(rc(u), mid+1, r);
}
}sgt;
int main() {
scanf("%d", &n);
rep(i, 1, n) {
scanf("%d", &p[i]);
pos[p[i]] = i;
}
ps[0] = pos[1];
ps[n] = pos[n];
rep(i, 1, n-1) ps[i] = max(pos[i], pos[i+1]);
rep(i, 1, n) {
auto lwit = sng.upper_bound(-p[i]);
auto upit = sps.upper_bound(p[i]);
int lw = lwit == sng.end() ? 0 : -*lwit;
int up = upit == sps.end() ? n : *upit;
// printf("i=%d lw=%d up=%d p=%d\n", i, lw, up, p[i]);
sgt.modify(1, 0, n, lw, p[i]-1, HI);
// printf("GIVEHI [%d, %d]\n", lw, p[i]-1);
sgt.modify(1, 0, n, p[i], up-1, LO);
// printf("GIVELO [%d, %d]\n", p[i], up-1);
sng.insert(-p[i]);
sps.insert(p[i]);
}
sgt.print(1, 0, n);
return 0;
}
T3
亂搞題,沒啥技術含量。
首先如果一個手鐲被分成了兩個部分,即先后存在三束光線,使得這個手鐲的狀態是有、無、有,那么一定不符合要求。
然后如果兩個手鐲沒有交叉,在任意一束光線處觀察,都一定可以通過將每個手鐲的兩次出現替換為有權值的左、右括號(小括號、中括號、大括號、第四括號……),得到一個合法的括號序列。判掉了在一束光線處有交叉的情況。
還有就是如果兩個手鐲的位置關系變了,一定被分成了兩個部分或有交叉。A 與 B 的位置關系包括:A 在 B 前面、A 在 B 后面、A 包含於 B、A 包含 B。每種情況開個 vector 或類似的東西維護即可。
交上去,發現過了大多數點,但少數點過不去。手模了幾組數據,把自己 hack 了。
問題在於漏了一種情況:顯然,如果手鐲 A 包含於手鐲 B,那么當 B 閉合時(即在一束光線中不再出現時),A 一定更早地閉合了,不可能有一束光線沒有 B 但有 A,否則此時必然存在交叉。
加上這個判斷即可,過了 \(20/20\) 個點。
寫得不太好看的賽時 AC 代碼:
//By: Luogu@rui_er(122461)
#include <bits/stdc++.h>
#define rep(x,y,z) for(int x=y;x<=z;x++)
#define per(x,y,z) for(int x=y;x>=z;x--)
#define debug printf("Running %s on line %d...\n",__FUNCTION__,__LINE__)
#define fileIO(s) do{freopen(s".in","r",stdin);freopen(s".out","w",stdout);}while(false)
using namespace std;
typedef long long ll;
const int N = 105;
int T, n, m, k[N], buc[N][N], tmp[N], tmpv1[N];
vector<int> a[N];
stack<int> stkv1;
set<int> ins[N], aft[N];
template<typename T> void chkmin(T& x, T y) {if(x > y) x = y;}
template<typename T> void chkmax(T& x, T y) {if(x < y) x = y;}
bool verdictBracket(int id) {
rep(i, 1, n) tmpv1[i] = 0;
while(!stkv1.empty()) stkv1.pop();
for(auto i : a[id]) {
if(!tmpv1[i]) {
rep(j, 1, n) {
if(i == j) continue;
if(tmpv1[j] == 1) {
ins[j].insert(i);
if(ins[i].find(j) != ins[i].end()
|| aft[i].find(j) != aft[i].end()
|| aft[j].find(i) != aft[j].end())
return
// printf("VER! @%d.%d.%d\n", id, i, j)&
0;
}
if(tmpv1[j] == 2) {
aft[j].insert(i);
if(ins[i].find(j) != ins[i].end()
|| ins[j].find(i) != ins[j].end()
|| aft[i].find(j) != aft[i].end())
return
// printf("VER? @%d.%d.%d\n", id, i, j)&
0;
}
}
tmpv1[i] = 1;
stkv1.push(i);
}
else if(i == stkv1.top()) {
tmpv1[i] = 2;
stkv1.pop();
}
else return 0;
}
rep(i, 1, n) {
if(!tmpv1[i]) {
for(auto j : ins[i]) {
if(tmpv1[j]) return 0;
}
}
}
return 1;
}
#define give(result) do{puts(result);goto Init;}while(false)
int main() {
for(scanf("%d", &T);T;T--) {
scanf("%d%d", &n, &m);
rep(i, 1, m) {
scanf("%d", &k[i]);
a[i].resize(k[i]);
rep(j, 0, k[i]-1) {
scanf("%d", &a[i][j]);
++buc[i][a[i][j]];
}
}
rep(i, 1, m) {
if(!verdictBracket(i)) give("NO");
rep(j, 1, n) {
if(!tmp[j] && buc[i][j] == 2) ++tmp[j];
else if(tmp[j] == 1 && !buc[i][j]) ++tmp[j];
else if(tmp[j] == 2 && buc[i][j] == 2) ++tmp[j];
// printf("%d%c", tmp[j], " \n"[j==n]);
if(tmp[j] == 3) give("NO");
}
}
per(i, m, 1) if(!verdictBracket(i)) give("NO"); // rejudge it (reverse HACK)
give("YES");
Init:
rep(i, 1, m) {
k[i] = 0;
a[i].clear();
rep(j, 1, n) buc[i][j] = 0;
}
rep(i, 1, n) {
tmp[i] = 0;
ins[i].clear();
aft[i].clear();
}
n = m = 0;
}
return 0;
}
/*
HACKED (Verdict: WRONG_ANSWER)
1
4 2
4 1 2 2 1
2 2 2
Expected: NO
Found: YES
Rejudge: NO (Verdict: OK)
*/
總結
符合預期。還記得 USACO 2020~2021 OPEN Gold 我還啥都不會直接棄了呢。按照總滿分為 \(1000\) 分計,大概是得到了 \(716.67\) 分,貌似卡在了晉級/不晉級的中間,只能看人品了。。
upd:貌似上 P 失敗了。。