首页 > 其他分享 >[Tricks-00003]CF1989F 套路叠加,高级分治

[Tricks-00003]CF1989F 套路叠加,高级分治

时间:2024-11-16 08:45:36浏览次数:1  
标签:int mid Tricks CF1989F SCC 400005 dfn low 00003

先说一个简单问题:给定一个 \(n\times m\) 的黑白网格图,每次可以将一行或者一列染成同一种色,判断是否能到达?

经典做法:倒过来考虑,每次将颜色全相同或为 * 的一行全染成 *,判断是否可以将这张图染成全 *。经典网格图转二分图,如果 \(s_{i,j}='W'\) 则将 \(i\) 向 \(j'\) 连一条有向边,否则 \(j'\) 向 \(i\) 连。每次相当于将一个没有出度或入度的点对应的边都删掉,判断是否能把所有边删空。容易发现等价于判断是否存在强连通分量,如果是 DAG,则直接按拓扑序取即可,否则随便找一个 SCC,里面点永远动不了。

不过这个题有额外操作方法,保证了每个 SCC 都也能操作成功,那么这个代价怎么算呢?

upd:不是哥们我读错题了,,,当时这里卡住了以为是个很难的问题,现在发现,它的题意指的是,同时染的格子的颜色可以从这次染这行的和这列的里面随便选一个,不过不能凭空制造!因此本题应该换个角度这样理解:咕咕咕

现在有了这个结论,那么我们只需要求出每个时刻的 SCC 状态即可!!!

受到一些可能是 QOJ 上的经典题的启发,我们想到了分治!不过类似连通性这种直接做分治,会遇到一些很大的问题。我比如把 \(time\leq mid\) 的所有边拿出来跑 SCC,那么现在会产生一个缩点之后是 DAG 的有向图,可是我这些 DAG 边究竟应该放哪里呢?我之后做右半边的时候点是用缩完之后还是之前的呢???

所以接下来我们就要明确一下这个常见套路。我们对每条边求出的是,它在第几时刻之后"完成使命"了,也就是说,相连的两个点归到了同一个 SCC。这样,我最后求每个时刻的 SCC 时,只要把挂在这个时刻上的所有边拉出来,缩个并查集就好了。因此,我们可以用 solve(l,r,vector<int>g) 来表示一次分治,要去将 \(g\) 里面所有边得到它们对应的时刻。当然,有可能完成不了使命,我们就将其设为 \(q+1\),不是大问题。当然,每条边的完成时刻一定不早于出现时刻。所以何时加什么边就很明确了:

先得到一个 \(mid\)。将 \(g\) 中所有 \(time\leq mid\) 的边拉出来跑 SCC,得到一堆强连通块,这样可以求出 \(g\) 中每条边应该归到左,还是右,亦或直接扔掉。具体地,对一个 \(\leq mid\) 的边,如果两点已经属于一个 SCC 了,就归到左边,否则归到右边;\(>mid\) 的边,如果两点已经属于了,就没用可以扔了,否则归到右边,接下来继续递归即可。

看起来很对,不过写代码的时候会产生这样一个问题:用缩前还是缩后的点来着?其实很容易想到肯定是用缩后的点的,不过加入的时间要注意一下,应该是先递归处理左半边,然后把这次的 SCC 缩点做好(可以再用个并查集维护),最后处理右半边。不难做到 \(O(n+q\log q)\)。如果实现地不精细多个 log 也无所谓,只要不写 set 常数应该还好。

代码:

#include<bits/stdc++.h>
using namespace std;
int uu[200005],vv[200005],cx[200005],N;
vector<int>g[400005];
int dfn[400005],low[400005],st[400005],vist[400005];
int col[400005],t1=0,t2=0;
void tarjan(int x){
	dfn[x]=low[x]=++t1;
	st[++t2]=x,vist[x]=1;
	for(auto cu:g[x]){
		if(!dfn[cu]){
			tarjan(cu);
			low[x]=min(low[x],low[cu]);
		}else if(vist[cu]){
			low[x]=min(low[x],dfn[cu]);
		}
	}
	if(dfn[x]!=low[x])return;
	int pp;
	do{
		pp=st[t2--];vist[pp]=0;
		col[pp]=x;
	}while(pp!=x);
}
int ff[400005];
int findff(int x){
	return x==ff[x]?x:ff[x]=findff(ff[x]);
}
void solve(int l,int r,vector<int>gg){
	if(l==r){
		for(auto d:gg)cx[d]=l;
		return;
	}
	int mid=(l+r)>>1;
	vector<int>se;
	for(auto d:gg){
		int U=findff(uu[d]),V=findff(vv[d]);
		dfn[U]=low[U]=col[U]=vist[U]=0;
		dfn[V]=low[V]=col[V]=vist[V]=0;
		if(d<=mid){
			se.emplace_back(U);se.emplace_back(V);
			g[U].emplace_back(V);
		}
	}
	sort(se.begin(),se.end());
	se.resize(unique(se.begin(),se.end())-se.begin());
	t1=t2=0;
	for(auto x:se)if(!dfn[x]){
		tarjan(x);
	}
	vector<int>g1,g2;
	for(auto d:gg){
		int U=ff[uu[d]],V=ff[vv[d]];
		if(d<=mid){
			if(col[U]==col[V])g1.emplace_back(d);
			else g2.emplace_back(d);
		}else{
			if(col[U]!=col[V]||!col[U]||!col[V])g2.emplace_back(d);
		}
	}
	vector<pair<int,int>>v;
	for(auto x:se){
		g[x].clear();
		v.emplace_back(x,col[x]);
	}
	solve(l,mid,g1);
	for(auto pi:v){
		int fx=findff(pi.first),fy=findff(pi.second);
		if(fx!=fy)ff[fx]=fy;
	}
	solve(mid+1,r,g2);
}
int fa[400005],cnt[400005];
int findfather(int x){
	return x==fa[x]?x:fa[x]=findfather(fa[x]);
}
vector<int>v2[200005];
long long f(int x){
	return x==1?0:1ll*x*x;
}
int main(){
	int n,m,q;
	scanf("%d%d%d",&n,&m,&q);N=n+m;
	for(int i=1;i<=q;++i){
		int u,v;char op[15];
		scanf("%d%d%s",&u,&v,op+1);
		int U=u,V=n+v;
		if(op[1]=='B')swap(U,V);
		uu[i]=U,vv[i]=V;
	}
	vector<int>vc;
	for(int i=1;i<=q;++i)vc.emplace_back(i);
	for(int i=1;i<=N;++i)ff[i]=i;
	solve(1,q+1,vc);
	for(int i=1;i<=N;++i)fa[i]=i,cnt[i]=1;
	for(int i=1;i<=q;++i){
		if(cx[i]&&cx[i]<=q)v2[cx[i]].emplace_back(i);
	}
	long long ans=0;
	for(int i=1;i<=q;++i){
		for(auto d:v2[i]){
			int fu=findfather(uu[d]),fv=findfather(vv[d]);
			if(fu!=fv){
				fa[fu]=fv;
				ans=ans-f(cnt[fu])-f(cnt[fv]);
				ans=ans+f(cnt[fv]+=cnt[fu]);
			}
		}
		printf("%lld\n",ans);
	}
	return 0;
}

标签:int,mid,Tricks,CF1989F,SCC,400005,dfn,low,00003
From: https://www.cnblogs.com/maihe/p/18546657

相关文章

  • [CEOI2023] Tricks of the Trade 题解
    Description有\(n\)个机器人排成一排,第\(i\)个机器人的购买价是\(a_i\)欧元,卖出价是\(b_i\)欧元。给定\(1\lek\len\),你需要购买一段长度至少为\(k\)的区间中所有的机器人,然后选择其中的恰好\(k\)个机器人来卖出。你需要求出:你能够得到的最大收益;在收益最大化......
  • 【热门主题】000034 云原生后端:开启现代应用开发新纪元
    前言:哈喽,大家好,今天给大家分享一篇文章!并提供具体代码帮助大家深入理解,彻底掌握!创作不易,如果能帮助到大家或者给大家一些灵感和启发,欢迎收藏+关注哦......
  • [Tricks-00002]CF2026F 操作建树&维护带删deque信息的经典套路
    这怎么是*2700???我大受震撼了好吧。简要题意:有一个初始长度是\(cnt=1\)的序列\(S\),序列每个位置都是若干个二元组\((p,t)\)组成的可重集,初始时\(S_1\)为空集。\(q\)组操作(为修改或询问),有如下四种操作:1x:把\(S_x\)复制到一个新加的点\(S_{++cnt}\)上。2xpt:将\((p......
  • 【热门主题】000032 大数据治理:开启数据驱动新时代
    前言:哈喽,大家好,今天给大家分享一篇文章!并提供具体代码帮助大家深入理解,彻底掌握!创作不易,如果能帮助到大家或者给大家一些灵感和启发,欢迎收藏+关注哦......
  • C++刷题tricks整理
    是自己做题中整理的常用C++操作,此为存档。STL容器容器适配器,STL里面的vector/array/deque/set/map/unordered_set可以直接使用==比较是否相等:vector<int>a;deque<int>a;map<int,string>a;unordered_map<int,string>a;set<int>a;unordered_set<int>a;forward_lis......
  • tricks
    二分答案P2824排序有时候可以尝试二分最后的答案,把不好维护的东西变成\(0\)和\(1\)。操作分块P5443桥梁将操作分块维护,一般适用于可以很好维护静态询问但是需要支持修改的情况(?)。状态压缩去除后效性P2157学校食堂dp有后效性但影响范围很小可以考虑把后续决策压缩起......
  • Tricks(长期更新)
    会很杂,尽量分类,每个trick会配题。难以分类的难以分类可能只是自己太菜了。曼哈顿距离与切比雪夫距离的转化对于两点\((x_1,y_1),(x_2,y_2)\),曼哈顿距离为\(|x_1-x_2|+|y_1+y_2|\),切比雪夫距离为\(\max(|x_1-x_2|,|y_1-y_2|)\)。画图可以发现到原点的曼哈顿距离为\(1\)的点形......
  • 一些常用tricks,基础知识收录
    uoj转,YAML的ppt格式不想改了,将就看看吧|一些常用tricks,基础知识收录byzsjchildren:|题目链接:T1:CF504EMishaandLCPonTreeT2:LuoguP2617DynamicRankingsT3:LuoguP4197PeaksT4:LuoguP4690[Ynoi2016]镜中的昆虫T5:H1079.E.‘MinamiKotori......
  • 【油猴脚本】00003案例 Tampermonkey油猴脚本引入css 库,油猴脚本css库的使用
    前言:哈喽,大家好,今天给大家分享html+css绚丽Loading!并提供具体代码帮助大家深入理解,彻底掌握!创作不易,如果能帮助到大家或者给大家一些灵感和启发,欢迎收藏+关注哦......
  • 边分治维护强连通分量(CF1989F,P5163)
    这里的边分治和树上的点分治边分治不一样,是维护强连通分量用的,每条边有一个出现时间,通过将每条边按连通关系分流重新排列,从而维护每个时间点整张图的连通性。具体的,这个算法是维护这样的一类问题:n个点,m条边按时间顺序依次加入,每加入一条边,你需要回答一些问题,比如在这个时间点......