蒟蒻快活不了了,必须找点题摩擦了
记录表
省份 | 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 */
题外话:
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 }
顺便说个事情,开 $c++11$ 的话,std::swap 和 priority queue::swap 的复杂度都是 $O(1)$ 的(不开 $c++11$ 交换两个数组或堆之类的东西就是 $O(n)$ 的了)。详见zsy的博客。