1. 程式人生 > 其它 >#Tarjan,貪心#LOJ 3684 「COCI 2022.3」Usmjeravanje

#Tarjan,貪心#LOJ 3684 「COCI 2022.3」Usmjeravanje

題目傳送門


分析

可以發現題目實際上求的是最小強連通分量個數。

並且每個強連通分量必然是由最多兩段區間 \(a_l\sim a_r,b_L\sim b_R\) 組成的

只要存在一條路 \(b_R->a_l,a_r->b_L\) 那麼這個區間就能連在一起。

將所有的邊按 \(a\) 先升序,再 \(b\) 降序的順序排序,然後按順序列舉,觀察到當前邊也許會和後面的邊有交。

如果當前邊不是 \(b_i\) 連向 \(a_i\) 一定不優,反轉之後把有交的邊刪掉,可以維護 \(b\) 的最大值,如果 \(b\) 不超過最大值就證明有交。

然後定向之後跑一遍Tarjan就可以了。


程式碼

#include <cstdio>
#include <cctype>
#include <algorithm>
using namespace std;
const int N=400011;
struct node{int y,next;}e[N<<1]; struct rec{int x,y,rk;}a[N];
int dfn[N],low[N],as[N],n,m,k,et,tot,v[N],st[N],Top,scc; bool ans[N];
int iut(){
	int ans=0; char c=getchar();
	while (!isdigit(c)) c=getchar();
	while (isdigit(c)) ans=ans*10+c-48,c=getchar();
	return ans;
}
void print(int ans){
	if (ans>9) print(ans/10);
	putchar(ans%10+48);
}
int min(int a,int b){return a<b?a:b;}
bool cmp(rec x,rec y){return x.x<y.x||(x.x==y.x&&x.y>y.y);}
void add(int x,int y){e[++et]=(node){y,as[x]},as[x]=et;}
void tarjan(int x){
	dfn[x]=low[x]=++tot,v[x]=1,st[++Top]=x;
	for (int i=as[x];i;i=e[i].next)
	if (!dfn[e[i].y]){
		tarjan(e[i].y);
		low[x]=min(low[x],low[e[i].y]);
	}else if (v[e[i].y]) low[x]=min(low[x],dfn[e[i].y]);
	if (dfn[x]==low[x]){
		int y; ++scc;
		do{
			y=st[Top--],v[y]=0;
		}while (y!=x);
	}
}
int main(){
	n=iut(),m=iut(),k=iut();
	for (int i=1;i<n;++i) add(i,i+1);
	for (int i=1;i<m;++i) add(i+n,i+1+n);
	for (int i=1;i<=k;++i) a[i]=(rec){iut(),iut(),i};
	sort(a+1,a+1+k,cmp);
	for (int i=1,mx=0;i<=k;++i)
	if (mx<a[i].y) add(a[i].y+n,a[i].x),mx=a[i].y,ans[a[i].rk]=1;
	    else add(a[i].x,a[i].y+n);
	for (int i=1;i<=n+m;++i) if (!dfn[i]) tarjan(i);
	printf("%d",scc);
	for (int i=1;i<=k;++i) putchar(i==1?10:32),putchar(ans[i]+48);
	return 0;
}