【bzoj4881】[Lydsy2017年5月月赛]线段游戏 树状数组+STL-set
題目描述
quailty和tangjz正在玩一個關于線段的游戲。在平面上有n條線段,編號依次為1到n。其中第i條線段的兩端點坐標分別為(0,i)和(1,p_i),其中p_1,p_2,...,p_n構成了1到n的一個排列。quailty先手,他可以選擇一些互不相交的線段,將它們拿走,當然他也可以一條線段也不選。然后tangjz必須拿走所有剩下的線段,若有兩條線段相交,那么他就輸了,否則他就贏了。注意若quailty拿走了全部線段,那么tangjz也會勝利。quailty深深喜歡著tangjz,所以他不希望tangjz輸掉游戲,請計算他有多少種選擇線段的方式,使得tangjz可以贏得游戲。輸入
第一行包含一個正整數n(1<=n<=100000),表示線段的個數。 第二行包含n個正整數p_1,p_2,...,p_n(1<=p_i<=n),含義如題面所述。輸出
輸出一行一個整數,即tangjz勝利的方案數,因為答案很大,請對998244353取模輸出。樣例輸入
5
1 2 4 5 3
樣例輸出
8
題目大意
給定一個1~n的全排列序列,求出將這個序列分成兩個都不含逆序對的子序列的方案數(子序列可以為空,可以不連續)
題解
樹狀數組+STL-set
先說一下個人的思路吧~(按照這個思路T了,后面會講優化)
首先,一個逆序對不能分在同一個子序列里,即一個逆序對必須分到兩個不同的子序列里。
對于每個逆序對組(一個集合,其中每個元素都至少與一個其它元素存在逆序對關系),只存在兩種不同的分法,所以可以用帶權并查集來維護逆序對組數。
于是每次找到一個數,就看它前面有多少個比它大的數,然后將所有比它大的數與它在帶權并查集中合并,若矛盾則無解,最后統計一下就可以了。
而這里如果加了無解判斷,那么合并操作的總次數是O(n)級別的。
然而一開始用set TLE了,才發現set很難查詢一段區間,時間會很長。
所以要手寫Treap或Splay,果斷放棄。
最后還是參考了下 CQzhangyu 大犇的做法:先用樹狀數組求最長下降子序列,判斷是否達到3導致無解;然后插入時只保留它和比它大的數中的最大的那個,最后答案為2^size。
這里簡單證明一下:按照我的做法,每次帶權并查集合并時都要合并很多數,而實際上如果有解,那么只需要保留一個逆序對組的一個元素即可代表整個組。由于是逆序對,這個元素最大時才能代表整個組來繼續進行接下來的元素的合并操作。
所以不需要每次都找所有比當前數大的,只需要維護最大值就行了。
#include <cstdio> #include <set> #define N 100010 using namespace std; int n , f[N] , a[N]; set<int> s; set<int>::iterator it; void update(int x , int a) {int i;for(i = x ; i <= n ; i += i & -i) f[i] = max(f[i] , a); } int query(int x) {int i , ans = 0;for(i = x ; i ; i -= i & -i) ans = max(ans , f[i]);return ans; } int main() {int i , tmp , ans = 1;scanf("%d" , &n);for(i = 1 ; i <= n ; i ++ ){scanf("%d" , &a[i]);tmp = query(n - a[i]);if(tmp >= 2){printf("0\n");return 0;}update(n - a[i] + 1 , tmp + 1);}for(i = 1 ; i <= n ; i ++ ){tmp = a[i];while((it = s.upper_bound(tmp)) != s.end()) tmp = *it , s.erase(tmp);s.insert(tmp);}tmp = s.size();while(tmp -- ) ans = ans * 2 % 998244353;printf("%d\n" , ans);return 0; }?
轉載于:https://www.cnblogs.com/GXZlegend/p/6867336.html
總結
以上是生活随笔為你收集整理的【bzoj4881】[Lydsy2017年5月月赛]线段游戏 树状数组+STL-set的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: JavsScript--on与addEv
- 下一篇: 转!!配置Tomcat时server.x