(线段树优化建图+tarjan强连通缩点+拓扑排序)CF1914G2 Light Bulbs (Hard Version)

HZOJ
洛谷

写在前面

So are you happy now,
Finally happy now, are you?
那什么 也就那样吧我,
像是弄丢了一切,
一切都随心所欲地到来 又悄无声息地离开,
照这样下去 我可能不会再爱上什么了,
旅行在那已破败不堪的,
记忆之中,
我们在橙色太阳之下,
一同起舞 无形无影,
在那没有注定了的离别,
曾经美好的记忆之中相遇,
Forever young.

CF1914G2 Light Bulbs (Hard Version) 题解

题意

给定\(n\)种颜色,每种颜色各2个的灯泡序列,每个开着的灯泡可以使与其成对的同色灯泡打开,每对开着的同色灯泡可以使二者形成的区间内的灯泡打开。求问能使所有灯泡打开的最小初始打开灯泡集合的大小和该集合的数量。

思路

首先考虑转化条件。很显然能将题意转化为相交或包含的区间可以两两合并,求问最后形成了多少个不交区间和不交区间的端点个数积。

考虑如何合并区间。暴力跳的复杂度是\(O(n^2)\),太劣了。貌似也没有什么数据结构能简单地维护。只有进一步转化条件。考虑将问题转化到图上。将每种颜色的灯泡抽象成一个点,能作用的点间相互连边。但是注意到暴力连的时间空间复杂度都是\(O(n^2)\) 的,所以考虑优化。由于每种颜色的点连的边都是连向该颜色两点间区间的所有点,所以考虑线段树优化建图。

线段树优化建图的思路大概是父亲向子节点连边,叶子节点向原节点连边。连边时就由原节点连向区间对应的线段树上的点。本题中原节点即代表每种颜色的灯泡,线段树的叶子节点代表灯泡序列。如果在原图上连边,形成的连通块个数即为子任务1的答案。但是由于有线段树沟通,所有节点都在一个连通块内。考虑tarjan求强连通分量,强连通分量的个数即为子任务1的答案。但是有线段树作为中间节点,无法直接求出,故考虑缩点后拓扑排序排除线段树上节点对统计答案时的干扰。

Warnings

1.多测记得清空,而且此类问题最好手动清空,因为memset会遍历大量无用位置使得运行时间大大超限。
2.统计第二问答案时每个点应乘以其点权的2倍,因为每个点的点权是其包含的颜色个数,而每种颜色有2个灯泡可以任选。
3.拓扑排序只删去线段树上点所连出的边。

代码

#include<bits/stdc++.h>
using namespace std;
typedef pair<int,int> pii;
const int maxn=2e5+10,mod=998244353;
int t;
int n,col[maxn<<3];
pii kk[maxn];
int tot,to[maxn*30],nxt[maxn*30],h[maxn*30],mxsiz,root;
inline void adde(int x,int y){
	to[++tot]=y;
	nxt[tot]=h[x];
	h[x]=tot;
}int totc,toc[maxn*30],nxtc[maxn*30],hc[maxn*30],in[maxn*30];
inline void addec(int x,int y){
	toc[++totc]=y;
	nxtc[totc]=hc[x];
	hc[x]=totc;
	++in[y];
}
int cnt,dfn[maxn*30],low[maxn*30],stk[maxn*30],top,sum,bl[maxn*30],siz[maxn*30];
bool vis[maxn*30];
void tarjan(int x){
	dfn[x]=low[x]=++cnt;
	stk[++top]=x;
	vis[x]=1;
	for(int i=h[x];i;i=nxt[i]){
		int y=to[i];
		if(!dfn[y]){
			tarjan(y);
			low[x]=min(low[x],low[y]);
		}
		else if(vis[y]) low[x]=min(low[x],dfn[y]);
	}
	if(dfn[x]==low[x]){
		++sum;
		siz[sum]=0;
		int y;
		do{
			y=stk[top--];
			vis[y]=0;
			bl[y]=sum;
			if(y<=n) ++siz[sum];
		}while(x!=y);
	}
}
int lc[maxn*10],rc[maxn*10];
inline void build(int &u,int l,int r){
	if(!u) u=++mxsiz;
	if(l==r){
		adde(u,col[l]);
		return;
	} 
	int mid=(l+r)>>1;
	build(lc[u],l,mid);
	build(rc[u],mid+1,r);
	adde(u,lc[u]);
	adde(u,rc[u]);
}
inline void update(int u,int ul,int ur,int l,int r,int o){
	if(ul>ur) return;
	if(ul==l&&ur==r){
		adde(o,u);
		return;
	} 
	int mid=(l+r)>>1;
	if(ul<=mid) update(lc[u],ul,min(ur,mid),l,mid,o);
	if(ur>mid) update(rc[u],max(mid+1,ul),ur,mid+1,r,o);
}
inline void cc(int &u,int l,int r){
	if(l==r){
		u=0;
		return;
	}
	int mid=(l+r)>>1;
	cc(lc[u],l,mid);
	cc(rc[u],mid+1,r); 
	u=0;
} 
inline void clear(){
	for(int i=1;i<=n*2;i++) col[i]=0;
	for(int i=1;i<=mxsiz;i++) bl[i]=dfn[i]=low[i]=vis[i]=0;
	for(int i=1;i<=tot;i++) h[i]=0;
	for(int i=1;i<=totc;i++) hc[i]=0;
	for(int i=1;i<=sum;i++) in[i]=siz[i]=0;
	for(int i=1;i<=n;i++) kk[i].first=kk[i].second=0;
	cc(root,1,n<<1);
	mxsiz=sum=top=cnt=totc=tot=root=0;
}
inline void dd(){
	cin>>n;
	mxsiz=n;
	for(int i=1;i<=n<<1;i++){
		cin>>col[i];
		if(!kk[col[i]].first) kk[col[i]].first=i;
		else kk[col[i]].second=i;
	}
	build(root,1,n<<1);
	for(int i=1;i<=n;i++) if(kk[i].first+1<=kk[i].second-1) update(root,kk[i].first+1,kk[i].second-1,1,n<<1,i);
	for(int i=1;i<=mxsiz;i++)
	  if(!dfn[i]) tarjan(i);
	for(int i=1;i<=mxsiz;i++)
		for(int j=h[i];j;j=nxt[j]){
			int y=to[j];
			if(bl[y]!=bl[i]) addec(bl[i],bl[y]);
		}
	queue<int>topu;
	for(int i=1;i<=sum;i++)
	  if(!in[i]) topu.push(i);
	int ans1=0,ans2=1;	
	while(topu.size()){
		int x=topu.front();
		topu.pop();
		if(siz[x]){
			++ans1;
			ans2=2ll*ans2*siz[x]%mod;
			continue;
		} 
		for(int i=hc[x];i;i=nxtc[i]){
			int y=toc[i];
			--in[y];
			if(!in[y]) topu.push(y);
		}
	}
	cout<<ans1<<' '<<ans2<<'\n';
	clear();
}
int main(){
	std::ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
	cin>>t;
	while(t--) dd();
	return 0;
}
posted @ 2025-08-24 07:58  _dlwlrma  阅读(13)  评论(0)    收藏  举报