vector<int> v[N];
vector<pair<int,int> > e[N];
void add_edge(int a,int b){
v[a].eb(b);
}
void add_edge(int a,int b,int w) {
e[a].eb(mp(b,w));
}void dfs(int x){
vis[x]=true;
for(int i:v[x]){
if(!vis[i]) dfs(i);
}
}void bfs(int s){
queue<int> q;
q.push(s);
while(!q.empty()){
int x=q.front();q.pop();
for(int i:v[x]) {
if(!vis[i]){
vis[i]=true;
q.push(i);
}
}
}
}wiwihorz 讀書會圖論I簡報的 code
wiwihorz 讀書會圖論I簡報的 code
void dfs(int x){
for(int &p=it[x];p<SZ(e[x]);p++){
if(!used[e[x][p].S]) {
used[e[x][p].S]=1;
dfs(e[x][p].F);
}
}
ans.eb(x);
}void dfs(int x,int p=-1,int d=0){
dep[x]=d;
for(int i:v[x]){
if(i==p) continue;
dfs(i,x,d+1);
}
}int dis[N],maxdep[x];
void dfs(int x,int p=-1){
maxdep[x]=dis[x]=0;
for(int i:v[x]){
if(i!=p){
dfs(i,x);
dis[x]=max(dis[x],max(dis[i],maxdep[x]+maxdep[i]+1));
maxdep[x]=max(maxdep[x],maxdep[i]+1);
}
}
}void toplogical sort(){
queue<int> q;
for(int i=1;i<=n;i++) if(!deg[i]) q.push(i);
while(q.size()){
int x=q.front();
q.pop();
for(auto [i,w]:v[x]){
deg[i]--;
if(!deg[i]) q.push(i);
}
}
}每個任務 \(x\) 有其花費天數、也有一些順序關係。他能開始做只有當連向 \(x\) 的所有任務都完成了
void toplogical sort(){
queue<int> q;
for(int i=1;i<=n;i++) if(!deg[i]) q.push(i);
while(q.size()){
int x=q.front();
q.pop();
for(auto [i,w]:v[x]){
dp[i]=max(dp[i],dp[x]+w);
deg[i]--;
if(!deg[i]) q.push(i);
}
}
}一種資料結構可以進行兩種操作
void init(int n){
for(int i=1;i<=n;i++) p[i]=i,sz[i]=1;
}
int fp(int x){
if(x!=p[x]) p[x]=fp(p[x]);//路徑壓縮
return p[x];
}
void Union(int a,int b){
a=fp(a);//找到連通塊的祖先
b=fp(b);
if(a!=b){
if(sz[a]<sz[b]){
swap(a,b);//確保a是數量較大的
}
p[b]=a;//把b指向a
sz[a]+=sz[b];
}
}
int in[N],out[N];
int t=1;
void dfs(int x,int p=-1){
in[x]=t++; // 進入的時間戳記
for(int i:v[x]) {
if(i!=p) dfs(i,x);
}
out[x]=t++; // 離開的時間戳記
}bool isanc(int a,int b){
return in[a]<=in[b]&&out[b]<=out[a];
}void build(){
for(int i=1;i<=K;i++){
for(int j=1;j<=n;j++) ac[i][j]=ac[i-1][ac[i-1][j]];
}
}int LCA(int a,int b){
if(isanc(a,b)) return a;
if(isanc(b,a)) return b;
for(int i=K;i>=0;i--){//跳到洽 $k_1-1$ 層的位置
if(!isanc(ac[i][a],b)) a=ac[i][a];
}
return ac[0][a];
}int in[N],out[N];
int t=1;
void dfs(int x,int p=-1){
in[x]=t++; // 進入的時間戳記
if(p!=-1) ac[0][x]=p;
else ac[0][x]=x; // 預處理父親
for(int i:v[x]) {
if(i!=p) dfs(i,x);
}
out[x]=t++; // 離開的時間戳記
}
void build(){
for(int i=1;i<=K;i++){
for(int j=1;j<=n;j++) ac[i][j]=ac[i-1][ac[i-1][j]];
}
}
bool isanc(int a,int b){//判斷 a 是否為 b 的祖先
return in[a]<=in[b]&&out[b]<=out[a];
}
int LCA(int a,int b){
if(isanc(a,b)) return a;
if(isanc(b,a)) return b;
for(int i=K;i>=0;i--){//跳到洽 $k_1-1$ 層的位置
if(!isanc(ac[i][a],b)) a=ac[i][a];
}
return ac[0][a];
}同時也會是權重最大值最小的樹
DSU s; //並查集
void Kruskal(vector<edge> e){
sort(e.begin(),e.end(),cmp);//按照邊權由小到大排
int sum=0;
for(edge ei:e){
if(!s.same(ei.a,ei.b)) s.union(ei.a,ei.b),sum+=ei.w;
}
}typedef pair<int,int> pii;
void dijkistra(int s,int t){
priority_queue<pii,vector<pii>,greater<pii>> pq;
pq.push(mp(0,s));
fill(dis,dis+N,INF);
dis[s]=0;
while(pq.size()){
pii now=pq.top();
pq.pop();
for(pii p2:v[now.S]){
if(dis[p2.F]>dis[now.S]+p2.S) {
dis[p2.F]=dis[now.S]+p2.S;
pq.push(mp(dis[p2.F],p2.F));
}
}
}
}typedef pair<int,int> pii;
void dijkistra(int s,int t){
priority_queue<pii,vector<pii>,greater<pii>> pq;
pq.push(mp(0,s));
fill(dis,dis+N,INF);
dis[s]=0;
while(pq.size()){
pii now=pq.top();
pq.pop();
if(dis[now.S]<now.F) continue; //允許重複入隊寫法
for(pii p2:v[now.S]){
if(dis[p2.F]>dis[now.S]+p2.S) {
dis[p2.F]=dis[now.S]+p2.S;
pq.push(mp(dis[p2.F],p2.F));
}
}
}
}wiwihorz 暑培圖論II簡報的 code
wiwihorz 暑培圖論II簡報的 code
wiwihorz 暑培圖論II簡報的 code
void FloydWarshall(){
for(int k=1;k<=n;k++){
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
dis[i][j]=min(dis[i][k]+dis[k][j]);
}
}
}
}二分圖 (bipartite graph)
可以把點分成兩堆且同一堆內的節點間都沒有邊
匹配 (matching)
找一些點兩兩配對,每個點只能在一個匹配中且同一匹配的點必須相鄰
最大匹配 (maximum matching)
有最多配對的匹配
匹配是一個邊的獨立集
匹配點 有被配對的點
匹配邊 兩端是一個配對的邊
交錯路徑
交替經過未匹配邊、匹配邊的路徑
增廣路
一種滿足頭尾都是未匹配點的交錯路徑
const int N=1e3+7;
vector<int> v[N];//X的鄰點
int match[N]; // Y所匹配的節點
bool vis[N]; //在這次尋找中是否被走過
int dfs(int x){
for(int y:v[x]){
if(vis[y]) continue;
vis[y]=true;
if(match[y]==-1||dfs(match[y])){
match[y]=x;
return true;
}
}
return false;
}
int bipartiteMatching(int n){
fill(match,match+n+1,-1);
int ans=0;
rep(i,1,n){
fill(vis,vis+n+1,0);
if(dfs(i)) ans++;
}
return ans;
}
點覆蓋 用點去覆蓋所有邊的點集合
點獨立集 互不相鄰的點集
邊覆蓋 用邊去覆蓋所有點的邊集合
邊獨立集 互不共點的邊集 ( 匹配 )
二分圖左右兩側 \(X,Y\)
最小點覆蓋 \(C_v\)
最小邊覆蓋 \(C_e\)
最大點獨立集 \(I\)
最大邊獨立集 = 最大匹配 = \(M\)
因為覆蓋 \(M\) 條匹配至少需要 \(|M|\)個點
所以 \(|C_v| \geq |M|\)
所以只要構造出一組點獨立集 \(|C_v|=|M|\)即可
構造方法如下:
從 左邊沒有被匹配的點去走符合增廣路 "交替出現" 要求的路徑(未匹配邊-匹配邊-未匹配邊...) 並標記路過的所有點。
之後左邊沒被標記到的點 + 右邊被標記的點 即為最小點覆蓋
證明- 1 ( 該得到的點集可以覆蓋所有的邊 ):
證明不可能有一條邊 左端有標記,右端沒標記
假定得到點集為 \(|P|\)
證明-2 \(|P|=|M|\)
首先圖不會有孤點,不然一定無解
1. 把 \(M\)中的邊都加入 \(C_e\) 則覆蓋\(2|M|\)個點,
最多再選\(V-2|M|\)條邊集可覆蓋所有點
故\(|C_e|\leq |V|-|M|\)
2. 因為 \(C_e\) 上若有環則至少可以拔掉一條邊,
故 \(C_e\) 為森林,連通塊數量為 \(|V|-|C_e|\)
則從每個連通塊選一條邊會形成邊獨立集
因為邊獨立集不會超過最大邊獨立集
所以連通塊數量必須小於最大邊獨立集(最大匹配)
故 \(|V|-|C_e|\leq |M|\)
由 1. ,2. 得證 \(|C_e|=|V|-|M|\)
const int N=1e3+7;
vector<int> v[N];//X的鄰點
int match[N]; // Y所匹配的節點
bool vis[N]; //在這次尋找中是否被走過
int dfs(int x){
for(int y:v[x]){
if(vis[y]) continue;
vis[y]=true;
if(match[y]==-1||dfs(match[y])){
match[y]=x;
return true;
}
}
return false;
}
int bipartiteMatching(int n){
fill(match,match+n+1,-1);
int ans=0;
rep(i,1,n){
fill(vis,vis+n+1,0);
if(dfs(i)) ans++;
}
return ans;
}
vector<int> v2[2*N];
int tag[2*N];
vector<pii> e;
void paint(int x){
tag[x]=true;
for(int i:v2[x]){
if(!tag[i]){
paint(i);
}
}
}
void getce(int n){
int ans=bipartiteMatching(n);
cout<<ans<<"\n";
for(pii p2:e){
if(match[p2.S]!=p2.F){
v2[p2.S+n].eb(p2.F);
}
else v2[p2.F].eb(p2.S+n);
}
rep(i,1,n){
if(match[i]==-1){
paint(i+n);
}
}
rep(i,1,n){
if(tag[i]) cout<<1<<" "<<i<<"\n";
}
rep(i,n+1,2*n){
if(!tag[i]) cout<<2<<" "<<i-n<<"\n";
}
}有一個棋盤,然後接下來會有 \(m\) 個事件發生,第 \(i\)個事件會在時間 \(t_i\) 發生在 (\(x_i\),\(y_i\)),你可以派一些人去處理這些事件。一個人被派出去的時候,他一開始可以在任何位置,接下來他移動到別的地方花的時間等於曼哈頓距離。求你至少要派幾個人。
\( m \leq 1000 \)
對事件建點, \(i_{out},i_{in}\)
若從 \(i\) 事件結束後可以接著做 \(j\) 事件
則建邊 \(i_{out} \rightarrow j_{in}\)
等價於要用最少的路徑去使每個節點都被走訪
可以先假設答案是 \(m\)
那之後會發現如果有一個匹配 \(i,j)\) 則 答案會 -1。
故最佳解即為 \(|V|-|M|\)