<span>省选模拟8 题解</span>
A. 序列
发现较难维护的是后一个信息。
所以考虑直接分块维护。
为每个块开一个vector,表示这个块中所有元素对vector中的所有元素依次取过max。
因为取max的性质,这个vector中的元素只需要保留单调递增的部分。
在查询的时候和暴力重构一些块的时候,可以直接在vector中二分确定某个元素更新了多少次。
因为卡内存,所以每隔5000次询问暴力重构所有块一次。
询问时手贱把upperbound打成lowerbound,哭了。
1 #include<bits/stdc++.h> 2 #define ll long long 3 using namespace std; 4 const int N=1e5+7; 5 int n,m,sq; 6 ll a[N],add[700]; 7 int sz[N],bl[N],tim[700]; 8 vector<ll> ve[700]; 9 inline void rebuild(int k){ 10 if(!tim[k]&&ve[k].empty()) return ; 11 int l=(k-1)*sq+1,r=min(n,k*sq); 12 for(int i=l;i<=r;++i){ 13 sz[i]+=ve[k].end()-upper_bound(ve[k].begin(),ve[k].end(),a[i])+tim[k]; 14 if(!ve[k].empty()) a[i]=max(a[i],ve[k].back())+add[k]; 15 else a[i]+=add[k]; 16 } 17 tim[k]=0; add[k]=0; ve[k].clear(); 18 } 19 inline void modify(int l,int r,int c){ 20 if(!c) return ; 21 rebuild(bl[l]); rebuild(bl[r]); 22 for(int i=l,lim=min(bl[l]*sq,r);i<=lim;++i) ++sz[i],a[i]+=c; 23 if(bl[l]!=bl[r]) for(int i=(bl[r]-1)*sq+1;i<=r;++i) ++sz[i],a[i]+=c; 24 for(int i=bl[l]+1;i<bl[r];++i) add[i]+=c,++tim[i]; 25 } 26 inline void insert(int l,int r,int c){ 27 rebuild(bl[l]); rebuild(bl[r]); 28 for(int i=l,lim=min(bl[l]*sq,r);i<=lim;++i) if(c>a[i]) a[i]=c,++sz[i]; 29 if(bl[l]!=bl[r]) for(int i=(bl[r]-1)*sq+1;i<=r;++i) if(c>a[i]) a[i]=c,++sz[i]; 30 for(int i=bl[l]+1;i<bl[r];++i) if(ve[i].empty()||c-add[i]>ve[i].back()) ve[i].push_back(c-add[i]); 31 } 32 inline void query(int x){ 33 int k=bl[x]; 34 if(!ve[k].empty()) printf("%lld ",max(a[x],ve[k].back())+add[k]); 35 else printf("%lld ",a[x]+add[k]); 36 printf("%d\n",sz[x]+ve[k].end()-upper_bound(ve[k].begin(),ve[k].end(),a[x])+tim[k]); 37 } 38 inline int read(register int x=0,register char ch=getchar(),register int f=0){ 39 for(;!isdigit(ch);ch=getchar()) f=ch=='-'; 40 for(; isdigit(ch);ch=getchar()) x=(x<<1)+(x<<3)+(ch^48); 41 return f?-x:x; 42 } 43 int main(){ 44 n=read(); sq=sqrt(n)*0.5+1; 45 for(int i=1;i<=n;++i) a[i]=read(),bl[i]=(i-1)/sq+1; 46 m=read(); 47 for(int i=1,l,r,c;i<=m;++i){ 48 char ch=getchar(); while(!isalpha(ch)) ch=getchar(); 49 if(ch=='A') l=read(),r=read(),c=read(),modify(l,r,c); 50 else if(ch=='M') l=read(),r=read(),c=read(),insert(l,r,c); 51 else query(read()); 52 if(!(i&4095)) for(int j=1;j<=bl[n];++j) rebuild(j); 53 } 54 return 0; 55 }
复杂度瓶颈在于修改时的暴力重构,复杂度为$O(n \sqrt{nlogn})$。
发现在重构时可以通过单调指针,所以维护每个块中元素的大小顺序,对于修改操作可以进行二路归并,总复杂度$O(n\sqrt n)$。
正解做法是离线后进行扫描线操作。
将每个操作视为在左端点加入,右端点删除。
容易发现可以把相邻的加法操作合在一起,于是操作序列转化为$A_1\ M_1\ A_2\ M_2\ A_3\ M_3$的形式。
考虑已经维护了原来的合法的取max操作,设$s_i$表示$A_i$的前缀和。
取max操作能够成立仅当$M_{i-1}+s_i-s_{i-1}<M_i$。
移项可得$M_{i-1}-s_{i-1}<M_i-s_{i}$,所以只要维护$M_i-s_i$形成的单调递增的结构就可以了。
这个东西可以用线段树维护单调栈的思路维护。
B. 旅行计划
部分分很简单,但是正解很难想到。
正解大概是取$d$为$k$和联通块内所有边权的gcd。
答案显然只能为$d$的倍数,不妨将所有的边权和$k$都除掉$d$。
存在一种情况,使得联通块内所有边权在模$k$意义下总和为$1$。
然而只可以简单地构造一条回路,使得经过联通块内任意边任意偶数次(题解给出的构造方法是在DFS树上走,遇到非树边可以来回转)。
所以构造出一条在模$k$意义下贡献为$2$的回路$C$是可行的。
如果$k$为奇数,那么一定可以通过不断走$u\rightarrow v\rightarrow u$这条路径,使得最终答案为$0$。
只需要考虑$k$为偶数。
现在考虑$u\ v$之间是否存在一条长度为偶数(即在模$2$意义下为$0$)的路径。
因为已经有了回路$C$,如果存在长度为偶数的路径,答案一定为$0$。
如果不存在,那么答案只能为$1$。
所以对于每个联通块分别奇偶染色(当然这里的染色标准并不是相邻异色),判是否为二分图就可以了。
然而还有一个问题是,每次给出的$k$是不同的,所以没有办法预处理。
然而注意到这里的$k$在除过$d$之后仍为偶数,也就是说$k$中质因子$2$的个数要比原联通块的$gcd$中$2$的个数要多。
所以原联通块中边权在除两个不同的$gcd$之后的奇偶性是相同的,所以直接预处理是可行的。
C. Hack
可以发现直接用最小割就可以保证每条路径一定被割,但是却不能保证只割一次。
手玩发现割不止一次的情况,等价于源点汇点分别形成的$S$/$T$集合中,满足$T$集合内存在边回到$S$集合,然后就死掉了。
题解给出的做法是,对每条边建立边权为$inf$的反向边,这样上述的情况会存在增广路,所以上述情况并不是一个合法的割。
所以直接跑最小割就可以了。