首页 > 其他分享 >Light Bulbs (Hard Version) 题解

Light Bulbs (Hard Version) 题解

时间:2024-09-27 14:46:30浏览次数:8  
标签:int 题解 Hard 合并 集合 端点 Light 区间 激活

提供一个非常另类的解法,没有异或哈希,没有建图,没有缩点和强连通分量,而是使用了并查集和线段树的算法。

由于每个颜色恰好有两种,我们考虑把两个颜色的位置 \(i,j\) 变成一段区间 \([i,j]\)(\(i<j\)),然后每个颜色就能用一段区间 \([l,r]\) 表示。

根据题意,如果我们激活了一个区间 \([l,r]\),那么对于所有 \(l'\in (l,r)\wedge r'>r\) 的,或者 \(r'\in (l,r)\wedge l'<l\) 的区间 \([l',r']\),它们都会被自动激活。我们的目标就是激活最少的区间去覆盖 \([1,2n]\) 这个大的区间。

考虑使用并查集,把相交但不包含的任意两个区间合并到一个集合里面,即若 \([l_i,r_i]∩[l_j,r_j]\not= \varnothing\) 且 \([l_i,r_i],[l_j,r_j]\) 之间不存在包含关系,则把下标 \(i,j\) 合并。合并了所有 \((i,j)\) 之后,我们发现对于一个集合 \(S\),当我们激活了 \(S\) 中的任意一个区间 \([l,r]\),那么 \(S\) 的所有区间也必然都会被一起激活。也就是说,设 \(S\) 中所有区间的并区间为 \([L,R]\),我们覆盖 \([L,R]\) 就只需要随意激活 \(S\) 的任意一个区间。

由于题目保证了所有区间的并一定是 \([1,2n]\),要保证激活区间最少,我们只需要在不同的集合中各选择一个区间。但是有一种特殊情况,对于两个集合的区间并 \([L,R],[L',R']\),如果有 \(L<L'<R'<R\),那么我们就只需要激活 \([L,R]\),因为这样就已经覆盖了 \([L',R']\) 这个子区间,我们就没有必要去专门激活 \([L',R']\)。

而要处理这个特殊情况也很简单,只需要对每个集合求出它的区间并 \([L_i,R_i]\),然后判断哪些区间是被其余区间包含了的,如果被包含了,那就可以不用被激活。最后再求出所有必须被激活的区间的个数就行了。

不过题目还要询问方案数,这个问题也很简单,对于一个集合 \(S\),它含有 \(|S|\) 个区间,每个区间的两个端点只要被激活一个,那么整个区间就会被激活,因此我们对于每一个需要被激活的集合,求出它们的 \(2|S|\) 之积就行了。

大致思路就是这样,接下来讨论一些实现方法。

  • 如果判断一个区间是否被另一个区间包含?首先把这些区间按照 \(l\) 从小到大排序,然后维护前缀的所有区间的 \(r\) 最大值,若 \(r_i<\max_{1\leq j<i}\{r_j\}\),则 \(i\) 就会被 \(j\) 这个区间包含。

  • 如何快速把有交集且不包含的两个区间合并起来?我们可以对于每一个区间,按照 \(l\) 从小到大排序,用线段树维护前 \(i-1\) 个区间的右端点,当我们枚举到第 \(i\) 个区间时,我们就查询 \([l_i,r_i]\) 范围内的右端点,然后把 \(i\) 与这些右端点对应的区间编号合并。但是暴力对每一个区间合并是 \(O(n^2)\) 的。

    我们可以在线段树维护的过程中,每次给 \(r_j\) 的位置赋一个值,这个值就是 \(r_j\) 这个右端点所对应的区间下标。每次要合并的时候,我们就查询一次 \([l_i,r_i]\) 中的最大值,只将 \(i\) 和这个最大值合并。统计完之后,我们再将所有区间按照 \(r\) 从大到小排序,用线段树维护区间 \(l\) 的区间下标最大值,然后每次合并 \(i\) 与 \([l_i,r_i]\) 中的最大值。这样下来就能完全地合并每一个 \((i,j)\)。正确性大家可以尝试去证明一下,这里就不赘述了。

#include<cstdio>
#include<iostream>
#include<algorithm>
using namespace std;
const int MAXN=4e5+5;
const int MOD=998244353;
int n;
int a[MAXN];
int dp[MAXN];
struct node
{
	int l,r,id;
	bool operator<(const node &f)const{ return l<f.l; }
}l[MAXN],b[MAXN];
bool cmp(node x,node y){ return x.r<y.r; }
int pre[MAXN],cnt,tot;
int fa[MAXN],siz[MAXN],L[MAXN],R[MAXN];
int find(int x)
{
	if(fa[x]==x)	return x;
	return fa[x]=find(fa[x]);
}
void merge(int x,int y)
{
	x=find(x),y=find(y);
	if(x!=y)	fa[x]=y,siz[y]+=siz[x],L[y]=min(L[y],L[x]),R[y]=max(R[y],R[x]);
}
struct Segment_Tree
{
	int maxx;
}T[MAXN<<2];
void pushup(int x){ T[x].maxx=max(T[x<<1].maxx,T[x<<1|1].maxx); }
void change_tree(int x,int l,int r,int k,int v)
{
	if(l==r)	return (void)(T[x].maxx=v);
	int mid=(l+r)/2;
	if(k<=mid)	change_tree(x<<1,l,mid,k,v);
	else	change_tree(x<<1|1,mid+1,r,k,v);
	pushup(x);
}
int query(int x,int l,int r,int L,int R)
{
	if(L<=l&&r<=R)	return T[x].maxx;
	int mid=(l+r)/2,res=0;
	if(L<=mid)	res=max(res,query(x<<1,l,mid,L,R));
	if(R>mid)	res=max(res,query(x<<1|1,mid+1,r,L,R));
	return res;
}
void solve()
{
	cnt=0,tot=0;
	for(int i=1;i<=n;i++)	pre[i]=0;
	for(int i=1;i<=(n<<1);i++)
	{
		if(pre[a[i]])	l[++cnt]=(node){pre[a[i]],i,0},l[cnt].id=cnt;
		pre[a[i]]=i;
	}
	for(int i=1;i<=cnt;i++)	fa[i]=i,siz[i]=2,L[i]=l[i].l,R[i]=l[i].r;
	sort(l+1,l+cnt+1);
	for(int i=1;i<=cnt;i++)
	{
		int now=query(1,1,n*2,l[i].l,l[i].r);
		if(now)	merge(l[i].id,now);
		change_tree(1,1,n*2,l[i].r,l[i].id);
	}
	for(int i=1;i<=cnt;i++)	change_tree(1,1,n*2,l[i].r,0);
	sort(l+1,l+cnt+1,cmp);
	for(int i=cnt;i>=1;i--)
	{
		int now=query(1,1,n*2,l[i].l,l[i].r);
		if(now)	merge(l[i].id,now);
		change_tree(1,1,n*2,l[i].l,l[i].id);
	}
	for(int i=1;i<=cnt;i++)	change_tree(1,1,n*2,l[i].l,0);
	int num=0,res=1;
	for(int i=1;i<=cnt;i++)
	{
		if(fa[i]==i)	b[++tot]=(node){L[i],R[i],i};
	}
	sort(b+1,b+tot+1);
	int Max=0;
	for(int i=1;i<=tot;i++)
	{
		if(Max<b[i].r)	num++,res=1ll*res*siz[b[i].id]%MOD;
		Max=max(Max,b[i].r);
	}
	cout<<num<<" "<<res<<'\n';
}
int main()
{
	ios::sync_with_stdio(false);
	cin.tie(0),cout.tie(0);
	int T;
	cin>>T;
	while(T--)
	{
		cin>>n;
		for(int i=1;i<=(n<<1);i++)	cin>>a[i];
		solve();
	}
	return 0;
}

标签:int,题解,Hard,合并,集合,端点,Light,区间,激活
From: https://www.cnblogs.com/SuporShoop/p/18435704

相关文章

  • 【MX-J3-T3+】Tuple+ 题解
    一个比较自然的思路就是对于每个三元组\((u_i,v_i,w_i)\),把\((v_i,w_i)\)这个二元组放在属于\(u_i\)的vector里面。然后对于每一个\(i\in[1,n-3]\),把\(i\)的vector里面的所有二元组\((x,y)\)当作一条连接\(x,y\)的无向边,则我们的目的是在图中找出所有的三元环\(......
  • [ARC115E] LEQ and NEQ 题解
    我这场打的VP,结果E思考的时间比A还少。。但是我觉得我能想出这道题还是很有意义的,写篇题解记录一下。首先应该都不难想到动态规划吧?我们先使用暴力DP:设\(dp_{i,j}\)表示处理完前\(i\)个数,第\(i\)个数为\(j\)的方案数。我们考虑进行分类讨论:\(a_i≥a_{i-1}\):此时......
  • 9.27今日错题解析(软考)
    目录前言信息安全——网络攻击算法基础——二分查找数据库系统——数据库设计过程前言这是用来记录我每天备考软考设计师的错题的,今天知识点为网络攻击、二分查找和数据库设计过程,大部分错题摘自希赛中的题目,但相关解析是原创,有自己的思考,为了复习:),最后希望各位报考......
  • [GXOI/GZOI2019] 逼死强迫症 题解
    看到\(N\leq2\times10^9\)的范围,一眼矩阵快速幂优化DP。首先考虑朴素DP怎么写。根据题目所给信息,我们设\(dp_{i,0}\)表示前面\(i\)个方砖,并且已经使用了\(2\)个\(1\times1\)的方砖,\(dp_{i,1}\)则表示前面\(i\)个方砖,没有使用任何一个\(1\times1\)的方砖。......
  • [CERC2015] Digit Division 题解
    \(O(n^2)\)做法和大部分人最开始一样,我也想的是DP。设\(dp_i\)表示用前面\(i\)个字符拆分得到的答案。既然是统计方案数,我们肯定是根据前面的答案累加。考虑在\([1,i-1]\)中选择一个\(j\),如果\([j+1,i]\)的字符组成的数字能够被\(m\)整除,那么\(dp_i\)就可以累加......
  • [JLOI2015] 有意义的字符串 题解
    拿到题目,我们首先分析一下这个奇怪的式子:\[\lfloor(\frac{b+\sqrt{d}}{2})^n\rfloor~\text{mod}~p\]重点肯定是在里面的那个式子里面,最显眼的肯定也就是那个\(\sqrt{d}\),根据整体形式,我们可以联系一元二次方程的求根公式\(x=-\dfrac{-b\pm\sqrt{b^2-4ac}}{2a}\),这里也是一......
  • 「KDOI-06-S」消除序列 题解
    分享一个应该很少人想到的做法。首先贪心地想,第一种操作肯定最多选择一次。比如如果选择了下标\(i\)和\(j\)进行第一种操作,那么就等价于在\(\max\{i,j\}\)进行了一次操作。由于代价是非负数,则我们最多只用执行一次。当然也可以不使用这种操作。有了这个思路,我们先考虑不使......
  • 「TAOI-2」Ciallo~(∠・ω< )⌒★ 题解
    手玩了一个小时终于做出来了,这不得写一篇题解记录一下??下面设\(s\)的长度为\(n\),\(t\)的长度为\(m\)。考虑分类讨论:如果\(s\)中有一个子串\(s'\)与\(t\)完全相同(可以用哈希进行比较),设\(s'\)是\(s\)的第\(l\)到第\(r\)个字符组成的字符串,则我们可以删除\([1,......
  • 三星G8 OLED显示器S34BG850SC,使用DP线连接电脑,显示器黑屏问题解决。
    这个问题在网上好像很多人问,但是每个人的情况不同,总之我也是遇到了。事情大概:PC机显卡的DP口通过显示器自带的MiniDP线和显示器相连,这个没什么好说的了,原配只送MiniDP线,想必买这台显示器的人都是先用的这根线。然而我有一台NUC,通过雷电口也连接到了这台显示器。所以我这台G8是......
  • [TJOI2010] 天气预报 题解
    分析一下题目,大致意思就是给定一组常数\(a_i\),然后有一个递推式\(w_i=\sum_{j=1}^{n}w_{i-j}\timesa_{j}\),让你求出\(w_m\)对于\(4147\)取模的值。根据这个\(1\leqm\leq10^7\)的恐怖范围,姑且算到了\(O(m)\)的时间复杂度。但是观察一下这个递推式,发现\(O(m)\)跑......