Caiwen的博客

图论(新)

2025-03-01 12:58:27

欧拉路

欧拉路 一口气经过所有的点和边,且所有的边只经过一次

欧拉回路 能形成回路的欧拉路

判定

无向图

  • 当且仅当无向图中所有点的度数均为偶数时才有欧拉回路
  • 当且仅当无向图中有欧拉回路,或者有且仅有两个度数为奇数的点时才有欧拉路。对于后者,度数为奇数的点时起点或者终点
  • 需注意图是否联通

有向图

如果我们令一个点的出度记为 1,入度记为 -1,这个点所有的出度和入度相加,就是它的度数

  • 当且仅当有向图中所有的点度数为 0 时才有欧拉回路
  • 当且仅当有向图中有欧拉回路,或者有且仅有一个度数为 1 的点和一个度数为 -1 的点,其他所有的点度数均为 0。对于后者,度数为 1 的是起点,度数为 -1 的是终点
  • 需注意图是否联通

打印方案

递归遍历

直接 dfs 整个图,注意所有的边只能走一次,然后在回溯时打印答案。有向图和无向图都一样

例题 UVA10054 无向图求欧拉回路,打印方案

cpp
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
#include<bits/stdc++.h> #define int long long #define ull unsigned long long #define ls(k) (k)<<1 #define rs(k) (k)<<1|1 #define debug(x) cout<<#x<<"="<<x<<endl using namespace std; const int inf=0x3f3f3f3f3f3f3f3f; const int mod=0; typedef pair<int,int> pii; int d[55],g[55][55]; void dfs(int u){ for(int v=1;v<=50;v++){ if(g[u][v]){ g[u][v]--,g[v][u]--; dfs(v); cout<<v<<" "<<u<<endl; // 回溯的时候 } } } inline void subtask(){ int n;cin>>n; while(n--){ int u,v;cin>>u>>v; d[u]++,d[v]++; g[u][v]++,g[v][u]++; } for(int i=1;i<=50;i++) if(d[i]%2) return cout<<"some beads may be lost"<<endl,void(); for(int i=1;i<=50;i++) if(d[i]) return dfs(i),void(); } signed main(){ ios::sync_with_stdio(false); int t;cin>>t; for(int i=1;i<=t;i++){ cout<<"Case #"<<i<<endl; subtask(); memset(d,0,sizeof(d)); memset(g,0,sizeof(g)); cout<<endl; } return 0; }

模拟栈

如果递归深度过大的话,有可能会爆栈。此时我们考虑模拟递归时的栈来解决。基本思想是先从一个点一直往下dfs,dfs不动了,就把最后一个数字计入答案,然后再从第二个数字开始一直往下dfs

例题 POJ1780 有向图求欧拉路,打印方案,按字典序

题目大概意思是给定 nn 然后让我们生成一个字符串,使得字符串从头开始每看 nn 个数都是互不相同的 nn 位数字,且覆盖所有的 nn 位数字(含前导 0)

我们考虑现在已经有 n1n-1 位数字,然后填入下一位数字,此时就构成了一个 nn 位数字,但同时后面的 n1n-1 位数字又变成了新的”状态“,等待填入下一位数字

因此我们可以把所有的 n1n-1 位数字视为一个状态,通过连有向边,即填数,来转移到另一个状态。每个状态向外连边只能经过一次,且需要把所有的边都走一遍,于是就转化为了欧拉路问题

cpp
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
#include<iostream> #include<algorithm> #include<stack> using namespace std; #include <cassert> int nxt[1000006],n,cnt; stack<int> s,ans; void dfs(int x){// 一直走下去 while(nxt[x]<10){ int to=(x*10+nxt[x])%cnt; s.push(to); nxt[x]++; x=to; } } int main(){ ios::sync_with_stdio(false); while(cin>>n && n){ if(!n) break; if(n==1){ cout<<"0123456789"<<endl; continue; } cnt=1; for(int i=1;i<=n-1;i++) cnt*=10; for(int i=0;i<cnt;i++) nxt[i]=0; for(int i=1;i<=n-1;i++) cout<<0; dfs(0); while(!s.empty()){// 走不动了,说明该回溯到上一个点,然后再向另一个方向一直走 ans.push(s.top()%10);s.pop(); if(!s.empty()) dfs(s.top()); } int tot=n-1; while(!ans.empty()) cout<<ans.top(),ans.pop(),tot++; cout<<endl; } return 0; }

按字典序

先把点连出的所有边按照字典序排序,然后再求欧拉路,此时得到的方案其实就是按字典序的。但是由于我们是在回溯时记录答案,所以得到的方案其实是反的。所以我们还需要再把回溯时的要记录的点先放到一个栈里,然后最后再从栈中取出(相当于又反了一遍)

例题 P7771 有向图求欧拉路板子题,按字典序输出方案

cpp
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
#include<bits/stdc++.h> #define _ 200005 using namespace std; inline int read(){ int res=0,ch=getchar(),f=1; while(!isdigit(ch) and ch!=EOF){ if(ch=='-') f=-1; ch=getchar(); } while(isdigit(ch)){ res=(res<<3)+(res<<1)+(ch-'0'); ch=getchar(); } return res*f; } vector<int> ve[_]; int bl[_],n,m,in[_],out[_]; stack<int> st; void dfs(int x){ for(int i=bl[x];i<ve[x].size();i=bl[x]){ bl[x]++; dfs(ve[x][i]); } st.push(x); } int main(){ n=read(),m=read(); for(int i=1;i<=m;i++){ int u=read(),v=read(); in[v]++,out[u]++; ve[u].push_back(v); } for(int i=1;i<=n;i++) sort(ve[i].begin(),ve[i].end()); int flag=0,cnt1=0,cnt2=0,s=0; for(int i=1;i<=n;i++){ if(in[i]!=out[i]) flag=true; if(in[i]-out[i]==1) cnt1++; if(out[i]-in[i]==1) s=i,cnt2++; } if(flag&&!(cnt1==cnt2&&cnt1==1)) return cout<<"No",0; if(flag) dfs(s); else dfs(1); while(!st.empty()) cout<<st.top()<<' ',st.pop(); return 0; }

网络流

最大流

弧优化的dinic算法

cpp
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
int dis[_],rad[_],s,t; bool bfs(){ memset(dis,0,sizeof(dis));dis[s]=1; queue<int> q;q.push(s); while(!q.empty()){ int now=q.front();q.pop(); rad[now]=head[now]; for(int i=head[now];i;i=edge[i].nxt){ int to=edge[i].to; if(!dis[to]&&edge[i].w) dis[to]=dis[now]+1,q.push(to); } } return dis[t]!=0; } int dfs(int now,int rem){ if(now==t) return rem; int tmp=rem; for(int i=rad[now];i;i=edge[i].nxt){ int to=edge[i].to;rad[now]=i; if(dis[to]==dis[now]+1&&edge[i].w){ int k=min(edge[i].w,tmp); int d=dfs(to,k); edge[i].w-=d,edge[i^1].w+=d; tmp-=d; if(!tmp) break; } } return rem-tmp; }

DAG最小路径覆盖

不相交路径

image-20250306174049255

如图所示,一种不相交的最小路径覆盖为 1->3->4、2、5

一个点也可以视为一个长度为 00 的路径

我们可以先建立一个二分图,左边是 1n1\sim n ,右边也是 1n1\sim n。如果 DAG 上存在一个有向边 (u,v)(u,v) 的话,那么就把二分图中左边的 uu 和右边的 vv 连边。最后有

最小不相交路径覆盖=总点数最大匹配\text{最小不相交路径覆盖}=\text{总点数}-\text{最大匹配}

可以理解为,我先让每个点被自己覆盖。然后如果我在二分图上建立了一个匹配了,那么就相当于延长了一个路径,也就是少花费了一个点

可相交路径

还是如上图,一种可相交的最小路径覆盖为 1->3->4、2->3->5

我们先用 floyd 在图上搞传递闭包,然后如果 uu 能到达 vv ,就在把二分图左边的 uu 与右边的 vv 连边。最后答案还是按上式计算

可以理解为如果 uu 能到达 vv ,那么我们直接给连过去,先把中间的点省略,因为中间的点可能会被其他路径使用。如果后面的路径没使用到这个中间点的话,那么根据最大匹配的算法,一定会调整为选择使用中间的点,因为这样才能得到最大匹配

题目

P2472 [SCOI2007] 蜥蜴

https://www.luogu.com.cn/problem/P2472

题目描述

在一个 rrcc 列的网格地图中有一些高度不同的石柱,第 iijj 列的石柱高度为 hi,jh_{i,j}

一些石柱上站着一些蜥蜴,你的任务是让尽量多的蜥蜴逃到边界外。

每行每列中相邻石柱的距离为 11,蜥蜴的跳跃距离是 dd,即蜥蜴可以跳到平面距离不超过 dd 的任何一个石柱上。

石柱都不稳定,每次当蜥蜴跳跃时,所离开的石柱高度减 11(如果仍然落在地图内部,则到达的石柱高度不变)。

如果该石柱原来高度为 11,则蜥蜴离开后消失,以后其他蜥蜴不能落脚。

任何时刻不能有两只蜥蜴在同一个石柱上。

数据范围

对于 100%100\% 的数据满足:1r,c201\le r,c\le201d41\le d\le 41h31\le h\le 3

笔记

题目总体来说有两个限制,一个是跳完之后高度就减一,减到 0 后就不能再跳到该位置了。另一个是同一时间只能有一个蜥蜴在一个柱子上

对于后者,其实可以忽略。如果我们能够得到一个蜥蜴的跳出方案,而这个蜥蜴要跳到的柱子已经被占用了,那么我们可以一直等待柱子解除占用,毕竟题目又没要求时间

于是就只需要考虑前面这个限制了。其实就是限制经过一个点的次数。对于这种要求,我们有如下建模方案:将点拆成入点和出点,然后入点到出点设置流量为限制次数的边

image-20250306155451629

cpp
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
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
#include<bits/stdc++.h> #define int long long #define ull unsigned long long #define ls(k) (k)<<1 #define rs(k) (k)<<1|1 #define debug(x) cout<<#x<<"="<<x<<endl using namespace std; const int inf=0x3f3f3f3f3f3f3f3f; const int mod=0; typedef pair<int,int> pii; int n,m,d,g[22][22],s,t,has[22][22],siz=1,head[900],dis[900],rad[900]; inline int get(int x,int y,int t){ int res=(x-1)*m+y; return t==0? res:n*m+res; } inline int dis2(int x1,int y1,int x2,int y2){return (x1-x2)*(x1-x2)+(y1-y2)*(y1-y2);} struct Edge{int to,nxt,w;} edge[320004]; inline void _add(int u,int v,int w){edge[++siz].nxt=head[u],edge[siz].to=v,edge[siz].w=w,head[u]=siz;} inline void add(int u,int v,int w){_add(u,v,w),_add(v,u,0);} bool bfs(){ memset(dis,0,sizeof(dis));dis[s]=1; queue<int> q;q.push(s); while(!q.empty()){ int now=q.front();q.pop(); rad[now]=head[now]; for(int i=head[now];i;i=edge[i].nxt){ int to=edge[i].to; if(!dis[to]&&edge[i].w) dis[to]=dis[now]+1,q.push(to); } } return dis[t]!=0; } int dfs(int now,int rem){ if(now==t) return rem; int tmp=rem; for(int i=rad[now];i;i=edge[i].nxt){ int to=edge[i].to;rad[now]=i; if(dis[to]==dis[now]+1&&edge[i].w){ int k=min(edge[i].w,tmp); int d=dfs(to,k); edge[i].w-=d,edge[i^1].w+=d; tmp-=d; if(!tmp) break; } } return rem-tmp; } inline void subtask(){ cin>>n>>m>>d; int cnt=0; s=0,t=2*n*m+1; for(int i=1;i<=n;i++){ string str;cin>>str; for(int j=1;j<=m;j++) g[i][j]=str[j-1]-'0'; } for(int i=1;i<=n;i++){ string str;cin>>str; for(int j=1;j<=m;j++) has[i][j]=str[j-1]=='.'?0:1; } for(int i=1;i<=n;i++){ for(int j=1;j<=m;j++){ if(!g[i][j]) continue; if(has[i][j]) add(s,get(i,j,0),1),cnt++; add(get(i,j,0),get(i,j,1),g[i][j]); if(min(i,min(j,min(n-i+1,m-j+1)))<=d) add(get(i,j,1),t,inf); for(int k=max(1ll,i-d);k<=min(i+d,n);k++){ for(int l=max(1ll,j-d);l<=min(j+d,m);l++){ if(k==i&&l==j) continue; if(!g[k][l]) continue; if(dis2(i,j,k,l)<=d*d) add(get(i,j,1),get(k,l,0),inf); } } } } int ans=0; while(bfs()) ans+=dfs(s,inf); cout<<cnt-ans; } signed main(){ ios::sync_with_stdio(false); int t=1;//cin>>t; while(t--) subtask(); return 0; }

P2754 [CTSC1999] 家园 / 星际转移问题

https://www.luogu.com.cn/problem/P2754

题目描述

有 地球,月球,还有额外的 nn 个星球,然后有 mm 个宇宙飞船,可以分别以自己的某个顺序循环在几个星球之间穿梭,每次穿梭消耗一个单位的时间。每个飞船都有自己的容量限制

现在问至少需要多少时间才能把 kk 个人从 地球 运往 月球

数据范围

  • 1n131 \leq n \leq 13
  • 1m201 \leq m \leq 20
  • 1k501 \leq k \leq 50
  • 1rin+21 \leq r_i \leq n + 2
  • 1Si,jn-1 \leq S_{i, j}\leq n

笔记

先用并查集判断是否有解

然后题目中这个”时间“有点不好搞。这里又引出一个建模方法:根据时间分层,具体如图:

image-20250306160443125

然后我们枚举时间,建图跑最大流,直到得到的流量等于 kk 即可得到答案

注意理论上这种问题应该是二分答案的,但我们考虑直接枚举,因为如果有解,那么最大耗时不会超过 kn2kn^2 ,所以枚举可以。而且当我们枚举完一个答案再往下枚举的时候,我们可以直接在现有的图上补充点和边,而不用再重新建立一个完整的图,这是二分做不到的。而且我们跑完最大流后,补充点和边,直接再基于上一次的残留网络继续跑最大流就可以了。所以直接枚举的效率并不会太低

cpp
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
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
#include<bits/stdc++.h> #define ull unsigned long long #define ls(k) (k)<<1 #define rs(k) (k)<<1|1 #define debug(x) cout<<#x<<"="<<x<<endl using namespace std; const int inf=0x3f3f3f3f3f3f3f3f; const int mod=0; typedef pair<int,int> pii; #define _ 1000006 struct Edge{int nxt,to,w;} edge[_<<1]; int head[_],siz=1,opa[_],fa[_],las[_]; inline int find(int x){while(x!=fa[x]) x=fa[x]=fa[fa[x]];return x;} inline void _add(int u,int v,int w){edge[++siz].nxt=head[u],edge[siz].to=v,edge[siz].w=w,head[u]=siz;} inline void add(int u,int v,int w){_add(u,v,w),_add(v,u,0);} int dis[200005],rad[_],s,t; bool bfs(){ memset(dis,0,sizeof(dis));dis[s]=1; queue<int> q;q.push(s); while(!q.empty()){ int now=q.front();q.pop(); rad[now]=head[now]; for(int i=head[now];i;i=edge[i].nxt){ int to=edge[i].to; if(!dis[to]&&edge[i].w) dis[to]=dis[now]+1,q.push(to); } } return dis[t]!=0; } int dfs(int now,int rem){ if(now==t) return rem; int tmp=rem; for(int i=rad[now];i;i=edge[i].nxt){ int to=edge[i].to;rad[now]=i; if(dis[to]==dis[now]+1&&edge[i].w){ int k=min(edge[i].w,tmp); int d=dfs(to,k); edge[i].w-=d,edge[i^1].w+=d; tmp-=d; if(!tmp) break; } } return rem-tmp; } vector<int> to[_]; inline void subtask(){ int n,m,k;cin>>n>>m>>k; for(int i=0;i<=n+1;i++) fa[i]=i; for(int i=1;i<=m;i++){ cin>>opa[i]; int r;cin>>r; for(int j=1,x;j<=r;j++){ cin>>x; if(x==-1) x=1; else if(x!=0) x+=1; to[i].push_back(x); } for(int j=0;j<to[i].size();j++){ int u=to[i][j],v=to[i][(j+1)%to[i].size()]; u=find(u),v=find(v); if(u!=v) fa[u]=v; } } if(find(1)!=find(0)) return cout<<0,void(); s=0; for(int i=0;i<=n+1;i++) las[i]=i+1; int ans=0,base=n+3; add(s,las[0],k); int ok=0; while(true){ ans++; for(int i=0;i<=n+1;i++) add(las[i],base+i,inf); for(int i=1;i<=m;i++){ int u=to[i][(ans-1)%to[i].size()]; int v=to[i][ans%to[i].size()]; add(las[u],base+v,opa[i]); } for(int i=0;i<=n+1;i++) las[i]=base+i; t=base+1; base=base+n+1+1; while(bfs()) ok+=dfs(s,inf); if(ok==k) return cout<<ans<<endl,void(); } } signed main(){ ios::sync_with_stdio(false); int t=1;//cin>>t; while(t--) subtask(); return 0; }

P2765 魔术球问题

https://www.luogu.com.cn/problem/P2765

题目描述

假设有 nn 根柱子,现要按下述规则在这 nn 根柱子中依次放入编号为 112233,...的球“

  1. 每次只能在某根柱子的最上面放球。

  2. 同一根柱子中,任何 22 个相邻球的编号之和为完全平方数。

试设计一个算法,计算出在 nn 根柱子上最多能放多少个球。例如,在 44 根柱子上最多可放 1111 个球。

对于给定的 nn,计算在 nn 根柱子上最多能放多少个球。

数据范围

对于 100%100\% 的数据,保证 1n551 \leq n \leq 55

笔记

如果两个数能构成完全平方数,那么我们就把这两个数字连边,并规定一定是大的数字连向小的数字(其实小的数字往大的数字连边也可以,总之就是要保证一个顺序,但是大连小的话在后面补充建边时更有优势)

如果我们要判断答案是否为 xx,那么就相当于现在我们有一个含有 xx 个点的 DAG,并去判断这个 DAG 的最小不相交路径覆盖是否为 nn

我们沿用上道题的做法,枚举答案。考虑答案增加一,那么我们就相当于二分图两边多了个点,然后把这个点连向相加可以为完全平方数的数,然后在仅对新增的这个点跑最大匹配就可以了

设当前点数为 ansans,当前最大匹配为 okok,那么如果 ansok=nans-ok=n 说明 ansans 即为答案。ansans 增加 11okok 可能增加 11 也可能不增加,所以 ansokans-ok 是单调不减的,所以直接枚举的正确性是可以保证的

cpp
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
#include<bits/stdc++.h> #define int long long #define ull unsigned long long #define ls(k) (k)<<1 #define rs(k) (k)<<1|1 #define debug(x) cout<<#x<<"="<<x<<endl using namespace std; const int inf=0x3f3f3f3f3f3f3f3f; const int mod=0; typedef pair<int,int> pii; vector<int> ve[1000006]; int match[1000006],t[1000006],nxt[1000006]; bool dfs(int x,int tag){ if(t[x]==tag) return false; t[x]=tag; for(int to:ve[x]) if(!match[to]||dfs(match[to],tag)) return match[to]=x,true; return false; } inline void subtask(){ int n;cin>>n; int ans=0,ok=0,tag=0; while(true){ ans++; for(int i=2;i*i<2*ans;i++){ if(i*i<=ans) continue; ve[ans].push_back(i*i-ans); } if(dfs(ans,++tag)) ok++; if(ans-ok>n) break; } ans--; cout<<ans<<endl; for(int i=1;i<=ans;i++) nxt[match[i]]=i; for(int i=1;i<=ans;i++){ if(match[i]) continue; int now=i;cout<<now<<' '; while(nxt[now]) cout<<nxt[now]<<' ',now=nxt[now]; cout<<endl; } } signed main(){ ios::sync_with_stdio(false); int t=1;//cin>>t; while(t--) subtask(); return 0; }

P2766 最长不下降子序列问题

https://www.luogu.com.cn/problem/P2766

题目描述

给定正整数序列 x1,xnx_1 \ldots, x_n

  1. 计算其最长不下降子序列的长度 ss
  2. 如果每个元素只允许使用一次,计算从给定的序列中最多可取出多少个长度为 ss 的不下降子序列。
  3. 如果允许在取出的序列中多次使用 x1x_1xnx_n(其他元素仍然只允许使用一次),则从给定序列中最多可取出多少个不同的长度为 ss 的不下降子序列。

a1,a2,,asa_1, a_2, \ldots, a_s 为构造 SS 时所使用的下标,b1,b2,,bsb_1, b_2, \ldots, b_s 为构造 TT 时所使用的下标。且 i[1,s1]\forall i \in [1,s-1],都有 ai<ai+1a_i \lt a_{i+1}bi<bi+1b_i \lt b_{i+1}。则 SSTT 不同,当且仅当 i[1,s]\exists i \in [1,s],使得 aibia_i \neq b_i

数据范围

1n5001 \le n\le 500

笔记

直接使用 dp 跑最长不下降子序列。不用 O(nlogn)O(n\log n) 做法的原因是 dp 的话我们就知道以每个数结尾能够形成的最长不下降子序列的长度是多少,这有助于我们计算方案

如果所有的数都可以重复取,那么我们可以用组合数学直接计算出来方案个数。但现在一个数只能取一次,即使考虑 dp 计算的话,后效性也太大

于是此时网络流又派上用场了,我们按 dp 转移阶段分层,然后如果一个点能够转移到另一个点,就连边。直接这么连的话有些点会被取多次。于是又是套路的拆点,限制经过点的流量

对于第三问,把边权改为 inf 后继续增广就可以了

cpp
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
69
70
71
72
73
74
75
76
77
78
79
#include<bits/stdc++.h> #define int long long #define ull unsigned long long #define ls(k) (k)<<1 #define rs(k) (k)<<1|1 #define debug(x) cout<<#x<<"="<<x<<endl using namespace std; const int inf=0x3f3f3f3f3f3f3f3f; const int mod=0; typedef pair<int,int> pii; #define _ 505 int a[_],dp[_],siz=1,head[_],s,t; struct Edge{int nxt,to,w;} edge[1000006]; inline void _add(int u,int v,int w){edge[++siz].nxt=head[u],edge[siz].to=v,edge[siz].w=w,head[u]=siz;} inline void add(int u,int v,int w){_add(u,v,w),_add(v,u,0);} int dis[_],rad[_]; bool bfs(){ memset(dis,0,sizeof(dis));dis[s]=1; queue<int> q;q.push(s); while(!q.empty()){ int now=q.front();q.pop(); rad[now]=head[now]; for(int i=head[now];i;i=edge[i].nxt){ int to=edge[i].to; if(!dis[to]&&edge[i].w) dis[to]=dis[now]+1,q.push(to); } } return dis[t]!=0; } int dfs(int now,int rem){ if(now==t) return rem; int tmp=rem; for(int i=rad[now];i;i=edge[i].nxt){ int to=edge[i].to;rad[now]=i; if(dis[to]==dis[now]+1&&edge[i].w){ int k=min(edge[i].w,tmp); int d=dfs(to,k); edge[i].w-=d,edge[i^1].w+=d; tmp-=d; if(!tmp) break; } } return rem-tmp; } inline void subtask(){ int n;cin>>n; for(int i=1;i<=n;i++) cin>>a[i]; for(int i=1;i<=n;i++) dp[i]=1; int maxx=1; for(int i=2;i<=n;i++) for(int j=1;j<i;j++) if(a[j]<=a[i]) dp[i]=max(dp[i],dp[j]+1),maxx=max(maxx,dp[i]); cout<<maxx<<endl; if(maxx==1) return cout<<n<<endl<<n,void(); s=0,t=2*n+1; int e1,en; for(int i=1;i<=n;i++){ add(2*i-1,2*i,1); if(i==1) e1=siz-1; if(i==n) en=siz-1; if(dp[i]==1) add(s,2*i-1,inf); if(dp[i]==maxx) add(2*i,t,inf); for(int j=1;j<i;j++){ if(!(a[j]<=a[i]&&dp[j]==dp[i]-1)) continue; add(2*j,2*i-1,1); } } int ans=0; while(bfs()) ans+=dfs(s,inf); cout<<ans<<endl; edge[e1].w=inf; edge[en].w=inf; while(bfs()) ans+=dfs(s,inf); cout<<ans; } signed main(){ ios::sync_with_stdio(false); int t=1;//cin>>t; while(t--) subtask(); return 0; }
最后更新于:2025-04-03 14:06:28

Caiwen
本文作者
一只蒟蒻,爱好编程和算法