【BZOJ】3495: PA2010 Riddle 2-SAT算法
【题意】有n个城镇被分成了k个郡,有m条连接城镇的无向边。要求给每个郡选择一个城镇作为首都,满足每条边至少有一个端点是首都。n,m,k<=10^6。
【算法】2-SAT,前后缀优化建图
【题解】每个城镇只有作为首都和不是首都两种选项,即2-sat问题。
2-sat问题中所有边必须加反向边,下面过程忽略反向边。
对于一条边(x,y),如果x是0,那么y必须是1,即x-y'。(y-x'是反向边,考虑的时候忽略)
但是一个郡只有一个首都有点烦,有一种套路叫前后缀优化建图。
对于每个点x,假设一个点x+n,表示编号<=x的和x同在一个郡的点中是否有首都。
假设x和y是同一个郡的相邻编号点(y>x),那么:
1.前缀:(x+n)' - (y+n)'
2.修改:x' - (x+n)'
3.只有一个首都:(x+n)' - y
所以n*4个点和n*8条边,进行2-sat。
复杂度O(M),M=(m+3*n)*2。
#include<cctype> #include<cstdio> #include<cstring> #include<algorithm> using namespace std; int read(){ int s=0,t=1;char c; while(!isdigit(c=getchar()))if(c=='-')t=-1; do{s=s*10+c-'0';}while(isdigit(c=getchar())); return s*t; } const int maxn=4000010; int n,m,K,first[maxn],tot,c,B[1000010],C[1000010]; bool mark[maxn]; struct edge{int v,from;}e[maxn*2];//// void insert(int u,int v){ tot++;e[tot].v=v;e[tot].from=first[u];first[u]=tot; tot++;e[tot].v=u^1;e[tot].from=first[v^1];first[v^1]=tot; } int dfsnum=0,TOT,top,dfn[maxn],low[maxn],st[maxn],belong[maxn]; void tarjan(int x){ dfn[x]=low[x]=++dfsnum; st[++top]=x; for(int i=first[x];i;i=e[i].from)if(!dfn[e[i].v]){ tarjan(e[i].v); low[x]=min(low[x],low[e[i].v]); }else if(!belong[e[i].v])low[x]=min(low[x],dfn[e[i].v]); if(low[x]==dfn[x]){ TOT++; while(st[top]!=x)belong[st[top--]]=TOT; belong[st[top--]]=TOT; } } int main(){ n=read();m=read();K=read(); for(int i=1;i<=m;i++){ int u=read()-1,v=read()-1; insert(u<<1,v<<1|1); } for(int i=0;i<n;i++)insert(i<<1|1,(i+n)<<1|1);/// for(int k=1;k<=K;k++){ int w=read(),p,pre=read()-1;B[pre]=k; for(int i=2;i<=w;pre=p,i++){ p=read()-1;B[p]=k; insert((pre+n)<<1|1,(p+n)<<1|1); insert((pre+n)<<1|1,p<<1); } } for(int i=0;i<n*4;i+=2){ if(!dfn[i])tarjan(i); if(!dfn[i^1])tarjan(i^1); if(belong[i]==belong[i^1]){printf("NIE");return 0;} } printf("TAK"); return 0; }
从这道题开始,我知道了一件事:理论复杂度O(nm)的DFS版2-sat是可以无视随机被卡TLE的。
嘤嘤嘤T_T。