2019 各省省选试题选做

蒟蒻快活不了了,必须找点题摩擦了

记录表

省份 d1t1 d1t2 d1t3 d2t1 d2t2 d2t3
十二省联考 $√$ $√$        
ZJOI            
HNOI            
GX/GZOI            
BJOI $√$          
SNOI             
JSOI             
TJOI             
SDOI             

 注意事项:

1. GZOI 不是广州 OI,是贵州 OI!

2. 本文只贴签到题的完整题解,较长的题解放新文链接。


十二省联考

d1t1 异或粽子

签到题

这道题有很像的原题而且做法没什么区别

我这么菜甚至以前还出过这道题

好吧,总之就是这题很送

可持久化 trie,第 $i$ 个版本表示后缀 $[i,n]$ 中,第一大前缀异或和。用一个堆,开 $pair$ 存放 $[1...n,n]$ 这 $n$ 个后缀的最大前缀和 及每个最大前缀和对应的后缀的左端点,然后弹 $k-1$ 次堆顶,每次弹堆顶时 把堆顶那个最大前缀和对应的后缀求下一大前缀和,将其存入堆(若这个后缀中的所有前缀都被弹过,就忽略关于下一大前缀和的操作)。第 $k$ 次的堆顶就是答案了。

这样做的正确性显然:你正常的想法就是进行 $k-1$ 轮这样的操作:找到序列的最大异或和,删掉它。而任何时刻,序列的最大异或和都必定对应一个区间,也就对应一个左端点,那么我们只要同时维护所有左端点对应的后缀的答案,再取最优就行了。

时间复杂度 $O(k imes log(n))$。

我的 code(把以前自己写的标程拷过来改一改)

 1 #pragma GCC optimize(2)
 2 #include<bits/stdc++.h>
 3 #define rep(i,x,y) for(int i=(x);i<=(y);++i)
 4 //#define int long long
 5 //#define uint long long
 6 #define ll long long
 7 #define N 500002
 8 #define pii pair<uint,int>
 9 #define mp make_pair
10 using namespace std;
11 inline uint read(){
12     uint x=0; bool f=1; char c=getchar();
13     for(;!isdigit(c);c=getchar()) if(c=='-') f=0;
14     for(; isdigit(c);c=getchar()) x=(x<<3)+(x<<1)+(c^'0');
15     if(f) return x;
16     return 0-x;
17 }
18 int n,k,num[N][34],rt[N],rk[N],cnt;
19 uint a,sum[N];
20 struct Tree{int siz,son[2];}tr[N*34];
21 void modify(int lst,int &o,int x,int step){
22     if(!o) o=++cnt, tr[o]=tr[lst];
23     tr[o].siz++;
24     if(!step) return;
25     if(!num[x][step]) tr[o].son[0]=0, modify(tr[lst].son[0], tr[o].son[0], x, step-1);
26     else tr[o].son[1]=0, modify(tr[lst].son[1], tr[o].son[1], x, step-1);
27 }
28 uint query(int o,int x,int k,uint v,int step){
29     assert(o);
30     if(!step) return v;
31     int s=num[x][step]^1;
32     if(tr[tr[o].son[s]].siz>=k) return query(tr[o].son[s], x, k, v+(1ll<<(step-1)), step-1);
33     else return query(tr[o].son[s^1], x, k-tr[tr[o].son[s]].siz, v, step-1);
34 }
35 priority_queue<pii> pq;
36 ll ans;
37 signed main(){
38     n=read(), k=read();
39     modify(0,rt[0],0,32);
40     rep(i,1,n){
41         a=read(), sum[i]=sum[i-1]^a;
42         a=sum[i];
43         rep(j,1,32) num[i][j]=0;
44         int cnt=0;
45         while(a>0) num[i][++cnt]=a&1, a>>=1;
46         rk[i]=1, pq.push(mp(query(rt[i-1],i,1,0,32),i));
47         modify(rt[i-1],rt[i],i,32);
48     }
49     pii cur;
50     for(int i=1;i<k;++i){
51         cur=pq.top(), pq.pop();
52         ans+=cur.first;
53         int x=cur.second;
54         if(rk[x]+1<=x) pq.push(mp(query(rt[x-1],x,++rk[x],0,32),x));
55     }
56     cout<<ans+pq.top().first;
57     return 0;
58 }
59 /*
60 3 3
61 2 2 4
62 */
View Code

题外话:

1. 这题可以同时在 $n$ 个版本的 $trie$ 树上二分($syf$ 的做法)

2. 我出的那道题可以二分

d1t2 字符串问题

链接

d1t3 骗分过样例

链接

d2t1 皮配

d2t2 春节十二响

签到题

不难发现,对于一条在根拐弯的链,设其左链比右链长,其答案就是 max{ 左链的最大 $M_i$ 值,右链的最大 $M_i$ 值 } + max{ 左链的第二大 $M_i$ 值,右链的第二大 $M_i$ 值} + ... + max{ 左链的第 ? 大 $M_i$ 值,右链的最小 $M_i$ 值 } + 左链其余更小的 $M_i$ 值之和 + 根的 $M_i$ 值。右链比左链长同理。

这样贪心的正确性显然可以反证出来。

拓展到树上,我们相当于要从叶子往根“合并”每棵子树的堆,其中一棵子树的堆 存的是这棵子树的所有段的大小,堆的大小显然是这棵子树的最深点的深度(至少要用这么多个段,在此基础上让段的数量更少,即合并更多的子程序,答案显然会更优)。

这里的“合并”不是把两个大小为 $a$ 和 $b$ 的堆合并成大小为 $a+b$ 的堆的那个“合并”,而是这样的:在两个堆的最大值中取大的那个放入新堆,删掉两个堆的最大值,再取两个堆的最大值中取大的那个放入新堆,再删掉两个堆的最大值……直到有一个堆为空,然后把另一个堆剩下的数都放入新堆。一个子树的根 “合并”完其所有儿子子树的堆之后,会把它自己的 $M_i$ 值加入堆(显然根只能独自在一个段)。其实就是模拟之前说的那个 统计在根拐弯的链的答案的步骤

然而暴力“合并”的复杂度好像是错的。

但你仔细回顾前文就会发现,只需要使用适当的合并方法,按点的深度从大往小合并,一个点在合并其所有儿子子树时 先 $O(1)$ 继承其 $size$ 值最大的儿子堆,就能过了。

原因很显然——如 $Destinies$ 大佬所说,根据长链剖分的原理,这题的堆“合并”实际上就是长链剖分的数组合并!原树中每个点只会被合并一次!

所以时空复杂度直接参考长链剖分。时间复杂度再套上每次求堆顶的 $log$,是 $O(nlog{n})$ 的,不是 $O(nlog^2{n})$ 的!!!

 1 #include<bits/stdc++.h>
 2 #define rep(i,x,y) for(int i=(x);i<=(y);++i)
 3 #define dwn(i,x,y) for(int i=(x);i>=(y);--i)
 4 #define rep_e(i,u) for(int i=hd[u];i;i=e[i].nxt)
 5 #define ll long long
 6 #define N 200002
 7 using namespace std;
 8 inline int read(){
 9     int x=0; bool f=1; char c=getchar();
10     for(;!isdigit(c);c=getchar()) if(c=='-') f=0;
11     for(; isdigit(c);c=getchar()) x=(x<<3)+(x<<1)+(c^'0');
12     if(f) return x;
13     return 0-x;
14 }
15 int n,a[N],pos[N],tmp[N];
16 priority_queue<int>son[N];
17 struct edge{int v,nxt;}e[N<<1];
18 int hd[N],cnt;
19 inline void add(int u,int v){e[++cnt]=(edge){v,hd[u]}, hd[u]=cnt;}
20 void dfs(int u,int fa){
21     pos[u]=u;
22     rep_e(i,u) if(e[i].v!=fa){
23         dfs(e[i].v,u);
24         if(i==hd[u]) pos[u]=pos[e[i].v];
25         else{
26             if(son[pos[u]].size()<son[pos[e[i].v]].size()) swap(pos[u],pos[e[i].v]);
27             int x=0;
28             while(!son[pos[e[i].v]].empty()){
29                 tmp[++x]=max(son[pos[u]].top(),son[pos[e[i].v]].top());
30                 son[pos[u]].pop(), son[pos[e[i].v]].pop();
31             }
32             while(x) son[pos[u]].push(tmp[x--]);
33         }
34     }
35     son[pos[u]].push(a[u]);
36 }
37 int main(){
38     n=read();
39     int f;
40     rep(i,1,n) a[i]=read();
41     rep(i,2,n) f=read(), add(f,i), add(i,f);
42     dfs(1,0);
43     ll ans=0;
44     while(!son[pos[1]].empty()) ans+=son[pos[1]].top(), son[pos[1]].pop();
45     cout<<ans<<endl;
46     return 0;
47 }
View Code

顺便说个事情,开 $c++11$ 的话,std::swappriority queue::swap 的复杂度都是 $O(1)$ 的(不开 $c++11$ 交换两个数组或堆之类的东西就是 $O(n)$ 的了)。详见zsy的博客

d2t3 希望

BJOI

d1t1 奥术神杖

链接

d1t2 勘破神机

原文地址:https://www.cnblogs.com/scx2015noip-as-php/p/xoi2019.html