AtCoder Beginner Contest 217 D~E
比賽連結:Here
ABC水題,
D - Cutting Woods
題意:開始一根木棒長度為 \(n\) 並以 \(1\) 為單位在木棒上標記\((1\sim n)\) ,輸出 \(q\) 次操作
- 操作 \(1\) 斷開 \(x\) 所在的木棒:\([1,n]\) 在 \(x\) 斷開變成了 \([1,x],[x + 1,n]\)
- 操作 \(2\) 查詢 \(x\) 所在區間的長度
資料範圍:\(n\le 10^{9},q\le 1e5,1\le x\le 1e9\)
題解:
一開始沒有想到這個性質所以卡住了,
在分割之後左邊以及右邊這個區間的答案是固定的,也就是說答案只跟分割點有關,比如區間 \([l,r]\)
可以發現可以發現
\(l\) 到 \(x\) 這個區間的詢問答案都是 \(x−l+1\)
\(x+1\) 到 \(r\) 這個區間的詢問答案都是 \(r−l+1\)
所以可以考慮二分找到所在區間相鄰 \(2\) 個的分割點下標
先考慮邊界問題
邊界無非是:\(0\sim n + 1,0\sim n,1\sim n,1\sim n +1\) 這 \(4\) 種的其中一種
這個時候先假設區間是 \([1,2],[3,4],[5]\)
分割點是 0/1 2 4 5/5+1
對於查詢2
找到第一個大於等於2的數是2
第一個小於2的數應該是 0/1
答案是2
所以應該是 2 - 0 = 2
左邊界應該是0在考慮查詢5
答案是1
找到第一個大於等於5的數是5/6
第一個小於5的數應該是4
所以應該是 5 - 4 = 1
右邊界是n在考慮二分操作的時候 分割點陣列保持有序
所以可以用set動態維護
時間複雜度:\(\mathcal{O}(Nlog N)\)
set<int>s; int main() { ios::sync_with_stdio(false), cin.tie(nullptr); int n, m; cin >> n >> m; s.insert(0); s.insert(n); while (m--) { int c, x; cin >> c >> x; if (c == 1) s.insert(x); else cout << *s.lower_bound(x) - *--s.upper_bound(x) << "\n"; // 因為沒 -- 調了半天 } }
E - Sorting Queries
題意:給出一個空的序列和 \(q\) 次操作
- 操作 \(1\): 在序列末尾新增一個數 \(x\)
- 操作 \(2\) :輸出序列第一個數
- 操作 \(3\):給當前序列排序
資料範圍:\(q\le 1e5,1\le x\le 1e9\)
題解:
假設不考慮操作 \(3\),那麼我們可直接模擬,
但在操作 \(3\)的影響下,需要維護排序對佇列的影響,
假設佇列中兩個元素,\(head,fail\) 對應隊頭和隊尾
暴力時間複雜度是 \(\mathcal{O}(n^2 logn)\) 肯定是不可取的
在排序的時候我們可以考慮邊插入邊排序,用 \(set\) 維護
然後把對頭指向 \(set\) 後面的第一個下標
這樣時間複雜度便降到了 \(\mathcal{O}(n logn)\)
const int N = 1e6 + 10;
ll a[N];
multiset<ll>s;
bool st[N];
int main() {
ios::sync_with_stdio(false), cin.tie(nullptr);
int head = 0, fail = 0;
int t; cin >> t;
int k = 1; // k = 1 表示佇列第一個數的下標是1
while (t--) {
int op; cin >> op;
if (op == 1) {
int x; cin >> x;
a[++fail] = x;
} else if (op == 2) {
// 如果排序過
if (s.size()) {
cout << *s.begin() << "\n" ;
s.erase(s.begin());
} else {
cout << a[k] << "\n";
st[k] = 1;
k += 1;
}
} else {
for (int i = k ; i <= fail ; i ++) {
if (!st[i]) s.insert(a[i]) ;
}
// 插入之後 對頭指向隊尾的下一個下標
k = fail + 1 ;
}
}
}
The desire of his soul is the prophecy of his fate
你靈魂的慾望,是你命運的先知。