tarjan & 2-set
缩点模板
inline void add(int u,int v)
{
to[++cnt]=v,nxt[cnt]=he[u],he[u]=cnt;
}
void tar(int u)
{
low[u]=dfn[u]=++sgn;
st[++top]=u;
for(int e=he[u];e;e=nxt[e])
{
int v=to[e];
if(!dfn[v]) tar(v),low[u]=min(low[u],low[v]);
else if(!co[v]) low[u]=min(low[u],dfn[v]);
}
if(dfn[u]==low[u])
{
co[u]=++col;
while(top&&st[top]!=u)
co[st[top--]]=col;
top--;
}
}
时间复杂度\(O(m)\)
缩点后的染色是反DFN序,feel一下
例题1
缩点后必须是条链,DP即可
其中涉及到计数,因为选的是节点,需要用set维护新建的边,防止重复
set<int>S[N];
void dfs(int u) {
if(vis[u]) return;
f[u]=a[u],g[u]=1;
for(auto v:S[u]) {
dfs(v);
if(f[u]<f[v]+a[u]) {
f[u]=f[v]+a[u];
g[u]=g[v];
} else if(f[u]==f[v]+a[u]) {
g[u]=mo(g[u]+g[v]);
}
}
}
int main(){
scanf("%d%d%d",&n,&m,&p);
for(int i=1;i<=m;i++) {
int u,v; scanf("%d%d",&u,&v);
V[u].push_back(v);
E1[i]=u,E2[i]=v;
}
for(int i=1;i<=n;i++) {
if(!dfn[i]) tar(i);
}
for(int i=1;i<=m;i++) {
int u=E1[i],v=E2[i];
if(co[u]!=co[v]) {
S[co[u]].insert(co[v]);
}
}
int ans=0,sum=0;
for(int i=1;i<=col;i++) {
f[i]=a[i],g[i]=1;
for(auto v:S[i]) {
if(f[i]<f[v]+a[i]) {
f[i]=f[v]+a[i];
g[i]=g[v];
} else if(f[i]==f[v]+a[i]) {
g[i]=mo(g[i]+g[v]);
}
}
if(ans==f[i]) sum=mo(sum+g[i]);
else if(ans<f[i]){
ans=f[i],sum=g[i];
}
}
printf("%d\n%d\n",ans,sum);
return 0;
}
2-set分析
将一个点裂成2个点,分别表示两种对立的状态,
边表示逻辑关系,如果\(i\)->\(j\),则\((i,j)\)连边
然后tarjan缩成DAG
显然如果两种对立的状态在一个SCC中,则无解
反之有解,可以通过按染色的顺序去找,每次选择dfn较大的,也就是co较小的(保证不冲突)
其中如果有些状态无解,可以将其连向有解的状态(如果有解的状态能推出无解的状态,就会形成环)
Vector 清空:
V[i].clear();
vector<int>().swap(V[i]);
例题1
画张图,每条边可以在里面也可以在外面,这是两种对立的状态
如果两条边有重复,则必须一条在里一条在外
又因为平面图存在性质:
\[3n-6\leq m
\]
考虑数学归纳法:\(n=3\)时,\(m=3\)
n>3时,每加一个点最多多3条边(和头尾形成三角形)
即证
int main() {
int T; scanf("%d",&T);
while(T--) {
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++) {
scanf("%d%d",&E1[i],&E2[i]);
}
for(int i=1;i<=n;i++) {
int t; scanf("%d",&t);
a[t]=i;
}
if(m>3*n-6) {
puts("NO"); continue;
}
for(int i=1;i<=m+m;i++) {
V[i].clear(); vector<int>().swap(V[i]);
}
for(int i=1;i<=m;i++) {
int u=a[E1[i]],v=a[E2[i]];
memset(vis,0,sizeof(vis));
if(u<v) {
for(int j=u+1;j<v;j++) vis[j]=1;
vis[u]=vis[v]=-1;
} else {
for(int j=1;j<v;j++) vis[j]=1;
for(int j=u+1;j<=n;j++) vis[j]=1;
vis[u]=vis[v]=-1;
}
for(int j=i+1;j<=m;j++) {
u=a[E1[j]],v=a[E2[j]];
if(vis[u]!=-1&&vis[v]!=-1&&vis[u]^vis[v]) {
V[i].push_back(j+m);
V[j+m].push_back(i);
}
}
}
memset(dfn,0,sizeof(dfn));
memset(co,0,sizeof(co));
tot=col=top=0;
for(int i=1;i<=m+m;i++) {
if(!dfn[i]) tar(i);
}
bool fl=0;
for(int i=1;i<=m;i++) {
if(co[i]==co[i+m]) {
puts("NO"); fl=1; break;
}
}
if(!fl) puts("YES");
}
return 0;
}
例题2
二分答案转化为判定问题,2-set
struct Po{int x,y; }a[N],S,T,E1[N],E2[N];
inline int D(Po x,Po y) {
return abs(x.x-y.x)+abs(x.y-y.y);
}
bool check(int mid) {
for(int i=1;i<=n+n;i++) {
V[i].clear(); vector<int>().swap(V[i]);
}
for(int i=1;i<=A;i++) {
int u=E1[i].x,v=E1[i].y;
V[u+n].push_back(v),V[u].push_back(v+n);
V[v+n].push_back(u),V[v].push_back(u+n);
}
for(int i=1;i<=B;i++) {
int u=E2[i].x,v=E2[i].y;
V[u].push_back(v),V[u+n].push_back(v+n);
V[v+n].push_back(u+n),V[v].push_back(u);
}
for(int i=1;i<=n;i++) {
for(int j=1;j<i;j++) {
if(D(a[i],S)+D(a[j],T)+D(S,T)>mid) {
V[j+n].push_back(i+n);
V[i].push_back(j);
}
if(D(a[i],T)+D(a[j],S)+D(S,T)>mid) {
V[i+n].push_back(j+n);
V[j].push_back(i);
}
if(D(a[i],S)+D(a[j],S)>mid) {
V[i].push_back(j+n);
V[j].push_back(i+n);
}
if(D(a[i],T)+D(a[j],T)>mid) {
V[i+n].push_back(j);
V[j+n].push_back(i);
}
}
}
memset(dfn,0,sizeof(dfn));
memset(co,0,sizeof(co));
tot=col=top=0;
for(int i=1;i<=n+n;i++) {
if(!dfn[i]) tar(i);
}
for(int i=1;i<=n+n;i++) {
if(co[i]==co[i+n]) return 0;
}
return 1;
}
int main() {
while(scanf("%d%d%d",&n,&A,&B)!=EOF) {
scanf("%d%d%d%d",&S.x,&S.y,&T.x,&T.y);
for(int i=1;i<=n;i++) {
scanf("%d%d",&a[i].x,&a[i].y);
}
for(int i=1;i<=n+n;i++) {
V[i].clear(); vector<int>().swap(V[i]);
}
for(int i=1;i<=A;i++) {
int u,v; scanf("%d%d",&u,&v);
V[u+n].push_back(v),V[u].push_back(v+n);
V[v+n].push_back(u),V[v].push_back(u+n);
E1[i]=(Po){u,v};
}
for(int i=1;i<=B;i++) {
int u,v; scanf("%d%d",&u,&v);
V[u].push_back(v),V[u+n].push_back(v+n);
V[v+n].push_back(u+n),V[v].push_back(u);
E2[i]=(Po){u,v};
}
memset(dfn,0,sizeof(dfn));
memset(co,0,sizeof(co));
tot=col=top=0;
for(int i=1;i<=n+n;i++) {
if(!dfn[i]) tar(i);
}
bool fl=0;
for(int i=1;i<=n;i++) {
if(co[i]==co[i+n]) {
puts("-1"); fl=1; break;
}
}
if(fl) continue;
int l=0,r=1e8,mid,ans=0;
while(l<=r) {
mid=l+r>>1;
if(check(mid)) ans=mid,r=mid-1;
else l=mid+1;
}
printf("%d\n",ans);
}
return 0;
}
例题3
如果没有\(x\),每次只有两种状态2-set
如果有\(x\),可以枚举\(x\)的3种状态,但观察可以发现,只需要图\(a\)和图\(b\)带入\(x\)判断是否可行即包含了3种状态
注意:如果\((u,h_u,v,h_v)\),\(v\)为\(h_v\)的状态不存在,\(u\)为\(h_u\)的状态也不存在
int main() {
scanf("%d%d%s%d",&n,&K,s+1,&m);
for(int i=1;i<=m;i++) {
char ch=getchar();
int u=0,v=0,k1=0,k2=0;
while(ch<'0'||ch>'9') ch=getchar();
while(ch>='0'&&ch<='9') {
u=(u<<1)+(u<<3)+ch-'0';
ch=getchar();
}
while(ch<'A'||ch>'C') ch=getchar();
k1=ch-'A';
while(ch<'0'||ch>'9') ch=getchar();
while(ch>='0'&&ch<='9') {
v=(v<<1)+(v<<3)+ch-'0';
ch=getchar();
}
while(ch<'A'||ch>'C') ch=getchar();
k2=ch-'A';
E1[i]=(A){u,k1},E2[i]=(A){v,k2};
}
for(int i=1,j=1;i<=n&&j<=K;i++) {
if(s[i]=='x') loc[j++]=i;
}
for(int i=0;i<(1<<K);i++) {
for(int j=1;j<=K;j++) {
if(i&(1<<j-1)) s[loc[j]]='a';
else s[loc[j]]='b';
}
for(int j=1;j<=n+n;j++) {
V[j].clear();
vector<int>().swap(V[j]);
}
for(int j=1;j<=m;j++) {
int u=E1[j].u,k1=E1[j].v,v=E2[j].u,k2=E2[j].v;
if(s[u]-'a'==k1) continue;
if(s[v]-'a'==k2) {
if(s[u]=='a') k1--;
if(s[u]=='b') {
if(k1) k1--;
}
V[u+k1*n].pb(u+(!k1)*n);
continue;
}
if(s[u]=='a') k1--;
if(s[v]=='a') k2--;
if(s[u]=='b') {
if(k1) k1--;
}
if(s[v]=='b') {
if(k2) k2--;
}
V[u+k1*n].pb(v+k2*n);
V[v+n*(!k2)].pb(u+n*(!k1));
}
memset(dfn,0,sizeof(dfn));
memset(co,0,sizeof(co));
tot=top=col=0;
for(int j=1;j<=n+n;j++) {
if(!dfn[j]) tar(j);
}
bool fl=0;
for(int j=1;j<=n;j++) {
if(co[j]==co[j+n]) {
fl=1; break;
}
}
if(!fl) {
for(int j=1;j<=n;j++) {
if(co[j]<co[j+n]) {
if(s[j]=='a') putchar('B');
else putchar('A');
} else {
if(s[j]=='c') putchar('B');
else putchar('C');
}
}
return 0;
}
}
puts("-1");
return 0;
}