当前位置: 首页 > news >正文

2-sat

2-sat

定义

2−SAT2-SAT2SAT简单的说就是给出nnn个集合,每个集合有两个元素,已知若干个(a,b)(a,b)(a,b),表示aaabbb矛盾,然后从每个集合选择一个元素,判断能否一共选nnn个两两不矛盾的元素。
更通俗的讲,有nnn个变量,每种变量只有两种取值(一般为0和1),还有mmm个关系表达式,一般为 x1∧x2=false x_1 \land x_2=false x1x2=false 询问能否给这nnn个变量找出一组赋值,使得mmm个关系全部满足

逻辑关系

a∨b⇔(¬a→b)∧(¬b→a) a \lor b \Leftrightarrow (\lnot a \rightarrow b) \land (\lnot b \rightarrow a) ab(¬ab)(¬ba)

  • a成立或b成立 等价于若a不成立则b必成立若b不成立则a必成立

拆点建图

对于每个变量xxx,我们建立两个点x和¬xx和 \lnot xx¬x分别表示xxxturefalse , 在存储方式上,可以给第iii个变量标号为iii,其对应反值为i+ni+ni+n, 对于表达式a∨ba \lor bab可以转化为(¬a→b)∧(¬b→a)(\lnot a \rightarrow b) \land (\lnot b \rightarrow a)(¬ab)(¬ba),按照箭头方向建边即可

tarjan缩点

  1. 跑完tarjan缩点后判断
  2. iiii+ni+ni+n在同一个sccsccscc里,那么显然无解,因为$$不可能即取000又取111
  3. 否则iiii+ni+ni+n可能在一条链上或无关,那么一定存在可行解

构造可行解

  1. 因为蕴含关系具有传递性,选择后者冲突更小,即选择拓扑序较大的节点更容易没有冲突,即若i→i+ni \rightarrow i+nii+n,则我们选择i+ni+ni+n,若 i+n→ii+n \rightarrow ii+ni, 我们选择iii
  2. 由于tarjantarjantarjan得到的是逆拓扑序,所以sccsccscc编号越小的点其拓扑序越大,所以我们应该选择sccsccscc编号较小的变量所对应的文字语义,根据以上选择原则,若我们认为iii为真,i+ni+ni+n为假,则得到
  • scc[i]<scc[i+n]scc[i]<scc[i+n]scc[i]<scc[i+n],则令xi=1x_i=1xi=1
  • scc[i]>scc[i+n]scc[i]>scc[i+n]scc[i]>scc[i+n],则令xi=0x_i=0xi=0
  • 复杂度$O(n+m) $

P4782 【模板】2-SAT

题目描述

nnn 个布尔变量 x1∼xnx_1\sim x_nx1xn,另有 mmm 个需要满足的条件,每个条件的形式都是 「xix_ixitrue / falsexjx_jxjtrue / false」。比如 「x1x_1x1 为真或 x3x_3x3 为假」、「x7x_7x7 为假或 x2x_2x2 为假」。

2-SAT 问题的目标是给每个变量赋值使得所有条件得到满足。

代码
const int N=2e6+10;int dfn[N],low[N],tot;
int stk[N],instk[N],top;
int scc[N],siz[N],cnt;
vector<int>e[N];void tarjan(int x)
{dfn[x]=low[x]=++tot;stk[++top]=x;instk[x]=1;for(auto ed:e[x]){if(!dfn[ed]){tarjan(ed);low[x]=min(low[x],low[ed]);}else if(instk[ed]){low[x]=min(low[x],dfn[ed]);}}if(dfn[x]==low[x]){int y;++cnt;do{y=stk[top--];instk[y]=0;scc[y]=cnt;++siz[cnt];}while(y!=x);}
}void solve()
{int n,m;cin>>n>>m;for(int i=1;i<=m;i++){int a,x,b,y;cin>>a>>x>>b>>y;if(x && y){e[a+n].pb(b);e[b+n].pb(a);}if(x && !y){e[a+n].pb(b+n);e[b].pb(a);}if(!x && y){e[a].pb(b);e[b+n].pb(a+n);}if(!x && !y){e[a].pb(b+n);e[b].pb(a+n);}}for(int i=1;i<=2*n;i++) if(!dfn[i]) tarjan(i);for(int i=1;i<=n;i++){if(scc[i]==scc[i+n]) {cout<<"IMPOSSIBLE"<<endl;return;}}cout<<"POSSIBLE"<<endl;for(int i=1;i<=n;i++) cout<<(scc[i]<scc[i+n])<<" \n"[i==n];
}

P5782和平委员会

题目描述

根据宪法,Byteland 民主共和国的公众和平委员会应该在国会中通过立法程序来创立。 不幸的是,由于某些党派代表之间的不和睦而使得这件事存在障碍。 此委员会必须满足下列条件:

  • 每个党派都在委员会中恰有 111 个代表。
  • 如果 222 个代表彼此厌恶,则他们不能都属于委员会。

每个党在议会中有 222 个代表。代表从 111 编号到 2n2n2n。 编号为 2i−12i-12i12i2i2i 的代表属于第 iii 个党派。

任务:写一程序读入党派的数量和关系不友好的代表对,计算决定建立和平委员会是否可能,若行,则列出委员会的成员表。

第一行有两个非负整数 n,mn,mn,m。他们各自表示:党派的数量 nnn 和不友好的代表对 mmm

接下来 mmm 行,每行为一对整数 a,ba,ba,b,表示代表 aaabbb 互相厌恶。

如果不能创立委员会,则输出信息 NIE

若能够成立,则输出包括 nnn 个从区间 1112n2n2n 选出的整数,按升序写出,每行一个,这些数字为委员会中代表的编号。

如果委员会能以多种方法形成,程序可以只输出它们的某一个。

思路

不要无脑拆变量为真和假,逻辑关系的本质就是找到符合条件的点去建边,我们要满足题中的条件,两个互相厌恶的人不能在一起,那么我们就把一个人和另外一个人的搭档建边就可以了

代码
const int N=1e6+10;int dfn[N],low[N],tot;
int stk[N],instk[N],top;
int scc[N],siz[N],cnt;
vector<int>e[N];void tarjan(int x)
{dfn[x]=low[x]=++tot;stk[++top]=x;instk[x]=1;for(auto ed:e[x]){if(!dfn[ed]){tarjan(ed);low[x]=min(low[x],low[ed]);}else if(instk[ed]){low[x]=min(low[x],dfn[ed]);}}if(dfn[x]==low[x]){int y;++cnt;do{y=stk[top--];instk[y]=0;scc[y]=cnt;++siz[cnt];}while(y!=x);}
}void solve()
{int n,m;cin>>n>>m;auto to=[&](int x){if(x%2) return x+1;else return x-1;};for(int i=1;i<=m;i++){int a,b;cin>>a>>b;e[a].pb(to(b));e[b].pb(to(a));}for(int i=1;i<=2*n;i++) if(!dfn[i]) tarjan(i);for(int i=1;i<=2*n;i+=2) if(scc[i]==scc[i+1]) {cout<<"NIE"<<endl;return;}for(int i=1;i<=2*n;i+=2){if(scc[i]<scc[i+1]) cout<<i<<endl;else cout<<i+1<<endl;}}

P4171满汉全席

题目描述

和模板一样,不能同时不满足两种关系

第一行包含一个数字 KKK1≤K≤501\le K \le 501K50),代表测试文件包含了 KKK 组数据。

每一组测试数据的第一行包含两个数字 nnnmmmn≤100n≤100n100m≤1000m≤1000m1000),代表有 nnn 种材料,mmm 位评审员。

为方便起见,舍弃做法的中文名称而给予编号,编号分别从 111nnn

接下来的 mmm 行,每行都代表对应的评审员所拥有的两个喜好,每个喜好由一个英文字母跟一个数字代表,如 m1m1m1 代表这个评审喜欢第 111 个材料透过满式料理做出来的菜,而 h2h2h2 代表这个评审员喜欢第 222 个材料透过汉式料理做出来的菜。

每组测试数据输出一行,如果不会发生没有人能通过考核的窘境,输出 GOOD;否则输出 BAD(均为大写字母)。

思路

板子板子

代码
const int N=310;int dfn[N],low[N],tot;
int stk[N],instk[N],top;
int scc[N],siz[N],cnt;
vector<int>e[N];void tarjan(int x)
{dfn[x]=low[x]=++tot;stk[++top]=x;instk[x]=1;for(auto ed:e[x]){if(!dfn[ed]){tarjan(ed);low[x]=min(low[x],low[ed]);}else if(instk[ed]){low[x]=min(low[x],dfn[ed]);}}if(dfn[x]==low[x]){int y;++cnt;do{y=stk[top--];instk[y]=0;scc[y]=cnt;++siz[cnt];}while(y!=x);}
}void solve()
{int n,m;cin>>n>>m;for(int i=1;i<=2*n;i++){e[i].clear();low[i]=dfn[i]=0;scc[i]=siz[i]=0;stk[i]=instk[i]=0;}cnt=top=tot=0;for(int i=1;i<=m;i++)//汉1满0{char x,y;int a,b;cin>>x>>a>>y>>b;if(x=='m' && y=='m'){e[a].pb(b+n);e[b].pb(a+n);}if(x=='m' && y=='h'){e[a].pb(b);e[b+n].pb(a+n);}if(x=='h' && y=='m'){e[a+n].pb(b+n);e[b].pb(a);}if(x=='h' && y=='h'){e[a+n].pb(b);e[b+n].pb(a);}}for(int i=1;i<=2*n;i++) if(!dfn[i]) tarjan(i);for(int i=1;i<=n;i++) if(scc[i]==scc[i+n]) {cout<<"BAD"<<endl;return;}cout<<"GOOD"<<endl;
}

P3007The Continental Cowngress G

题目描述

由于对农场主约翰的领导不满,奶牛们已经从农场中分离出来,并成立了第一个大陆奶牛议会。基于「每头奶牛都能得到她想要的东西」这一原则,她们决定采用以下投票系统:

出席的 MMM 头奶牛将对 NNN 项立法议案进行投票。每头奶牛对两个(不同的)议案 BiB_iBiCiC_iCi 分别投下「赞成」或「反对」票(在输入文件中用 YN 表示)。这些投票分别称为 VBiVB_iVBiVCiVC_iVCi

最终,议案的通过与否必须满足每头奶牛至少有一个投票结果符合她的意愿。例如,如果 Bessie 对议案 111 投了「赞成」票,对议案 222 投了「反对」票,那么在任何有效的解决方案中,要么议案 111 通过,要么议案 222 被否决(或者两者都满足)。

给定每头奶牛的投票情况,你的任务是找出哪些议案将被通过,哪些议案将被否决,以符合上述规则。如果没有解决方案,请输出 IMPOSSIBLE。如果至少有一个解决方案,那么对于每个议案,显示:

Y 如果在每个解决方案中该议案都通过

N 如果在每个解决方案中该议案都被否决

? 如果存在一些解决方案中该议案通过,而在另一些解决方案中该议案没有通过

考虑以下投票集(每头奶牛投两票):

编号111222333
奶牛 111赞成反对
奶牛 222反对反对
奶牛 333赞成赞成
奶牛 444赞成赞成

由此,两个解决方案满足每头奶牛:

  • 议案 111 通过(这满足了奶牛 111333444
  • 议案 222 被否决(这满足了奶牛 222
  • 议案 333 可以通过或被否决(这就是有两个解决方案的原因)

事实上,这些是仅有的两个解决方案,因此答案是 YN?

111 行:两个用空格分隔的整数:NNNMMM

222 行到第 M+1M+1M+1 行:第 i+1i+1i+1 行描述奶牛 iii 的投票情况,包含四个用空格分隔的字段——一个整数,一个投票,另一个整数,和另一个投票:Bi,VBi,Ci,VCiB_i,VB_i,C_i,VC_iBi,VBi,Ci,VCi

思路

很好的一道题,关于选择YYY还是NNN我们只需要判断拓扑序大小即可,$ ?$ 的情况如何判断呢,用tarjantarjantarjan缩点的目的是判断两个互斥的变量是否在一个环内,即两者互相可达出现既取真又取假的无解情况,考虑有解的情况
1.i→i+ni \rightarrow i+nii+n此时i+ni+ni+niii约束,我们选择i+ni+ni+n
2.i+n→ii+n \rightarrow ii+ni此时iiii+ni+ni+n约束,我们选择iii
由此可知,若iiii+ni+ni+n互不约束,两种取值选择哪种都是合法的
即若iiii+ni+ni+n都不能到达彼此则为???
由于本题 n≤1000n \leq 1000n1000 支持O(n2)O(n^2)O(n2), 可以选择dfsdfsdfs处理可达性

代码
const int N=2010;int dfn[N],low[N],tot;
int stk[N],instk[N],top;
int scc[N],siz[N],cnt;
vector<int>e[N];void tarjan(int x)
{dfn[x]=low[x]=++tot;stk[++top]=x;instk[x]=1;for(auto ed:e[x]){if(!dfn[ed]){tarjan(ed);low[x]=min(low[x],low[ed]);}else if(instk[ed]){low[x]=min(low[x],dfn[ed]);}}if(dfn[x]==low[x]){int y;++cnt;do{y=stk[top--];instk[y]=0;scc[y]=cnt;++siz[cnt];}while(y!=x);}
}void solve()
{int n,m;cin>>n>>m;for(int i=1;i<=m;i++){int a,b;char x,y;cin>>a>>x>>b>>y;if(x=='N' && y=='N'){e[a].pb(b+n);e[b].pb(a+n);}if(x=='N' && y=='Y'){e[a].pb(b);e[b+n].pb(a+n);}if(x=='Y' && y=='N'){e[a+n].pb(b+n);e[b].pb(a);}if(x=='Y' && y=='Y'){e[a+n].pb(b);e[b+n].pb(a);}}for(int i=1;i<=2*n;i++) if(!dfn[i]) tarjan(i);for(int i=1;i<=n;i++) if(scc[i]==scc[i+n]) {cout<<"IMPOSSIBLE"<<endl;return;}vector<int>vis(2*n+1);auto dfs=[&](auto &&dfs,int u)->void{vis[u]=1;for(auto ed:e[u]){if(!vis[ed]) dfs(dfs,ed);}return;};for(int i=1;i<=n;i++){int x,y;fill(all(vis),0);dfs(dfs,i);x=vis[i+n];fill(all(vis),0);dfs(dfs,i+n);y=vis[i];if(x && !y) cout<<'N';if(!x && y) cout<<'Y';if(!x && !y) cout<<'?'; }}

cf27D

题意

给定一个环,环上有一些边,你可以选择把每条边建在环内或者环外,问是否有使得不存在边相交的建图方式,有的话输出具体方案

思路

若有两条边相交,则我们肯定选择一条建在环内,一条建在环外,因此每条边有两种状态:建在环内或者环外,考虑2−sat2-sat2sat求解
怎么判断两条边相交呢,假设存在[x1,y1],[x2,y2][x1,y1],[x2,y2][x1,y1],[x2,y2], 由于是在环上,那么若两边相交则一定有

-x1<x2<y1<y2x1 < x2 < y1 < y2x1<x2<y1<y2
-x2<x1<y2<y1x2 < x1 < y2 < y1x2<x1<y2<y1

n2n^2n2判断相交建图即可

代码
cconst int N=210;int dfn[N],low[N],tot;
int stk[N],instk[N],top;
int scc[N],siz[N],cnt;
vector<int>e[N];void tarjan(int x)
{dfn[x]=low[x]=++tot;stk[++top]=x;instk[x]=1;for(auto ed:e[x]){if(!dfn[ed]){tarjan(ed);low[x]=min(low[x],low[ed]);}else if(instk[ed]){low[x]=min(low[x],dfn[ed]);}}if(dfn[x]==low[x]){int y;++cnt;do{y=stk[top--];instk[y]=0;scc[y]=cnt;++siz[cnt];}while(y!=x);}
}void solve()
{int n,m;cin>>n>>m;vector<int>x(m+1),y(m+1);for(int i=1;i<=m;i++){cin>>x[i]>>y[i];if(x[i]>y[i]) swap(x[i],y[i]);}for(int i=1;i<=m;i++)//1内0外{for(int j=i+1;j<=m;j++){if((x[i]<x[j] && x[j]<y[i] && y[i]<y[j]) || (x[j]<x[i] && x[i]<y[j] && y[j]<y[i])){e[i].pb(j+m);e[i+m].pb(j);e[j].pb(i+m);e[j+m].pb(i);}}}for(int i=1;i<=2*m;i++) if(!dfn[i]) tarjan(i);for(int i=1;i<=m;i++){if(scc[i]==scc[i+m]) {cout<<"Impossible"<<endl;return;}}for(int i=1;i<=m;i++){if(scc[i]<scc[i+m]) cout<<'i';else cout<<'o';}
}

P3209平面图判定

题目描述

若能将无向图 G=(V,E)G=(V, E)G=(V,E) 画在平面上使得任意两条无重合顶点的边不相交,则称 GGG 是平面图。判定一个图是否为平面图的问题是图论中的一个重要问题。现在假设你要判定的是一类特殊的图,图中存在一个包含所有顶点的环,即存在哈密顿回路。

输入文件的第一行是一个正整数 TTT,表示数据组数 (每组数据描述一个需要判定的图)。接下来从输入文件第二行开始有 TTT 组数据,每组数据的第一行是用空格隔开的两个正整数 NNNMMM,分别表示对应图的顶点数和边数。紧接着的 MMM 行,每行是用空格隔开的两个正整数 uuuvvv (1≤u,v≤N)\left(1\leq u,v\leq N\right)(1u,vN),表示对应图的一条边 (u,v)\left(u,v\right)(u,v), 输入的数据保证所有边仅出现一次。每组数据的最后一行是用空格隔开的 NNN 个正整数,从左到右表示对应图中的一个哈密顿回路:V1,V2,…,VNV_1,V_2,…,V_NV1,V2,,VN,即对任意 i≠ji\not=ji=jVi≠VjV_i\not=V_jVi=Vj 且对任意 1≤i≤N−11\leq i\leq N-11iN1(Vi,Vi+1)∈E\left(V_i,V_{i+1}\right)\in E(Vi,Vi+1)E(V1,VN)∈E\left(V_1,V_N\right)\in E(V1,VN)E。输入的数据保证 100%100\%100% 的数据满足 T≤100,3≤N≤200,M≤10000T\leq100,3\leq N\leq200,M\leq10000T100,3N200,M10000

包含 TTT 行,若输入文件的第 iii 组数据所对应图是平面图,则在第 iii 行输出 YES\text{YES}YES,否则在第 iii 行输出 NO\text{NO}NO,注意均为大写字母

思路

和上一题的区别在于环的顺序不是1−n1-n1n那么我们只需要按照哈密顿路径映射编号即可,问题在于 m≤1e4m \leq 1e4m1e4 ,O(m2)O(m^2)O(m2)的建图方式是无法接受的,这里引入平面图的性质:m≤n∗3−6m \leq n*3-6mn36 , 即最多600600600条边,对于不符合m>n∗3−6m > n*3-6m>n36的情况直接返回nonono即可

代码
const int N=1210;int dfn[N],low[N],tot;
int stk[N],instk[N],top;
int scc[N],siz[N],cnt;
vector<int>e[N];void tarjan(int x)
{dfn[x]=low[x]=++tot;stk[++top]=x;instk[x]=1;for(auto ed:e[x]){if(!dfn[ed]){tarjan(ed);low[x]=min(low[x],low[ed]);}else if(instk[ed]){low[x]=min(low[x],dfn[ed]);}}if(dfn[x]==low[x]){int y;++cnt;do{y=stk[top--];instk[y]=0;scc[y]=cnt;++siz[cnt];}while(y!=x);}
}void solve()
{int n,m;cin>>n>>m;for(int i=1;i<=N;i++){e[i].clear();low[i]=dfn[i]=0;scc[i]=siz[i]=0;stk[i]=instk[i]=0;}tot=top=cnt=0;vector<int>x(m+1),y(m+1);for(int i=1;i<=m;i++) cin>>x[i]>>y[i];vector<int>id(n+1);for(int i=1;i<=n;i++){int x;cin>>x;id[x]=i;}if(m>3*n-6) {no;return;}for(int i=1;i<=m;i++){if(id[x[i]]>id[y[i]]) swap(x[i],y[i]);}for(int i=1;i<=m;i++){int x1=id[x[i]];int y1=id[y[i]];for(int j=i+1;j<=m;j++){int x2=id[x[j]];int y2=id[y[j]];if((x1<x2 && x2<y1 && y1<y2) ||(x2<x1 && x1<y2 && y2<y1)){e[i].pb(j+m);e[i+m].pb(j);e[j].pb(i+m);e[j+m].pb(i);}}}for(int i=1;i<=2*m;i++) if(!dfn[i]) tarjan(i);for(int i=1;i<=m;i++) if(scc[i]==scc[i+m]) {no;return;}yes;}

例 P6378Riddle

题意

nnn 个点 mmm 条边的无向图被分成 kkk 个部分。每个部分包含一些点。

请选择一些关键点,使得每个部分有一个关键点,且每条边至少有一个端点是关键点。

第一行三个整数 n,m,kn,m,kn,m,k

接下来 mmm 行,每行两个整数 a,ba,ba,b,表示有一条 a,ba,ba,b 间的边。

接下来 kkk 行,每行第一个整数为 www,表示这个部分有 www 个点;接下来 www 个整数,为在这个部分中的点的编号。

若可能选出请输出 TAK,否则输出 NIE

对于全部的测试点,保证 1≤k,w≤n≤1061\le k,w\le n\le 10^61k,wn106∑w=n\sum w=nw=n1≤a,b≤n1\le a,b\le n1a,bn0≤m≤1060\le m\le 10^60m106

思路

$ 2-sat +$前缀优化建图
每个点有两个状态选或不选,显然是2−sat2-sat2sat,对于一条边至少选一个点的条件比较容易解决,关键在于每个点集最多选一个点的条件,若选了一个点的话就不能选其他点,即对于$ i \in [l,r],我们要对,我们要对,我们要对i和和[l,i-1] , [i+1,r]$ 两个区间内的所有点都建边,时间复杂度是O(n2)O(n^2)O(n2)的无法接受,这时候就可以考虑前缀优化建图将时间复杂度降到$O(6n) $
建好图之后就是板子了

代码
const int N=4e6+10;int dfn[N],low[N],tot;
int stk[N],instk[N],top;
int scc[N],siz[N],cnt;
vector<int>e[N];void tarjan(int x)
{dfn[x]=low[x]=++tot;stk[++top]=x;instk[x]=1;for(auto ed:e[x]){if(!dfn[ed]){tarjan(ed);low[x]=min(low[x],low[ed]);}else if(instk[ed]){low[x]=min(low[x],dfn[ed]);}}if(dfn[x]==low[x]){int y;++cnt;do{y=stk[top--];instk[y]=0;scc[y]=cnt;++siz[cnt];}while(y!=x);}
}void solve()
{int n,m,k;cin>>n>>m>>k;for(int i=1;i<=m;i++){int a,b;cin>>a>>b;e[a+n].pb(b);e[b+n].pb(a);}for(int i=1;i<=k;i++){int w;cin>>w;int p;//前一个点for(int j=1;j<=w;j++){int x;cin>>x;e[x].pb(x+2*n);//向下e[x+3*n].pb(x+n);if(j!=1){e[p+2*n].pb(x+2*n);//向左e[x+3*n].pb(p+3*n);//向右e[x].pb(p+3*n);//左下e[p+2*n].pb(x+n);//右下}p=x;}}for(int i=1;i<=4*n;i++) if(!dfn[i]) tarjan(i);for(int i=1;i<=n;i++) if(scc[i]==scc[i+n]) {cout<<"NIE"<<endl;return;}cout<<"TAK"<<endl;	
}

cf587D

题意

给你一张 n 个点 m 条边的无向图,边有颜色和边权。你要从中删去一些边,满足:

  1. 任意两条删掉的边没有公共的顶点。

  2. 任意两条剩余的、颜色相同的边没有公共的顶点。

  3. 删去的边的边权最大值最小。
    求这个最小值和方案

思路

首先最大值最小可以想到二分,我们可以二分边权最大值,对于大于midmidmid的边不选,小于midmidmid的边可以选,每条边有选和不选两种状态,考虑2−sat2-sat2sat,
首先对于条件1和条件2,不难发现若一个点有两条以上相同颜色的边则一定无解,则可以将边分成以下333类:

  1. 对于条件1,若边x,yx , yx,y有相同的顶点,那么需要建边x1→y0x_1 \rightarrow y_0x1y0代表选xxx则不能选$y $
  2. 对于条件2, 若边x,yx , yx,y有相同的顶点且颜色相同,那么需要建边x0→y1x_0 \rightarrow y_1x0y1代表不选xxx则必须选yyy
  3. 边权大于midmidmid的,建边x→x+mx \rightarrow x+mxx+m代表不可能选xxx
  • 对于第111类边,其肯定是O(m2)O(m^2)O(m2)的,考虑前缀优化建图
  • 对于第222类边,经过特判之后,每个顶点最多有两条相同颜色的边,因此复杂度为O(m)O(m)O(m)
  • 但是需要注意我们是按顶点建边的,对于第111类和第222类边其应该是独立的,因此在前缀优化建图方式上我们选择动态开点,这样不会将两个独立的部分联系在一起
代码
const int N=2e6+10;
int dfn[N],low[N],tot;
int stk[N],instk[N],top;
int scc[N],siz[N],cnt;struct node{int v,c,w,id;bool operator < (const node&t)const{return c<t.c;}
};vector<node>e1[N];
vector<int>e[N];void tarjan(int x)
{dfn[x]=low[x]=++tot;stk[++top]=x;instk[x]=1;for(auto ed:e[x]){if(!dfn[ed]){tarjan(ed);low[x]=min(low[x],low[ed]);}else if(instk[ed]){low[x]=min(low[x],dfn[ed]);}}if(dfn[x]==low[x]){int y;++cnt;do{y=stk[top--];instk[y]=0;scc[y]=cnt;siz[cnt]++;}while(y!=x);}
}void solve()
{int n,m;cin>>n>>m;vector<int>w(m+1);for(int i=1;i<=m;i++){int a,b,c,d;cin>>a>>b>>c>>d;w[i]=d;e1[a].pb({b,c,d,i});e1[b].pb({a,c,d,i});}for(int i=1;i<=n;i++)//一个点上2条以上同色边{sort(all0(e1[i]));for(int j=2;j<e1[i].size();j++){if(e1[i][j-2].c==e1[i][j].c) {no;return;}}}auto check=[&](int x)->bool{for(int i=0;i<N;i++){e[i].clear();low[i]=dfn[i]=0;scc[i]=siz[i]=0;stk[i]=instk[i]=0;}tot=cnt=top=0;int k=2*m;for(int i=1;i<=n;i++){if(e1[i].empty()) continue;int pp0=0,pp1=0;for(int j=0;j<e1[i].size();j++){int x0=e1[i][j].id;int x1=x0+m;int p0=++k;int p1=++k;e[x0].pb(p0);//向下连e[p1].pb(x1);if(j!=0){e[pp0].pb(p0);//右e[p1].pb(pp1);//左e[pp0].pb(x1);//右下e[x0].pb(pp1);//左下}pp0=p0;pp1=p1;}for(int j=1;j<e1[i].size();j++)//颜色匹配{if(e1[i][j-1].c==e1[i][j].c){e[e1[i][j-1].id+m].pb(e1[i][j].id);e[e1[i][j].id+m].pb(e1[i][j-1].id);}}}for(int i=1;i<=m;i++) if(w[i]>x) e[i].pb(i+m);//代表不选for(int i=1;i<=2*m;i++) if(!dfn[i]) tarjan(i);for(int i=1;i<=m;i++) if(scc[i]==scc[i+m]) return false;return true;};int l=-1,r=1e9+1;while(l+1<r){int mid=(l+r)>>1;if(check(mid)) r=mid;else l=mid;}	if(r==1e9+1) {no;return;}check(r);vector<int>ans;for(int i=1;i<=m;i++) if(scc[i]<scc[i+m]) ans.pb(i);yes;cout<<r<<" "<<ans.size()<<endl;for(auto x:ans) cout<<x<<" ";
}
http://www.dtcms.com/a/482722.html

相关文章:

  • KPI、OKR 和 GS 的区别
  • 坂田网站建设费用明细wordpress 最近登录地址
  • 网站开发技术微信公众平台如何绑定网站
  • electron+react+esbuild入门项目
  • iOS 应用加固与苹果软件混淆指南,如何防止 IPA 被反编译与二次打包?
  • jsp电商网站怎么做网络营销是什么部门
  • 网站优化体验报告百度网盟推广步骤
  • 物联网系统三层架构解析
  • 京东联手广汽、宁德时代造车!
  • PEFT适配器加载
  • React Hooks 核心规则自定义 Hooks
  • 江门网站制作 华企立方洛宁县东宋乡城乡建设局网站
  • 河南网站建设哪家有三品合一网站建设案例
  • 位运算专题总结:从变量初始化陷阱到理解异或分组
  • Linux学习笔记(八)--环境变量与进程地址空间
  • 【动态规划】题目中的「0-1 背包」和「完全背包」的问题
  • Streamlit 中文全面教程:从入门到精通
  • 大模型系列-dify
  • 推荐系统:Python汽车推荐系统 数据分析 可视化 协同过滤推荐算法 汽车租赁 Django框架 大数据 计算机✅
  • 第16讲:深入理解指针(6)——sizeof vs strlen 与 指针笔试题深度解析
  • 【iOS】PrivacyInfo.xcprivacy隐私清单文件(二)
  • 环保网站建设公司排名手机访问wordpress网站卡
  • 从零构建大模型 Build a large language model from scratch by Sebastian Raschka 阅读笔记
  • 基于Chainlit和Llamalndex的智能RAG聊天机器人实现详解
  • 18.5 GLM-4大模型私有化部署实战:3秒响应+显存降低40%优化全攻略
  • Prisma 命令安全指南
  • Linux系统下文件操作系统调用详解
  • 网站备案后需要年检吗官方网站搭建
  • 515ppt网站建设北京朝阳区属于几环
  • 5~20.数学基础