生活随笔
收集整理的這篇文章主要介紹了
基环树小记
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
概念
基環樹就是有n個點n條邊的圖(比樹多出現一個環)。
特殊形態的基環樹
無向樹(N點N邊無向圖)
外向樹(每個點只有一條入邊)
內向樹(每個點只有一條出邊)
以上三種樹有十分優秀的性質,就是可以直接將環作為根。就可以對每個環的子樹進行單獨處理,最后再處理環
找環
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std
;
const int N
=5010;
struct Edge{int v
,nxt
;}edge
[N
<<1];
struct Line{int u
,v
;}line
[N
<<1];
int n
,m
,cnt
,head
[N
],dfn
[N
],ind
,fa
[N
],loop
[N
],len
;
bool ban
[N
][N
];
int t
,ans
[N
],rec
[N
];
bool cmp(Line a
,Line b
){return a
.v
>b
.v
;}
void addedge(int u
,int v
){edge
[++cnt
].v
=v
;edge
[cnt
].nxt
=head
[u
];head
[u
]=cnt
;
}
void getloop(int u
){dfn
[u
]=++ind
;for(int i
=head
[u
];i
;i
=edge
[i
].nxt
){int v
=edge
[i
].v
;if(v
==fa
[u
]) continue;if(dfn
[v
]){if(dfn
[v
]<dfn
[u
]) continue;for(int x
=v
;x
!=fa
[u
];x
=fa
[x
]) loop
[++len
]=x
;}else{fa
[v
]=u
;getloop(v
);}}
}
void dfs(int u
,int f
){rec
[++t
]=u
;for(int i
=head
[u
];i
;i
=edge
[i
].nxt
){int v
=edge
[i
].v
;if(v
==f
) continue;if(ban
[u
][v
]) continue;dfs(v
,u
);}
}
void getmin(){bool flag
=0;int i
;for(i
=1;i
<=n
;i
++){if(rec
[i
]<ans
[i
]){flag
=1;break;}else if(rec
[i
]>ans
[i
]) return;}if(!flag
) return;for(;i
<=n
;i
++) ans
[i
]=rec
[i
];
}
int main(){memset(ans
,0x7f,sizeof(ans
));scanf("%d%d",&n
,&m
);for(int i
=1;i
<=m
;i
++){int u
,v
;scanf("%d%d",&u
,&v
);line
[i
]=(Line
){u
,v
};line
[i
+m
]=(Line
){v
,u
};}sort(line
+1,line
+2*m
+1,cmp
);for(int i
=1;i
<=2*m
;i
++) addedge(line
[i
].u
,line
[i
].v
);if(m
==n
-1){dfs(1,0);for(int i
=1;i
<=n
;i
++) printf("%d ",rec
[i
]);return 0;}getloop(1);loop
[++len
]=loop
[1];for(int i
=1;i
<len
;i
++){ban
[loop
[i
]][loop
[i
+1]]=ban
[loop
[i
+1]][loop
[i
]]=1;t
=0;dfs(1,0);getmin();ban
[loop
[i
]][loop
[i
+1]]=ban
[loop
[i
+1]][loop
[i
]]=0;}for(int i
=1;i
<=n
;i
++) printf("%d ",ans
[i
]);return 0;
}
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#define N 1000010
#define int long long
using namespace std
;
struct Edge{int v
,nxt
;}edge
[N
];
int w
[N
],n
,ans
,cnt
,f
[N
][2],head
[N
],d
[N
],mark
;
bool vis
[N
];
void add(int u
,int v
){edge
[++cnt
].v
=v
;edge
[cnt
].nxt
=head
[u
];head
[u
]=cnt
;
}
void getloop(int u
){vis
[u
]=true;if(vis
[d
[u
]]) mark
=u
;else getloop(d
[u
]);return;
}
void dp(int u
){vis
[u
]=true;f
[u
][1]=w
[u
];f
[u
][0]=0;for(int i
=head
[u
];i
;i
=edge
[i
].nxt
){int v
=edge
[i
].v
;if(v
!=mark
){dp(v
);f
[u
][0]+=max(f
[v
][1],f
[v
][0]);f
[u
][1]+=f
[v
][0];}else f
[v
][1]=-2147483647/3;}return;
}
signed main(){scanf("%d",&n
);for(int i
=1;i
<=n
;i
++){scanf("%d%d",&w
[i
],&d
[i
]);add(d
[i
],i
);}for(int i
=1;i
<=n
;i
++){if(vis
[i
]) continue;getloop(i
);dp(mark
);int maxn
=max(f
[mark
][0],f
[mark
][1]);mark
=d
[mark
];dp(mark
);ans
+=max(maxn
,max(f
[mark
][0],f
[mark
][1]));}printf("%lld",ans
);
}
一般問題解決方法
一般解決方法都是如果是樹形dp,就對環部分進行環形dp的操作。不然可以考慮斷環法。
- 斷環法
每次斷開環上的一條邊跑一遍答案,然后取最大值。
適用于:數據較小,且環不會影響答案的題目
[NOIP2018 提高組] 旅行 - 二次dp法
對于環,我們可以像環形dp一樣將一條邊強行斷開處理一遍,然后強行連上再處理一遍
適用于:這樣子跑滿足正確性的
[ZJOI2008]騎士 - 斷環復制法
對于環,我們可以像環形dp一樣斷開環,然后再復制一遍放在后面
適用于:多個需要維護的
[IOI2008] Island
總結
以上是生活随笔為你收集整理的基环树小记的全部內容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。