11.28 考试总结
100+50+65+45=260,rk3。
T1 【模板】分治FFT
题目名称很吓人,但是主要要找到关键性质。
考虑 \(c(a+b)+ab=b(a+c)+ac=a(b+c)+bc=ab+ac+bc\),相当于说合并方式对答案贡献无关。每种合并方式对答案贡献很好求,合并方式数量可以理解为每次从剩下的堆中挑两个,然后让堆的数量减一,即 \(\sum\limits_{i=1}^{n-1}\frac{i(i+1)}{2}\)。
时间复杂度 \(O(n)\)。
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int p=998244353;
int n,sum,ans;
signed main(){
freopen("fft.in","r",stdin);
freopen("fft.out","w",stdout);
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
cin>>n;
for(int i=1;i<=n;i++){
int a;cin>>a;
ans=(ans+a*sum)%p;
sum=(sum+a)%p;
}for(int i=1;i<n;i++)
ans=i*(i+1)/2%p*ans%p;
cout<<ans;
return 0;
}
T2 【模版】最近公共祖先
注意力惊人的注意到边数为 \(k\) 的连通块对答案的贡献为 \(\lfloor\frac{k}{2}\rfloor\),考虑构造:
-
建立一棵生成树。
-
将在同一个点的非树边能配对的就配对删掉。
-
找到深度最深的点 \(x\),若有多个,选择还有一个非树边的点。假如 \(x=rt\),\(return\) 掉。
-
假如有非树边 \((x,y)\) 的话,输出 \((fa_x,x,y)\)。
-
假如父亲有其他儿子,与其他儿子配对删除。
-
假如父亲有非树边 \((fa_x,y)\) 的话,输出 \((y,fa_x,x)\)。
-
假如父亲是根节点,\(return\) 掉,否则输出 \((fa_{fa_x},fa_x,x)\),删掉 \(fa_x\)。
正确性显然,用 \(set\) 维护,时间复杂度 \(O(n\log n)\)。
#include<bits/stdc++.h>
using namespace std;
const int N=3e5+5;
int n,m,fa[N],sz[N];
int ft[N],dep[N],ans;
set<int>g[N];stack<int>tr[N];
struct node{int x,d,sz;};
bool operator<(node x,node y){
if(x.d!=y.d) return x.d>y.d;
if(x.sz!=y.sz) return x.sz>y.sz;
return x.x<y.x;
}set<node>st;
void init(){
for(int i=1;i<=n;i++)
fa[i]=i,sz[i]=0;
}int find(int x){
if(x==fa[x]) return x;
return fa[x]=find(fa[x]);
}int unite(int x,int y){
x=find(x),y=find(y);
if(x==y) return sz[x]++,0;
sz[x]+=sz[y]+1,fa[y]=x;
return 1;
}void print(int x,int y,int z){
cout<<x<<" "<<y<<" "<<z<<"\n";
}void dfs(int x,int f){
dep[x]=dep[f]+1,ft[x]=f;
st.insert({x,dep[x],tr[x].size()});
for(auto y:g[x])
if(y!=f) dfs(y,x);
if(f) g[x].erase(f);
}void gets(int nw){
while(st.size()){
int x=(*st.begin()).x;
st.erase({x,dep[x],tr[x].size()});
if(x==nw) return;
g[ft[x]].erase(x);
if(tr[x].size()){
print(ft[x],x,tr[x].top());
continue;
}if(g[ft[x]].size()){
int sn=(*g[ft[x]].begin());
g[ft[x]].erase(sn);
st.erase({sn,dep[sn]});
print(x,ft[x],sn);continue;
}if(tr[ft[x]].size()){
print(x,ft[x],tr[ft[x]].top());
tr[ft[x]].pop();
st.erase({ft[x],dep[ft[x]],1});
st.insert({ft[x],dep[ft[x]],0});
continue;
}if(ft[x]==nw) return;
print(x,ft[x],ft[ft[x]]);
g[ft[ft[x]]].erase(ft[x]);
st.erase({ft[x],dep[ft[x]]});
}
}int main(){
freopen("lca.in","r",stdin);
freopen("lca.out","w",stdout);
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
cin>>n>>m,init();
for(int i=1,u,v;i<=m;i++){
cin>>u>>v;
if(!unite(u,v)){
tr[u].push(v);
continue;
}g[u].insert(v);
g[v].insert(u);
}for(int i=1;i<=n;i++)
if(find(i)==i) ans+=sz[i]/2;
cout<<ans<<"\n";
for(int i=1;i<=n;i++)
while(tr[i].size()>1){
int x=tr[i].top();
tr[i].pop();
int y=tr[i].top();
tr[i].pop();
print(x,i,y);
}
for(int i=1;i<=n;i++)
if(!dep[i]) dfs(i,0),gets(i),st.clear();
return 0;
}
T3 【模版】普通平衡树
好一道离谱的线段树!
引理:一个长为 \(len\) 序列 \(b\) 的最大锯齿子序列长度为 \(\min(len,\sum\limits_{i=2}^{len-1}[(b_i-b_{i-1})(b_{i+1}-b_i)<0])\)。
证明:不妨设 \(b_i>b_{i-1},b_{i+1}\):
假如 \(b_{i-1}\) 是最大锯齿子序列中的一项,正确性显然;否则最长锯齿子序列最后一项一定满足 \(b_j<b_{i-1}<b_i\),那么 \(b_i\) 就可以加入最长锯齿子序列中。
\(Q.E.D.\)
那么我们用懒标记维护在这一段增加的序列,下方懒标记时,将儿子的懒标记续上自己的懒标记即可。懒标记维护不难。
时间复杂度 \(O(n+q\log n)\)。
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=3e5+5;
int ch(int x,int y,int z){
return (y-x)*(z-y)<0;
}struct tag{
int fs1,fs2;
int ed1,ed2;
int len,ans;
}tg[N*4];int n,q;
tag operator+(tag x,tag y){
if(!x.len) return y;
if(!y.len) return x;
if(x.len>1)
x.ans+=ch(x.ed2,x.ed1,y.fs1);
if(y.len>1)
y.ans+=ch(x.ed1,y.fs1,y.fs2);
if(y.len==1)
x.ed2=x.ed1,x.ed1=y.fs1;
else x.ed2=y.ed2,x.ed1=y.ed1;
if(x.len==1) x.fs2=y.fs1;
x.ans+=y.ans,x.len+=y.len;
return x;
}void operator+=(tag &x,tag y){x=x+y;}
void push_down(int x){
tg[x*2]+=tg[x];
tg[x*2+1]+=tg[x];
tg[x]={0,0,0,0,0,0};
}void chg(int x,int l,int r,int L,int R,tag k){
if(L<=l&&r<=R) return tg[x]+=k,void();
push_down(x);int mid=(l+r)/2;
if(L<=mid) chg(x*2,l,mid,L,R,k);
if(R>mid) chg(x*2+1,mid+1,r,L,R,k);
}int ans(int x,int l,int r,int v){
if(l==r) return tg[x].ans+min(tg[x].len,2ll);
push_down(x);int mid=(l+r)/2;
if(v<=mid) return ans(x*2,l,mid,v);
return ans(x*2+1,mid+1,r,v);
}signed main(){
freopen("odt.in","r",stdin);
freopen("odt.out","w",stdout);
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
cin>>n>>q;
while(q--){
int opt;cin>>opt;
if(opt==2){
int t;cin>>t;
cout<<ans(1,1,n,t)<<"\n";
continue;
}int l,r,x;cin>>l>>r>>x;
chg(1,1,n,l,r,{x,0,x,0,1,0});
}return 0;
}
T4 【模版】平面最近点对
假如将每头奶牛分为选或不选两种情况,再佐以二分,那么显然可以 \(2-sat\) 解决。
但是最劣情况边数为 \(n^2k\),那就不用想加上 \(\log\) 之后的死法了。
考虑优化。连边分为两部分:点对内两点连边、不能在边长为 \(x\) 时同时出现的两点。第一类无需优化,主要考虑第二类。
发现实际上连边范围是按维度排序后的前后缀,于是建立 \(4n\) 个点,表示前后缀,向他们连边,就可以保证 \(nk\) 的边数。
时间复杂度 \(O(nk\log V\log n)\),预处理出排序结果可以做到 \(O(nk\log V)\)。
#include<bits/stdc++.h>
using namespace std;
const int N=16005,M=8e5+5;
int n,k,a[N][25],nw,cw[N];
int cnt,id,dfn[M],low[M];
int tp,st[M],v[M],idx[M];
vector<int>g[M];
int cmp(int x,int y){
return a[x][nw]<a[y][nw];
}void tarjan(int x){
st[++tp]=x,v[x]=1;
dfn[x]=low[x]=++id;
for(auto y:g[x]){
if(!dfn[y]) tarjan(y),low[x]=min(low[x],low[y]);
else if(v[y]) low[x]=min(low[x],dfn[y]);
}if(dfn[x]!=low[x]) return;
while(st[tp+1]!=x){
idx[st[tp]]=cnt;
v[st[tp--]]=0;
}++cnt;
}int check(int x){
for(int i=1;i<=(k+1)*4*n;i++)
g[i].clear(),dfn[i]=low[i]=idx[i]=tp=cnt=0;
for(int i=1;i<=n;i++){
g[i+3*n].push_back(i);
g[i+2*n].push_back(i+n);
}for(int i=1;i<=k;i++){
nw=i;int ad=i*4*n;
sort(cw+1,cw+n*2+1,cmp);
for(int j=1;j<=2*n;j++){
if(j>1) g[ad+j].push_back(ad+j-1);
g[ad+j].push_back(cw[j]+2*n);
}for(int j=1,l=0;j<=2*n;j++){
while(a[cw[l+1]][i]+x<a[cw[j]][i]) l++;
if(l) g[cw[j]].push_back(ad+l);
}for(int j=2*n;j;j--){
if(j<2*n) g[ad+j+2*n].push_back(ad+j+2*n+1);
g[ad+j+2*n].push_back(cw[j]+2*n);
}for(int j=2*n,r=j+1;j;j--){
while(a[cw[r-1]][i]-x>a[cw[j]][i]) r--;
if(r<=2*n) g[cw[j]].push_back(ad+2*n+r);
}
}for(int i=1;i<=(k*2+4)*n;i++)
if(!dfn[i]) tarjan(i);
for(int i=1;i<=2*n;i++)
if(idx[i]==idx[i+2*n]) return 0;
return 1;
}int main(){
freopen("pair.in","r",stdin);
freopen("pair.out","w",stdout);
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
cin>>n>>k;
for(int i=1;i<=n;i++)
for(int id=0;id<2;id++)
for(int j=1;j<=k;j++)
cin>>a[i+id*n][j];
for(int i=1;i<=2*n;i++) cw[i]=i;
int l=0,r=1e9,ans;
while(l<=r){
int mid=(l+r)/2;
if(check(mid))
ans=mid,r=mid-1;
else l=mid+1;
}cout<<ans;
return 0;
}