0%

一条无向边需要四倍(也可以两倍)空间,有向边需要两倍空间 若有总流量上限,可以添加边0~1和n~n+1,cap为上限,cost为0,s=0,t=n+1 若求特定流量下的最小费用,需修改mincost_maxflow,并判断是否能达到该流量


spfa版 时间复杂度\(O(Fnm)\) (一看就不怎么靠谱)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
using namespace std;
int head[5005],vet[100005],nxt[100005],cap[100005],cost[100005],num;
long long dist[5005];
int prev[5005];
bool inque[5005];
int n,m,s,t;
int flow,Cost;
void addedge(int x,int y,int ca,int co) {
num++;vet[num]=y;cap[num]=ca;cost[num]=co;nxt[num]=head[x];head[x]=num;
num++;vet[num]=x;cap[num]=0;cost[num]=-co;nxt[num]=head[y];head[y]=num;
}
bool spfa(int s,int t) {
queue<int> que;
for (int i=1;i<=n;i++) dist[i]=1e12,inque[i]=false,prev[i]=-1;
que.push(s);dist[s]=0;inque[s]=true;
while (!que.empty()) {
int u=que.front();que.pop();
inque[u]=false;
for (int e=head[u];e!=-1;e=nxt[e]) {
int v=vet[e];
if(cap[e]>0 && dist[v]>dist[u]+cost[e]) {
dist[v]=dist[u]+cost[e];
prev[v]=e;
if (!inque[v]) {
inque[v]=true;
que.push(v);
}
}
}
}
if (dist[t]==1e12) return false;
else return true;
}
void mincost_maxflow(int s,int t) {
while (spfa(s,t)) {
int ff=1e9;
for (int i=t;i!=s;i=vet[prev[i]^1]) ff=min(ff,cap[prev[i]]);
Cost+=ff*dist[t];
flow+=ff;
for (int i=t;i!=s;i=vet[prev[i]^1]) {
cap[prev[i]]-=ff;
cap[prev[i]^1]+=ff;
}
}
}
int main() {
scanf("%d%d%d%d",&n,&m,&s,&t);
memset(head,-1,sizeof(head));num=-1;
for (int i=1;i<=m;i++) {
int x,y,w,c;
scanf("%d%d%d%d",&x,&y,&w,&c);
addedge(x,y,w,c);
}
mincost_maxflow(s,t);
printf("%d %d\n",flow,Cost);
return 0;
}


dijkstra版 算法原理

如果一开始就有负权边,需要先跑一遍spfa

1.不带堆优化,时间复杂度\(O(Fn^2)\)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
using namespace std;
int head[5005],vet[100005],nxt[100005],cap[100005],cost[100005],num;
long long dist[5005],h[5005];
int prev[5005];
bool used[5005];
int n,m,s,t;
int flow,Cost;
void addedge(int x,int y,int ca,int co) {
num++;vet[num]=y;cap[num]=ca;cost[num]=co;nxt[num]=head[x];head[x]=num;
num++;vet[num]=x;cap[num]=0;cost[num]=-co;nxt[num]=head[y];head[y]=num;
}
bool dijkstra(int s,int t) {
for (int i=1;i<=n;i++) dist[i]=1e12,used[i]=false,prev[i]=-1;
dist[s]=0;
for (int i=1;i<=n;i++) {
int mindist=1e12,p=0;
for (int j=1;j<=n;j++)
if (!used[j] && mindist>dist[j]) {
mindist=dist[j];
p=j;
}
used[p]=true;
for (int e=head[p];e!=-1;e=nxt[e]) {
int v=vet[e];
if (cap[e]>0 && dist[v]>dist[p]+cost[e]+h[p]-h[v]) {
dist[v]=dist[p]+cost[e]+h[p]-h[v];
prev[v]=e;
}
}
}
if (dist[t]==1e12) return false;
else return true;
}
void mincost_maxflow(int s,int t) {
memset(h,0,sizeof(h));
while (dijkstra(s,t)) {
for (int i=1;i<=n;i++) h[i]+=dist[i];
int ff=1e9;
for (int i=t;i!=s;i=vet[prev[i]^1]) ff=min(ff,cap[prev[i]]);
Cost+=ff*h[t];
flow+=ff;
for (int i=t;i!=s;i=vet[prev[i]^1]) {
cap[prev[i]]-=ff;
cap[prev[i]^1]+=ff;
}
}
}
int main() {
scanf("%d%d%d%d",&n,&m,&s,&t);
memset(head,-1,sizeof(head));num=-1;
for (int i=1;i<=m;i++) {
int x,y,w,c;
scanf("%d%d%d%d",&x,&y,&w,&c);
addedge(x,y,w,c);
}
mincost_maxflow(s,t);
printf("%d %d\n",flow,Cost);
return 0;
}

2.使用堆优化,时间复杂度\(O(Fnlogm)\)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
using namespace std;
typedef long long LL;
struct node {
LL dis,po;
bool operator<(const node &res) const {
return dis>res.dis;
}
};
int head[5005],vet[100005],nxt[100005],cap[100005],cost[100005],num;
LL dist[5005],h[5005];
int prev[5005];
bool used[5005];
int n,m,s,t;
int flow,Cost;
void addedge(int x,int y,int ca,int co) {
num++;vet[num]=y;cap[num]=ca;cost[num]=co;nxt[num]=head[x];head[x]=num;
num++;vet[num]=x;cap[num]=0;cost[num]=-co;nxt[num]=head[y];head[y]=num;
}
bool dijkstra(int s,int t) {
priority_queue<node> que;
for (int i=1;i<=n;i++) dist[i]=1e12,used[i]=false,prev[i]=-1;
que.push((node){0,s});dist[s]=0;
while (!que.empty()) {
int u=que.top().po;que.pop();
if (used[u]) continue;
used[u]=true;
for (int e=head[u];e!=-1;e=nxt[e]) {
int v=vet[e];
if (cap[e]>0 && dist[v]>dist[u]+cost[e]+h[u]-h[v]) {
dist[v]=dist[u]+cost[e]+h[u]-h[v];
prev[v]=e;
que.push((node){dist[v],v});
}
}
}
if (dist[t]==1e12) return false;
else return true;
}
void mincost_maxflow(int s,int t) {
memset(h,0,sizeof(h));
while (dijkstra(s,t)) {
for (int i=1;i<=n;i++) h[i]+=dist[i];
int ff=1e9;
for (int i=t;i!=s;i=vet[prev[i]^1]) ff=min(ff,cap[prev[i]]);
Cost+=ff*h[t];
flow+=ff;
for (int i=t;i!=s;i=vet[prev[i]^1]) {
cap[prev[i]]-=ff;
cap[prev[i]^1]+=ff;
}
}
}
int main() {
scanf("%d%d%d%d",&n,&m,&s,&t);
memset(head,-1,sizeof(head));num=-1;
for (int i=1;i<=m;i++) {
int x,y,w,c;
scanf("%d%d%d%d",&x,&y,&w,&c);
addedge(x,y,w,c);
}
mincost_maxflow(s,t);
printf("%d %d\n",flow,Cost);
return 0;
}

复杂度最坏为\(O(nm)\)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
void spfa(int s) {
queue<int> que;
memset(inque,false,sizeof(inque));
for (int i=1;i<=n;i++) dist[i]=1e9;
que.push(s);dist[s]=0;inque[s]=true;
while (!que.empty()) {
int u=que.front();que.pop();
inque[u]=false;
for (int e=head[u];e!=-1;e=nxt[e]) {
int v=vet[e];
if(dist[v]>dist[u]+cost[e]) {
dist[v]=dist[u]+cost[e];
if (!inque[v]) {
inque[v]=true;
que.push(v);
}
}
}
}
}

时间复杂度\(\ O(EV^2)\)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
using namespace std;
int head[205],vet[405],nxt[405],cap[405],num;
int level[205],iter[205];
int n,m;
void addedge(int x,int y,int val) {
num++;vet[num]=y;cap[num]=val;nxt[num]=head[x];head[x]=num;
num++;vet[num]=x;cap[num]=0;nxt[num]=head[y];head[y]=num;
}
void bfs(int s) {
memset(level,-1,sizeof(level));
queue<int> que;
level[s]=0;
que.push(s);
while (!que.empty()) {
int v=que.front();que.pop();
for (int e=head[v];e!=-1;e=nxt[e]) {
int u=vet[e];
if (level[u]<0 && cap[e]>0) {
level[u]=level[v]+1;
que.push(u);
}
}
}
}
int dfs(int v,int t,int fl) {
if (v==t) return fl;
for (int &e=iter[v];e!=-1;e=nxt[e]) {//当前弧优化
int u=vet[e];
if (cap[e]>0 && level[v]<level[u]) {
int ff=dfs(u,t,min(fl,cap[e]));
if (ff>0) {
cap[e]-=ff;
cap[e^1]+=ff;
return ff;
}
}
}
return 0;
}
int maxflow(int s,int t) {
int flow=0;
for (;;) {
bfs(s);
if (level[t]<0) return flow;
for (int i=1;i<=n;i++) iter[i]=head[i];
int ff=0;
while (ff=dfs(s,t,1e9)) flow+=ff;
}
}
int main() {
while (scanf("%d%d",&m,&n)!=EOF) {
memset(head,-1,sizeof(head));num=-1;
for (int i=1;i<=m;i++) {
int x,y,c;
scanf("%d%d%d",&x,&y,&c);
addedge(x,y,c);
}
printf("%d\n",maxflow(1,n));
}
return 0;
}

问题描述

JIH的玩具厂设立以来,发展了一张销售关系网。这张网以玩具厂为总代理(根),构成一颗树。每个节点都代表一个客户,且每个节点都有重要度ai。JIH想将这些客户划成若干类别,当然同一类的客户重要度相差太大总是不妥。所以JIH决定先进行市场调研。JIH会选择两个客户X,从X向根走一共k个节点进行调查。调查的结果是这条路径上重要程度相差最大的两个客户的差值是多少。因为特殊需要,要求重要度大的客户必须在重要度小的客户后面(顺序为X到根,若序列为递减,则输出0,详情见样例)。

输入

第一行一个整数N 表示N个客户
第二行N个整数Ai 表示N个客户的重要程度(工厂是1)
第三行开始 共N-1行 每行2个整数 x,y 表示x的父亲是y
接着一行一个正整数Q,表示Q次调研
接着Q行,每行两个整数X,K。含义见题目表述。

输出

Q行,每行一个正整数,含义见题目描述。

样例数据

Sample Input:

1
2
3
4
5
6
7
8
9
10
11
6
5 6 1 7 5 2
2 1
3 1
4 2
5 2
6 3
3
4 3
6 2
6 3
Sample Output:
1
2
3
0
0
4

数据范围

30% 的数据中N,Q<=1000
100%的数据中N,Q<=200000 Ai<=1000000


这是我有史以来最想吐槽的一道题,题目想表达什么都不知道。
老师说题意是,从x向根走一共k个节点中选两个,大的在小的后面,求最大相差多少。
姑且接受。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
#include<iostream>
#include<cstdio>
using namespace std;
int mn[200005][18],mx[200005][18],h[200005][18],ans[200005][18];
int a[200005];
int head[200005],nxt[200005],vet[200005],num;
int n,q;
void addedge(int x,int y) {
num++;
vet[num]=y;
nxt[num]=head[x];
head[x]=num;
}
void dfs(int x,int fa) {
if (fa>0) {
int v=fa,tmp=0;
h[x][0]=fa;
mx[x][0]=max(a[x],a[fa]);
mn[x][0]=min(a[x],a[fa]);
if (a[x]<a[fa]) ans[x][0]=a[fa]-a[x];
while (v>0) {
h[x][tmp+1]=h[v][tmp];
mx[x][tmp+1]=max(mx[x][tmp],mx[v][tmp]);
mn[x][tmp+1]=min(mn[x][tmp],mn[v][tmp]);
ans[x][tmp+1]=max(ans[x][tmp],max(ans[v][tmp],mx[v][tmp]-mn[x][tmp]));
v=h[v][tmp];
tmp++;
}
}
for (int e=head[x];e!=0;e=nxt[e]) dfs(vet[e],x);
}
int main() {
freopen("tree.in","r",stdin);
freopen("tree.out","w",stdout);
cin >> n;
for (int i=1;i<=n;i++) scanf("%d",&a[i]);
for (int i=1;i<n;i++) {
int x,y;
scanf("%d%d",&x,&y);
addedge(y,x);
}
dfs(1,0);
cin >> q;
while (q--) {
int x,k;
scanf("%d%d",&x,&k);
k--;
int mnn=1e9,anss=0;
while (k>0) {
int tmp=0;
while ((k&(1<<tmp))==0) tmp++;
k^=(1<<tmp);
anss=max(anss,max(ans[x][tmp],mx[x][tmp]-mnn));
mnn=min(mnn,mn[x][tmp]);
x=h[x][tmp];
if (x==0) break;
}
printf("%d\n",anss);
}
return 0;
}

【问题描述】

LYK定义了一个数叫“立方数”,若一个数可以被写作是一个正整数的3次方,则这个数就是立方数,例如1,8,27就是最小的3个立方数。
LYK还定义了一个数叫“立方差数”,若一个数可以被写作是两个立方数的差,则这个数就是“立方差数”,例如7(8-1),26(27-1),19(27-8)都是立方差数。
现在给定一个数P,LYK想要知道这个数是不是立方差数。
当然你有可能随机输出一些莫名其妙的东西,因此LYK有T次询问~
这个问题可能太难了…… 因此LYK规定P是个质数!

【输入格式】

输入文件名为 cubicp.in
第一行一个数T,表示有T组数据。
接下来T行,每行一个数P。

【输出格式】

输出文件名为 cubicp.out
输出T行,对于每个数如果是立方差数,输出“YES”,否则输出“NO”。

【输入输出样例 1】

1
2
3
4
5
6
5
2
3
5
7
11
1
2
3
4
5
NO
NO
NO
YES
NO

【数据规模与约定】

对于30%的数据p<=100。
对于60%的数据p<=10^6。
对于100%的数据p<=10^12,T<=100。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include<iostream>
#include<cstdio>
#include<cmath>
using namespace std;
int main() {
freopen("cubicp.in","r",stdin);
freopen("cubicp.out","w",stdout);
int T;
cin >> T;
while (T--) {
long long P=0;
scanf("%lld",&P);
long double tmp=(sqrt((long double)12*P-3)-3)/6;
if (tmp==(int)tmp) printf("YES\n");
else printf("NO\n");
}
return 0;
}

定义:

1
2
3
int trie[sigma(len)][26],num;//注意,点是[0,25]
int fail[sigma(len)];//该节点的后缀节点编号
bool danger[sigma(len)];//该节点是否有字符串匹配结束

建Trie树:

1
2
3
4
5
6
7
8
9
void add(int x,int p) {
if (p>len) {
danger[x]=true;
return;
}
int tmp=str[p]-'a';
if (trie[x][tmp]==-1) trie[x][tmp]=++num;
add(trie[x][tmp],p+1);
}
或者
1
2
3
4
5
6
7
8
9
void add() {
int nowp=0;
for (int i=1;i<=len;i++) {
int tmp=str[i]-'a';
if (tree[nowp][tmp]==-1) tree[nowp][tmp]=++num;
nowp=tree[nowp][tmp];
}
danger[nowp]=true
}
建Trie图:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
void build_ac() {
fail[0]=0;//关于root需要特殊处理
for (int i=0;i<26;i++) {
int v=trie[0][i];
if (v!=-1) {
fail[v]=root;
que.push(v);
} else trie[0][i]=0;
}
while (!que.empty()) {//bfs序保证所有深度较浅的节点已经完成
int u=que.front();
que.pop();
for (int i=0;i<26;i++) {
int v=trie[u][i];
if (v==-1) {
trie[u][i]=trie[fail[u]][i];//儿子的等效节点,是该点的后缀节点的儿子
continue;
}
fail[v]=trie[fail[u]][i];//儿子的后缀,是该点的后缀节点的儿子
que.push(v);
danger[v]=danger[v]|danger[fail[v]];//如果其后缀节点也是危险节点,那么它也是危险节点
}
}
}

基本操作

1
2
3
4
5
6
7
8
9
10
11
12
13
//注意!!!很容易爆int
struct BIT {
LL c[MAXN];
inline int lowbit(int x) {return x&(-x);}
inline void update(int x,LL val) {
for (;x<=n;x+=lowbit(x)) c[x]+=val;
}
inline LL getsum(int x) {
LL sum=0;
for (;x>0;x-=lowbit(x)) sum+=c[x];
return sum;
}
};

c[i]表示a[i-lowbit(i)+1]~a[i]的和
c[i]维护的点数量为,i在二进制下最后一个1是右数第几位

1.单点修改,区间查询

1
2
3
4
for (int i=1;i<=n;i++) {
scanf("%d",&a[i]);
update(i,a[i]);
}
1
update(x,val);
1
LL ans=getsum(r)-getsum(l-1);

2.区间修改,单点查询

将d[i]+1以后做一遍前缀和,发现区间[i,n]都+1了
于是将区间修改转换为做前缀和的单点修改。
(也可以这么理解,设a[i]的改变值差分后得到d[i],前缀和会把中间项全部消掉,最后只留下这个点的改变值。)

1
2
update(l,val);
update(r+1,-val);
1
LL ans=a[x]+getsum(x);

3.区间修改,区间查询

还是那个数组d,d[i]在前缀和意义下是区间[i,n]的共同增量。
查询区间[l,r]的和,就是求sum[r]-sum[l-1],所以现在的问题是求sum[i]。

1
2
3
4
sum[i]=(a[1]+d[1])+(a[2]+d[1]+d[2])+...+(a[i]+d[1]+d[2]+...+d[i]) //a[i]为原始数组
=a[1]+a[2]+...+a[i]+d[1]*i+d[2]*(i-1)+d[3]*(i-2)+...+d[i]*1
=sigma(a[x])+sigma(d[x]*(i+1-x))
=sigma(a[x])+(i+1)*sigma(d[x])-sigma(d[x]*x)
其中 sigma(a[x])是可以预处理出来的,于是只需要维护d[x]与d[x]*x的前缀和(用两个树状数组)
参考资料

关键代码
d1维护d[x],d2维护d[x]*x,asum是ai前缀和

1
2
3
4
d1.update(l,val);
d1.update(r+1,-val);
d2.update(l,(LL)val*l);
d2.update(r+1,-(LL)val*(r+1));
1
2
3
LL sum1=asum[l-1]+l*d1.getsum(l-1)-d2.getsum(l-1);
LL sum2=asum[r]+(r+1)*d1.getsum(r)-d2.getsum(r);
printf("%lld\n",sum2-sum1);

二维树状数组(未完待续)

此处不妨令左下角为原点(0,0)

1.单点修改,区间求和

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
struct BIT_2D {
int c[MAXN][MAXM];
inline int lowbit(int x) {return x&(-x);}
inline void update(int xx,int yy,int val) {
for (int x=xx;x<=n;x+=lowbit(x))
for (int y=yy;y<=m;y+=lowbit(y))
c[x][y]+=val;
}
inline LL query(int xx,int yy) {
LL sum=0;
for (int x=xx;x>0;x-=lowbit(x))
for (int y=yy;y>0;y-=lowbit(y))
sum+=c[x][y];
return sum;
}
};

设修改点(x,y)
询问左下角(x1,y1)到右上角(x2,y2),满足x1<=x2,y1<=y2

1
update(x,y,val);
1
LL ans=getsum(x2,y2)-getsum(x1-1,y2)-getsum(x2,y1-1)+getsum(x1-1,y1-1);

2.区间修改,单点求和

1
这里写代码片

3.区间修改,区间求和

对于原矩阵A,用矩阵dij表示(i,j)−(n,m)的增量,那么子矩阵(1,1)−(x,y)的和为:

因此,用四个树状数组分别维护:dij,(i∗dij),(j∗dij),(i∗j∗dij)
最后,贴一个二维树状数组的模板。

1
咕咕咕

参考资料1
参考资料2

  1. 分析问题,模拟样例,确定起点数量、终点数量

  2. 枚举最后一步对原问题拆封出子问题(分治)

  3. 确定最优子结构(当前结构与前面的选择无关)

  4. 确定动态规划转移及边界

  5. 确定初值和终值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
//针对有向图
void dfs(int x) {
f[x]=true;
d[x]=low[x]=++Time;
sta.push(x);instack[x]=true;
for (int e=head[x];e!=0;e=next[e]) {
if (!f[vet[e]]) {
dfs(vet[e]);
low[x]=min(low[x],low[vet[e]]);
}
else if (instack[vet[e]]) low[x]=min(low[x],d[vet[e]]);
}
if (low[x]==d[x]) {
idnum++;
while (sta.top()!=x) {
instack[sta.top()]=false;
idc[sta.top()]=idnum;
sta.pop();
}
idc[x]=idnum;
instack[x]=false;
sta.pop();
}
}
int main() {
int n;
cin >> n;
while (!sta.empty()) sta.pop();

for (int i=1;i<=n;i++)
if (!f[i]) dfs(i);
for (int i=1;i<=n;i++)
for (int e=head[i];e!=0;e=next[e])//遍历每条边
if (idc[i]!=idc[vet[e]]) ind[idc[vet[e]]]++;

return 0;
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
//双联通分量针对无向图
void dfs(int x,int ef) {
f[x]=true;
low[x]=d[x]=++Time;
sta.push(x);
for (int e=head[x];e!=-1;e=nxt[e]) {
if (e==(ef^1)) continue;
if (!f[vet[e]]) {
dfs(vet[e],e);
low[x]=min(low[x],low[vet[e]]);
}
else low[x]=min(low[x],d[vet[e]]);
}
if (low[x]==d[x]) {
idnum++;
while (sta.top()!=x) {
idx[sta.top()]=idnum;
sta.pop();
}
idx[x]=idnum;
sta.pop();
}
}
int main() {
num=-1;
memset(head,-1,sizeof(head));
int n,m;
cin >> n >> m;
for (int i=1;i<=m;i++) {
int x,y;
scanf("%d%d",&x,&y);
addedge(x,y);
addedge(y,x);
}
for (int i=1;i<=n;i++)
if (!f[i]) dfs(i,-1);
for (int i=0;i<m;i++) {
int u=idx[vet[i<<1]],v=idx[vet[i<<1|1]];
if (u!=v) deg[u]++,deg[v]++;
}
return 0;
}

或者不用栈,dfs不经过桥,将每个点赋予新编号