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\),考虑构造:

  1. 建立一棵生成树。

  2. 将在同一个点的非树边能配对的就配对删掉。

  3. 找到深度最深的点 \(x\),若有多个,选择还有一个非树边的点。假如 \(x=rt\)\(return\) 掉。

  4. 假如有非树边 \((x,y)\) 的话,输出 \((fa_x,x,y)\)

  5. 假如父亲有其他儿子,与其他儿子配对删除。

  6. 假如父亲有非树边 \((fa_x,y)\) 的话,输出 \((y,fa_x,x)\)

  7. 假如父亲是根节点,\(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;
}
posted @ 2024-11-28 20:14  长安一片月_22  阅读(3)  评论(0编辑  收藏  举报