Light Bulbs (Hard Version) 题解
提供一个非常另类的解法,没有异或哈希,没有建图,没有缩点和强连通分量,而是使用了并查集和线段树的算法。
由于每个颜色恰好有两种,我们考虑把两个颜色的位置
根据题意,如果我们激活了一个区间
考虑使用并查集,把相交但不包含的任意两个区间合并到一个集合里面,即若
由于题目保证了所有区间的并一定是
而要处理这个特殊情况也很简单,只需要对每个集合求出它的区间并
不过题目还要询问方案数,这个问题也很简单,对于一个集合
大致思路就是这样,接下来讨论一些实现方法。
-
如果判断一个区间是否被另一个区间包含?首先把这些区间按照
从小到大排序,然后维护前缀的所有区间的 最大值,若 ,则 就会被 这个区间包含。 -
如何快速把有交集且不包含的两个区间合并起来?我们可以对于每一个区间,按照
从小到大排序,用线段树维护前 个区间的右端点,当我们枚举到第 个区间时,我们就查询 范围内的右端点,然后把 与这些右端点对应的区间编号合并。但是暴力对每一个区间合并是 的。我们可以在线段树维护的过程中,每次给
的位置赋一个值,这个值就是 这个右端点所对应的区间下标。每次要合并的时候,我们就查询一次 中的最大值,只将 和这个最大值合并。统计完之后,我们再将所有区间按照 从大到小排序,用线段树维护区间 的区间下标最大值,然后每次合并 与 中的最大值。这样下来就能完全地合并每一个 。正确性大家可以尝试去证明一下,这里就不赘述了。
#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;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 使用C#创建一个MCP客户端
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现