【LOJ3285】「USACO 2020 US Open Platinum」Circus(乱搞)(并查集)

本文深入探讨了算法竞赛中的一种特定问题解决策略,重点讲解如何通过计算等价类大小来优化状态转移,从而达到高效解决问题的目的。文章详细分析了在一条链上无法进行交换的情况,并提出了解决方案,即跨过一系列度数为2的节点,实现左右子树间的有效交换。此外,还介绍了如何通过枚举和合并链来计算可达连通块的点数,以及如何将复杂度控制在O(n)范围内。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

传送门


题解:

大体思路大家区别不大,具体做法千奇百怪。。。

点击此处膜拜大佬:here

我们考虑计算等价类的大小,即一个状态可以转移到多少个合法的状态。

首先容易注意到一条链上啥都交换不了,也就是说我们需要跨过一堆度数为 2 2 2 的点。

假设链两端的子树大小为 A , B A,B A,B ,当前考虑为 K K K,不难发现左右能进行交换当且仅当 K < A + B − 2 K<A+B-2 K<A+B2,去掉链上两端的点。

于是我们把原树中所有链拿出来,记录下两端的点和 s z sz sz,从大到小枚举 k k k 同时把可以进行交换的链拿出来合并。

我们需要计算能够到达每个连通块的有多少个点,转为计算不能到达的有多少点即可,记录一下还没有连上的边的数量。

最后,枚举连通块可以暴力,因为连通块数量=未合并的链数量+1,一条链在 K < A + B − 2 K<A+B-2 K<A+B2 的时候会被加入,那么它对复杂度的贡献也就是链长,总的链长就是 O ( n ) O(n) O(n) 的。


代码:

#include<bits/stdc++.h>
#define ll long long
#define re register
#define cs const

namespace IO{

inline char gc(){
	static cs int Rlen=1<<22|1;static char buf[Rlen],*p1,*p2;
	return (p1==p2)&&(p2=(p1=buf)+fread(buf,1,Rlen,stdin),p1==p2)?EOF:*p1++;
}template<typename T>T get_integer(){
	char c;bool f=false;while(!isdigit(c=gc()))f=c=='-';T x=c^48;
	while(isdigit(c=gc()))x=((x+(x<<2))<<1)+(c^48);return f?-x:x;
}inline int gi(){return get_integer<int>();}

char obuf[(int)(3e7+7)],*oh=obuf,ch[23];
template<typename T>void print(T a,char c){
	if(a<0)a=-a,*oh++='-';int tl=0;
	do ch[++tl]=a%10; while(a/=10);
	while(tl)*oh++=ch[tl--]^48;*oh++=c;
}struct obuf_flusher{~obuf_flusher(){fwrite(obuf,1,oh-obuf,stdout);}}Flusher;

}using namespace IO;

using std::cerr;
using std::cout;

cs int mod=1e9+7;
inline int add(int a,int b){return a+b>=mod?a+b-mod:a+b;}
inline int dec(int a,int b){return a-b<0?a-b+mod:a-b;}
inline int mul(int a,int b){ll r=(ll)a*b;return r>=mod?r%mod:r;}
inline void Inc(int &a,int b){a+=b-mod;a+=a>>31&mod;}
inline void Dec(int &a,int b){a-=b;a+=a>>31&mod;}
inline void Mul(int &a,int b){a=mul(a,b);}
inline int po(int a,int b){int r=1;for(;b;b>>=1,Mul(a,a))if(b&1)Mul(r,a);return r;}
inline void ex_gcd(int a,int b,int &x,int &y){
	if(!b){x=1,y=0;return;}ex_gcd(b,a%b,y,x);y-=a/b*x;
}inline int Inv(int a){static int x,y;ex_gcd(mod,a,y,x);return x+(x>>31&mod);}

cs int N=1e5+7;

struct atom{
	int u,v,su,sv,sm;
	atom(int _u,int _v,int _su,int _sv)
		:u(_u),v(_v),su(_su),sv(_sv),sm(su+sv-2){}
};

int n,rt,sz[N];
std::vector<int> G[N];
int fac[N],ifc[N];
std::vector<atom> path;

void dfs(int u,int p){
	for(int v:G[u])
		if(v!=p)dfs(v,u),sz[u]+=sz[v];
	++sz[u];
}

void get_path(int u,int p,int up,int siz){
	if(G[u].size()!=2){
		if(up)
			path.push_back((atom){u,up,sz[u],siz});
		for(int v:G[u])if(v!=p)
			get_path(v,u,u,n-sz[v]);
	}else {
		for(int v:G[u])if(v!=p)
			get_path(v,u,up,siz);
	}
} 

int pr[N],nx[N],fa[N];

void del(int u){
	pr[nx[u]]=pr[u];
	nx[pr[u]]=nx[u];
}

int f[N],g[N],ans[N];

int gf(int u){
	while(u!=fa[u])
		u=fa[u]=fa[fa[u]];
	return u;
}

void merge(cs atom &t){
	int u=gf(t.u),v=gf(t.v);
	f[u]+=f[v]-2,g[u]+=g[v]-t.sm,fa[v]=u;
}

void Main(){
	n=gi();
	for(int re i=1;i<n;++i){
		int u=gi(),v=gi();
		G[u].push_back(v);
		G[v].push_back(u);
	}
	for(int re i=fac[0]=1;i<=n;++i)
		fac[i]=mul(fac[i-1],i);
	ifc[n]=Inv(fac[n]);
	for(int re i=n;i;--i)
		ifc[i-1]=mul(ifc[i],i);
	for(rt=1;G[rt].size()==2;++rt);
	dfs(rt,0);get_path(rt,0,0,0);
	for(int re i=0;i<=n+1;++i)
		pr[i]=i-1,nx[i]=i+1;
	for(int re i=1;i<=n;++i)
		if(G[i].size()==2u)del(i);
		else fa[i]=i;
	for(auto &t:path){
		++f[t.u],++f[t.v];
		g[t.u]+=t.su-1;
		g[t.v]+=t.sv-1;
	}
	std::sort(path.begin(),path.end(),
		[](cs atom &a,cs atom &b){return a.sm>b.sm;});
	auto nw=path.begin();
	for(int re k=n-1;k;--k){
		while(nw!=path.end()&&nw->sm>k)
			merge(*nw++);
		ans[k]=fac[k];
		for(int re i=nx[0];i<=n;i=nx[i]){
			if(gf(i)!=i)del(i);
			else {
				int t=k-k*f[i]+g[i];
				Mul(ans[k],ifc[t]);
			}
		}
	}for(int re i=1;i<n;++i)
		print(ans[i],'\n');
	print(fac[n],'\n');
}

inline void file(){
#ifdef zxyoi
	freopen("circus.in","r",stdin);
#endif
}signed main(){file();Main();return 0;}
<think>我们正在查询与LOJ6279相关的编程题目或解决方案。LOJ(LibreOJ)是一个在线的评测系统,主要收录算法竞赛题目。根据题号6279,我们需要确定该题目的具体内容。由于我无法直接访问网络,我将基于已知信息进行推理。在算法竞赛中,LOJ的题目编号通常与特定的题目对应。题号6279可能对应一个特定的问题。根据常见的题目类型,我们可以推测它可能涉及数据结构或算法。然而,为了准确回答,我们可以尝试回忆或推断常见的LOJ题目。例如,LOJ6000系列题目多为数据结构题,如线段树、树状数组等。实际上,通过搜索记忆,我回想起LOJ6279是“数列分块入门3”(BlockArray3)题目。这是“分块”系列题目中的一题。题目描述:给出一个长为n的数列,以及n个操作,操作涉及区间加法,询问区间内小于某个值x的前驱(即最大的小于x的数)。因此,用户需要的是关于LOJ6279题目的描述和解决方案。解决方案:由于题目要求分块处理,我们将数列分成若干块,每块大小为sqrt(n)。对于每个块,我们维护一个有序的数组(用于二分查找)以及一个加法标记(记录整个块被加了多少)。操作:1.区间加法:对于整块,直接更新加法标记;对于不完整的块,暴力更新元素值,然后重新排序该块(或者重新构建有序数组)。2.查询区间内小于x的前驱:遍历区间内的所有块。对于整块,在块的有序数组中二分查找小于(x-加法标记)的最大值;对于不完整的块,暴力遍历每个元素,判断是否小于x(注意加上加法标记)并更新最大值。代码实现(C++)示例:```cpp#include<iostream>#include<vector>#include<cmath>#include<algorithm>#include<climits>usingnamespacestd;constintMAXN=100010;constintBLOCK_SIZE=320;//约sqrt(MAXN)intn;vector<int>a;vector<int>block_add;//每块的加法标记vector<vector<int>>sorted_blocks;//每块的有序数组voidinit(){intblock_num=(n+BLOCK_SIZE-1)/BLOCK_SIZE;block_add.assign(block_num,0);sorted_blocks.resize(block_num);for(inti=0;i<n;i++){sorted_blocks[i/BLOCK_SIZE].push_back(a[i]);}for(inti=0;i<block_num;i++){sort(sorted_blocks[i].begin(),sorted_blocks[i].end());}}voidupdate_block(intblock_id){sorted_blocks[block_id].clear();intstart=block_id*BLOCK_SIZE;intend=min((block_id+1)*BLOCK_SIZE,n);for(inti=start;i<end;i++){sorted_blocks[block_id].push_back(a[i]);}sort(sorted_blocks[block_id].begin(),sorted_blocks[block_id].end());}voidadd(intl,intr,intc){intblock_l=l/BLOCK_SIZE,block_r=r/BLOCK_SIZE;if(block_l==block_r){for(inti=l;i<=r;i++){a[i]+=c;}update_block(block_l);}else{for(inti=l;i<(block_l+1)*BLOCK_SIZE;i++){a[i]+=c;}update_block(block_l);for(inti=block_l+1;i<block_r;i++){block_add[i]+=c;}for(inti=block_r*BLOCK_SIZE;i<=r;i++){a[i]+=c;}update_block(block_r);}}intquery(intl,intr,intc){intblock_l=l/BLOCK_SIZE,block_r=r/BLOCK_SIZE;intans=-1;if(block_l==block_r){for(inti=l;i<=r;i++){intval=a[i]+block_add[block_l];if(val<c){if(val>ans)ans=val;}}}else{for(inti=l;i<(block_l+1)*BLOCK_SIZE;i++){intval=a[i]+block_add[block_l];if(val<c){if(val>ans)ans=val;}}for(inti=block_l+1;i<block_r;i++){//在块i的有序数组中二分查找小于c-block_add[i]的最大值inttarget=c-block_add[i];autoit=lower_bound(sorted_blocks[i].begin(),sorted_blocks[i].end(),target);if(it!=sorted_blocks[i].begin()){it--;intval=*it+block_add[i];if(val>ans)ans=val;}}for(inti=block_r*BLOCK_SIZE;i<=r;i++){intval=a[i]+block_add[block_r];if(val<c){if(val>ans)ans=val;}}}returnans;}intmain(){cin>>n;a.resize(n);for(inti=0;i<n;i++){cin>>a[i];}init();for(inti=0;i<n;i++){intop,l,r,c;cin>>op>>l>>r>>c;l--;r--;//如果题目输入是1-indexed,则转换为0-indexedif(op==0){add(l,r,c);}else{cout<<query(l,r,c)<<endl;}}return0;}```注意:上述代码是示例代码,可能需要根据题目具体输入要求进行调整。§§相关问题§§1.LOJ6279题目的具体描述是什么?2.分块算法的时间复杂度是多少?3.除了分块,还有哪些方法可以解决LOJ6279?4.如何优化分块算法的常数?5.分块算法适用于哪些场景?</think>### LOJ 6279 题目分析与解决方案 **题目描述**:LOJ 6279 是 LibreOJ 上的 **"数列分块入门 3"**,属于分块算法的经典练习题。核心要求如下: - **操作 1**:区间 $[l, r]$ 内每个数加上值 $c$ - **操作 2**:查询区间 $[l, r]$ 内小于 $c$ 的**最大元素值**(即前驱),不存在时返回 $-1$ #### 分块算法设计 分块的核心思想是将数列划分为 $\sqrt{n}$ 大小的块,平衡暴力操作与批量处理。 ##### 关键数据结构 1. **块内有序数组**: - 每块维护一个排序后的数组,用于二分查找前驱。 - 示例:块 $B_i$ 的排序数组为 $S_i$。 2. **加法懒标记**: - 记录整块被加的总值 $add_i$(不影响块内有序性)。 ##### 操作实现 1. **区间加法($[l, r] + c$)**: - **整块**:直接更新懒标记 $add_i \gets add_i + c$,时间复杂度 $O(1)$。 - **碎块**:暴力修改元素值,并**重构有序数组**(排序),时间复杂度 $O(\sqrt{n} \log \sqrt{n})$。 2. **查询前驱($[l, r]$ 内 $< c$ 的最大值)**: - **整块**:在有序数组 $S_i$ 中二分查找 $c - add_i$ 的前驱,时间复杂度 $O(\log \sqrt{n})$。 - **碎块**:暴力遍历元素,检查 $a_j + add_i < c$ 并更新最大值,时间复杂度 $O(\sqrt{n})$。 #### 代码实现(C++) ```cpp #include <iostream> #include <vector> #include <cmath> #include <algorithm> #include <climits> using namespace std; const int MAXN = 100010; int n, block_size; vector<int> a; vector<int> add_tag; // 块的加法标记 vector<vector<int>> blocks; // 每块的有序数组 void init() { block_size = sqrt(n); int block_num = (n + block_size - 1) / block_size; add_tag.assign(block_num, 0); blocks.resize(block_num); for (int i = 0; i < n; i++) blocks[i / block_size].push_back(a[i]); for (int i = 0; i < block_num; i++) sort(blocks[i].begin(), blocks[i].end()); } void rebuild_block(int block_id) { blocks[block_id].clear(); int start = block_id * block_size; int end = min(start + block_size, n); for (int i = start; i < end; i++) blocks[block_id].push_back(a[i]); sort(blocks[block_id].begin(), blocks[block_id].end()); } void range_add(int l, int r, int c) { int block_l = l / block_size, block_r = r / block_size; if (block_l == block_r) { // 同一块内 for (int i = l; i <= r; i++) a[i] += c; rebuild_block(block_l); } else { // 左侧碎块 for (int i = l; i < (block_l + 1) * block_size; i++) a[i] += c; rebuild_block(block_l); // 中间整块 for (int i = block_l + 1; i < block_r; i++) add_tag[i] += c; // 右侧碎块 for (int i = block_r * block_size; i <= r; i++) a[i] += c; rebuild_block(block_r); } } int query_predecessor(int l, int r, int c) { int ans = -1; int block_l = l / block_size, block_r = r / block_size; // 左侧碎块暴力查询 for (int i = l; i < min(r + 1, (block_l + 1) * block_size); i++) { int val = a[i] + add_tag[block_l]; if (val < c && val > ans) ans = val; } // 中间整块二分查找 for (int i = block_l + 1; i < block_r; i++) { int target = c - add_tag[i]; auto it = lower_bound(blocks[i].begin(), blocks[i].end(), target); if (it != blocks[i].begin()) { it--; int val = *it + add_tag[i]; if (val < c && val > ans) ans = val; } } // 右侧碎块暴力查询 if (block_l != block_r) { for (int i = block_r * block_size; i <= r; i++) { int val = a[i] + add_tag[block_r]; if (val < c && val > ans) ans = val; } } return ans; } int main() { cin >> n; a.resize(n); for (int i = 0; i < n; i++) cin >> a[i]; init(); for (int i = 0; i < n; i++) { int op, l, r, c; cin >> op >> l >> r >> c; l--; r--; // 转为0-indexed if (op == 0) range_add(l, r, c); else cout << query_predecessor(l, r, c) << endl; } return 0; } ``` #### 算法分析 - **时间复杂度**: - 单次修改/查询:$O(\sqrt{n} \log \sqrt{n})$(碎块排序主导)。 - 总操作 $m$ 次:$O(m \sqrt{n} \log n)$。 - **空间复杂度**:$O(n)$。 #### 优化技巧 1. **减少排序次数**: - 碎块修改时只重构受影响块的有序数组。 2. **块大小调整**: - 实测调整块大小为 $n^{0.6}$ 可能更快(需测试)。 #### 应用场景 分块算法适用于**强制在线**的区间问题(如 LOJ 的数列分块系列题),在 $O(\sqrt{n})$ 复杂度下平衡修改与查询[^1]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值