1、1736: 飞行员配对方案问题
这一题基本上不用多说,就是一题二分图加方案输出;
我先试着用二分图求一发:
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<vector>
#include<algorithm>
#define MAX_V 5500
using namespace std;
int book[MAX_V],girl[MAX_V],color[MAX_V];
vector<int> G[MAX_V];
int K,N,M;
bool serch(int x){
int i,j;
for(i=0;i<G[x].size();i++){
if(book[G[x][i]]==0){
book[G[x][i]]=1;
if(girl[G[x][i]]==0||serch(girl[G[x][i]])){
girl[G[x][i]]=x;
///printf("___ %d\n", x);
return true;
}
}
}
return false;
}
int main(){
int i,j,k,l;
int x,y,ans;
scanf("%d %d",&M,&N);
ans=0;
N-=M;
fill(girl,girl+MAX_V,0);
for(i=0;i<MAX_V;i++)
G[i].clear();
while(1){
scanf("%d %d",&x,&y);
y-=M;
if(x==-1||y==-1)
break;
G[x].push_back(y);
}
for(i=1;i<=M;i++){
fill(book,book+MAX_V,0);
if(serch(i))
ans++;
}
printf("%d\n",ans);
for(int i=1;i<=N; ++i){
if(girl[i])
printf("%d %d\n", girl[i], i+M);
}
return 0;
}
接着用了网络流来了一发,设置一个超级源点S, 和一个超级汇点T,将外籍飞行员1~m连接上S,流量为1;英国飞行员m+1~n连接一条流量为1的边至T;其他按照题目输入给边连接:
#include<queue>
#include<stdio.h>
#include<string.h>
using namespace std;
const int maxn = 1005;
const int maxm = 1e6+7;
const int inf = 0x3f3f3f3f;
typedef long long LL;
int t, n, m;
struct Edge {
int to, w, next;
} edge[maxm];
int first[maxn], cur[maxn], sign, dist[maxn], pre[maxn];
void init() {
memset(first, -1, sizeof(first));
sign = 0;
}
void add_edge(int u,int v,int w) {
edge[sign].to = v, edge[sign].w = w;
edge[sign].next = first[u], first[u] = sign++;
edge[sign].to = u, edge[sign].w = 0;
edge[sign].next = first[v], first[v] = sign++;
}
bool bfs(int s,int t) {
memset(dist, -1, sizeof(dist));
queue<int>que;
que.push(s), dist[s] = 0;
while(!que.empty()) {
int now = que.front();
que.pop();
if(now==t) return 1;
for(int i = first[now]; ~i; i = edge[i].next) {
int to = edge[i].to, ww = edge[i].w;
if(dist[to] == -1 && ww > 0) {
dist[to] = dist[now] + 1;
que.push(to);
}
}
}
return 0;
}
int dfs(int s, int t, int max_flow) {
if(s == t) return max_flow;
for(int &i = cur[s]; ~i; i = edge[i].next) {
int to = edge[i].to, ww = edge[i].w;
if(dist[to] == dist[s] + 1 && ww > 0) {
int flow = dfs(to, t, min(max_flow, ww));
if(flow > 0) {
edge[i].w -= flow;
edge[i^1].w += flow;
return flow;
}
}
}
return 0;
}
int dinic(int s,int t) {
int ans = 0;
while(bfs(s, t)) {
for(int i = 1; i <= n; i ++ )
cur[i] = first[i];
ans += dfs(s, t, inf);
}
return ans;
}
int main() {
int u, v, w;
scanf("%d %d", &m, &n);
init();
int S=n+1, T=n+2;
for(int i=1; i<=m; ++i) add_edge(S, i, 1);
for(int i=m+1; i<=n; ++i) add_edge(i, T, 1);
while(1){
scanf("%d %d", &u, &v);
w=1;
if(u==-1&&v==-1)
break;
add_edge(u, v, w);
}
n+=2;
printf("%d\n", dinic(S, T));
for(int i=m+1; i<=n-2; ++i){
for(int j=first[i]; ~j; j=edge[j].next){
if(i!=T&&edge[j].to!=T&&edge[j].w)
printf("%d %d\n", min(i, edge[j].to), max(i, edge[j].to));
}
}
puts("");
return 0;
}
2、1740: 圆桌问题
emmmm!!!!
这题。。。也是一个基础的网络流问题;
显然每个单位只能在一张桌子坐一个人,也就是每个单位有且仅有1的流量指向桌子,只要知道了这个就简单了;
对每个单位建立一条到超级源点S的边,流量为每个单位的人数;
对每张桌子建立一条到超级汇点的边,流量为桌子容量;
桌子和单位按照刚刚那样建立边;
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
#define inf 0x3fffffff
using namespace std;
const int maxn=555, maxm=1e6+7;
int first[maxn],sign,cur[maxn];
int s,t,d[maxn];
int mp[maxn][maxn];
struct node{
int to,w,next;
}edge[maxm];
void init(){
memset(first,-1,sizeof(first));
sign=0;
}
void add_edge(int u,int v,int w){
edge[sign].to=v;
edge[sign].w=w;
edge[sign].next=first[u];
first[u]=sign++;
edge[sign].to=u;
edge[sign].w=0;
edge[sign].next=first[v];
first[v]=sign++;
}
int bfs(){
queue<int>q;
memset(d,0,sizeof(d));
d[s]=1;
q.push(s);
while(!q.empty()){
int top=q.front();
q.pop();
for(int i=first[top];~i;i=edge[i].next){
int to=edge[i].to;
if(edge[i].w>0&&d[to]==0){
d[to]=d[top]+1;
if(to==t)
return 1;
q.push(to);
}
}
}
return d[t]!=0;
}
int dfs(int top,int flow){
if(top==t)
return flow;
int ans=0,x=0;
for(int i=cur[top];~i;i=edge[i].next){
int to=edge[i].to;
if(edge[i].w>0&&d[to]==d[top]+1){
x=dfs(to,min(flow-ans,edge[i].w));
edge[i].w-=x;
edge[i^1].w+=x;
if(edge[i].w)
cur[top] = i;
ans+=x;
if(ans==flow)
return flow;
}
}
if(ans==0)
d[top]=0;
return ans;
}
int dinic(int n){
int ans=0;
while(bfs()){
for(int i=0;i<=n;i++)
cur[i]=first[i];
ans+=dfs(s,inf);
}
return ans;
}
int peo[1007], tab;
vector<int> vv[1007];
int main(){
int n, m, sum=0;
scanf("%d%d", &n, &m);
init();
s=0,t=n+m+1;
for(int i=1; i<=n; ++i){
scanf("%d", peo+i), sum+=peo[i];
add_edge(s, i, peo[i]);
}
for(int i=1; i<=n; ++i)
for(int j=n+1; j<=m+n; ++j)
add_edge(i, j, 1);
for(int i=n+1; i<=m+n; ++i){
scanf("%d", &tab);
add_edge(i, t, tab);
}
int x,y;
int ans=dinic(t);
if(ans==sum){
puts("1");
for(int i=n+1; i<=m+n; ++i){
for(int j=first[i]; ~j; j=edge[j].next){
if(edge[j].to!=t&&edge[j].w)
vv[edge[j].to].push_back(i-n);
}
}
for(int i=1; i<=n; ++i){
if(vv[i].size()){
printf("%d", vv[i][0]);
for(int j=1; j<vv[i].size(); ++j)
printf(" %d", vv[i][j]);
puts("");
}
}
}else{
puts("0");
}
return 0;
}
3、魔术球问题
这一题一开始除了建边以外没有想法,问了大佬才知道是最小路径覆盖问题;
最小路径覆盖:将原来的有向无环图G=(V,E),n=|V|。把G中每一个点x拆成编号为x和x+n的两个点。建立一张新的二分图。1~n为二分图的左部,n+1~2*n为右部分,对于原图的每条有向边(x, y),将其建立成(x,y+n)的连边,得到G图的拆点二分图,记为G2;那么最小路径覆盖条数=n-G2的最大匹配数;
这题就可以变成问至少多少个点可以被n条路径覆盖;
将每个点和之前比他小的点连上一条流量为1的边,且连接所有的可以和这个点值相加为完全平方数的点值相连,流量为1;
因为起点可以是前k个点里的任意一个点,所以说超级源点S要和1~k每个点连接一条边,流量为1;
然后再按照之前的建图规则将1+MAX~k+MAX(MAX:最大的k设置1567~无穷大区间里任意一个就可以了#滑稽)的点连接上超级汇点T,流量为1;
将dinic(我是用这个)初始化一次,之后加入一个点就加边一次增广一次;
直到当前点的数量k-增广出来的值大于题目输入的n时我们就可以知道k-1的时候是路径为n时的最大可容纳点;
详细的见代码:
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
#define inf 0x3fffffff
using namespace std;
const int maxn=5007, maxm=1e6+7;
int first[maxn],sign,cur[maxn];
int s,t,d[maxn];
int mp[maxn][maxn];
struct node{
int to,w,next;
}edge[maxm];
int sqr[89];
void init(){
memset(first,-1,sizeof(first));
sign=0;
}
void add_edge(int u,int v,int w){
edge[sign].to=v;
edge[sign].w=w;
edge[sign].next=first[u];
first[u]=sign++;
edge[sign].to=u;
edge[sign].w=0;
edge[sign].next=first[v];
first[v]=sign++;
}
int bfs(){
queue<int>q;
memset(d,0,sizeof(d));
d[s]=1;
q.push(s);
while(!q.empty()){
int top=q.front();
q.pop();
for(int i=first[top];~i;i=edge[i].next){
int to=edge[i].to;
if(edge[i].w>0&&d[to]==0){
d[to]=d[top]+1;
if(to==t)
return 1;
q.push(to);
}
}
}
return d[t]!=0;
}
int dfs(int top,int flow){
if(top==t)
return flow;
int ans=0,x=0;
for(int i=cur[top];~i;i=edge[i].next){
int to=edge[i].to;
if(edge[i].w>0&&d[to]==d[top]+1){
x=dfs(to,min(flow-ans,edge[i].w));
edge[i].w-=x;
edge[i^1].w+=x;
if(edge[i].w)
cur[top] = i;
ans+=x;
if(ans==flow)
return flow;
}
}
if(ans==0)
d[top]=0;
return ans;
}
int dinic(int n){
int ans=0;
while(bfs()){
for(int i=0;i<=n;i++)
cur[i]=first[i];
ans+=dfs(s,inf);
}
return ans;
}
void add_point(int i, int &n){
register int j;
for(j=1; sqr[j]<2*i; ++j)
if(sqr[j]>i) add_edge(sqr[j]-i, i+1569, 1);
add_edge(s, i, 1);
add_edge(i+1569, t, 1);
}
int fa[maxn], vis[maxn];
int main(){
register int n, i, j, now, sav;
for(i=1; i<78; ++i)
sqr[i]=i*i;
scanf("%d", &n);
init();
s=0,t=1569*2+1;
int ans=0;
for(i=1; ; ++i){
add_point(i, n);
ans+=dinic(t);
if(i-ans>n)
break;
}
now=i-1;
printf("%d\n", now);
for(i=1; i<=now; ++i){
for(sav=i, j=first[i]; ~j; j=edge[j].next)
if(edge[j].to!=s&&edge[j^1].w)
fa[sav]=edge[j].to-1569, sav=edge[j].to-1569;
}
for(i=1; i<=now; ++i){
if(!vis[i]){
printf("%d", i);
vis[i]=1;
for(j=fa[i]; j; j=fa[j]){
printf(" %d", j);
vis[j]=1;
}
puts("");
}
}
return 0;
}
这题ac后可以瞎jb优化
用别人dp的ac代码打个n=1~55的表存在loc数组里,将1~loc[n]个点全部加入图里建图,跑一次dinic得到答案,2ms;
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
#define inf 0x3fffffff
using namespace std;
const int maxn=5007, maxm=1e6+7;
int first[maxn],sign,cur[maxn];
int s,t,d[maxn];
int mp[maxn][maxn];
struct node{
int to,w,next;
}edge[maxm];
int sqr[89], loc[57]={0,1,3,7,11,17,23,31,39,49,59,71,83,97,111,127,143,161,179,199,219,241,263,287,311,337,363,391,419,449,479,511,543,577,611,647,683,721,759,799,839,881,923,967,1011,1057,1103,1151,1199,1249,1299,1351,1403,1457,1511,1567};
void init(){
memset(first,-1,sizeof(first));
sign=0;
}
void add_edge(int u,int v,int w){
edge[sign].to=v;
edge[sign].w=w;
edge[sign].next=first[u];
first[u]=sign++;
edge[sign].to=u;
edge[sign].w=0;
edge[sign].next=first[v];
first[v]=sign++;
}
int bfs(){
queue<int>q;
memset(d,0,sizeof(d));
d[s]=1;
q.push(s);
while(!q.empty()){
int top=q.front();
q.pop();
for(int i=first[top];~i;i=edge[i].next){
int to=edge[i].to;
if(edge[i].w>0&&d[to]==0){
d[to]=d[top]+1;
if(to==t)
return 1;
q.push(to);
}
}
}
return d[t]!=0;
}
int dfs(int top,int flow){
if(top==t)
return flow;
int ans=0,x=0;
for(int i=cur[top];~i;i=edge[i].next){
int to=edge[i].to;
if(edge[i].w>0&&d[to]==d[top]+1){
x=dfs(to,min(flow-ans,edge[i].w));
edge[i].w-=x;
edge[i^1].w+=x;
if(edge[i].w)
cur[top] = i;
ans+=x;
if(ans==flow)
return flow;
}
}
if(ans==0)
d[top]=0;
return ans;
}
int dinic(int n){
int ans=0;
while(bfs()){
for(int i=0;i<=n;i++)
cur[i]=first[i];
ans+=dfs(s,inf);
}
return ans;
}
int fa[maxn], vis[maxn];
int main(){
register int n, i, j, now, sav;
for(i=1; i<78; ++i)
sqr[i]=i*i;
scanf("%d", &n);
init();
s=0,t=1567*2+1;
int ans=0;
for(i=1; i<=loc[n]; ++i){
for(j=1; sqr[j]<2*i; ++j)
if(sqr[j]>i) add_edge(sqr[j]-i, i+1567, 1);
add_edge(s, i, 1);
add_edge(i+1567, t, 1);
}
ans+=dinic(t);
now=i-1;
printf("%d\n", loc[n]);
for(i=1; i<=now; ++i){
for(sav=i, j=first[i]; ~j; j=edge[j].next)
if(edge[j].to!=s&&edge[j^1].w)
fa[sav]=edge[j].to-1567, sav=edge[j].to-1567;
}
for(i=1; i<=now; ++i){
if(!vis[i]){
printf("%d", i);
vis[i]=1;
for(j=fa[i]; j; j=fa[j]){
printf(" %d", j);
vis[j]=1;
}
puts("");
}
}
return 0;
}
4、太空飞行计划问题
这一题是最大权闭合子图的问题,最大权闭合子图问题可以转化成最小割问题(详细看篇博客:https://www.cnblogs.com/wuyiqi/archive/2012/03/12/2391960.html)
那么这一题就是最大流问题;
而且根据上面看的博客我们得到了建图的方案,即对超级源点S与每一个实验建立一条容量为投资p的边,再对每一个仪器建立一条到超级汇点T的容量为仪器价格的边,其他实验到仪器的边建为INF的流量;
现在我们可以求答案了,也就是总实验的投资减去仪器的价格;
但是方案的输出还不明了,但是我们仔细一想,这不是最小割问题吗!也就是说我们可以求他的割集里包含的点,这个点就是最小割的答案;
最小割定义:给定一个网络G=(V, E),源点S,汇点T。若一个边集E` ⊆ E被删除去之后,源点和汇点不再连通,则称该边集为网络的割。边的容量之和最小的割成为最小割;
那么怎么求割边的点集是啥呢!dinic的实现方法是每次分层以后多次增广,也就是说每次增广基本上都是在削减残余网络里的剩余正向流量,即最后一次成功到达汇点T的BFS一定是最小的割集,也就是我们找到了最小割的割集,那么同理,最后一次经过的点,就是最小割包含的点集,也就是我们取最大权闭合子图的方案,我们可以用dinic的dis数组来判断;剩下的看代码。
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
#define inf 0x3fffffff
using namespace std;
const int maxn=5007, maxm=1e6+7;
int first[maxn], sign, cur[maxn];
int s,t,dis[maxn];
int mp[maxn][maxn], N, M;
struct node{
int to,w,next;
}edge[maxm];
int inr[maxn], anss[maxn];
void init(){
memset(first,-1,sizeof(first));
sign=0;
}
void write(int x){
if(x==0){putchar(48);return;}
int len=0,dg[20];
while(x>0){dg[++len]=x%10;x/=10;}
for(int i=len;i>=1;i--)putchar(dg[i]+48);
}
void add_edge(int u,int v,int w){
edge[sign].to=v;
edge[sign].w=w;
edge[sign].next=first[u];
first[u]=sign++;
edge[sign].to=u;
edge[sign].w=0;
edge[sign].next=first[v];
first[v]=sign++;
}
int bfs(){
queue<int>q;
memset(dis,0,sizeof(dis));
dis[s]=1;
q.push(s);
while(!q.empty()){
int top=q.front();
q.pop();
for(int i=first[top];~i;i=edge[i].next){
int to=edge[i].to;
if(edge[i].w>0&&dis[to]==0){
dis[to]=dis[top]+1;
if(to==t)
return 1;
q.push(to);
}
}
}
return dis[t]!=0;
}
int dfs(int top,int flow){
if(top==t)
return flow;
int ans=0,x=0;
for(int i=cur[top];~i;i=edge[i].next){
int to=edge[i].to;
if(edge[i].w>0&&dis[to]==dis[top]+1){
x=dfs(to,min(flow-ans,edge[i].w));
edge[i].w-=x;
edge[i^1].w+=x;
if(edge[i].w)
cur[top] = i;
ans+=x;
if(ans==flow)
return flow;
}
}
if(ans==0)
dis[top]=0;
return ans;
}
int dinic(int n){
int ans=0;
while(bfs()){
for(int i=0;i<=n;i++)
cur[i]=first[i];
ans+=dfs(s,inf);
}
return ans;
}
int main(){
register int i, ans, sum=0, cnt, n, m;
scanf("%d%d", &m, &n);
N=n, M=m;
init();
s=0,t=m+n+1;
for(i=1; i<=m; i++){
int x;
scanf("%d", &x);
sum+=x;
add_edge(s, i, x);
char ch=getchar();
while((ch=getchar())!='\n')
{
x=ch-'0';
while((ch=getchar())&&ch>='0'&&ch<='9')
x=x*10+ch-'0';
add_edge(i, x+m, maxn);
if(ch=='\n') break;
}
}
for(i=1; i<=n; ++i)
scanf("%d", &cnt), add_edge(i+m, t, cnt);
ans=dinic(t);
cnt=1;
for(i=1; i<=m; ++i)
if(dis[i]) anss[cnt++]=i;///dis不是0则有去过别的点 那么就是割集的点集之一
for(i=1; i<cnt-1; ++i){
printf("%d ", anss[i]);
}
printf("%d\n", anss[cnt-1]);
cnt=1;
for(i=m+1; i<=n+m; ++i)
if(dis[i]) anss[cnt++]=i-m;
for(i=1; i<cnt-1; ++i){
printf("%d ", anss[i]);
}
printf("%d\n", anss[cnt-1]);
printf("%d\n", sum-ans);
return 0;
}
5、最小路径覆盖问题
emmmmm!!!这题。。。字面意思。。。魔术球问题弱化版。。。其实应该先写这题的。。。。
直接贴代码了,模型已经见过了!!!
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
#define inf 0x3fffffff
using namespace std;
const int maxn=5007, maxm=1e6+7;
int first[maxn],sign,cur[maxn];
int s,t,d[maxn];
int mp[maxn][maxn];
struct node{
int to,w,next;
}edge[maxm];
void init(){
memset(first,-1,sizeof(first));
sign=0;
}
void add_edge(int u,int v,int w){
edge[sign].to=v;
edge[sign].w=w;
edge[sign].next=first[u];
first[u]=sign++;
edge[sign].to=u;
edge[sign].w=0;
edge[sign].next=first[v];
first[v]=sign++;
}
int bfs(){
queue<int>q;
memset(d,0,sizeof(d));
d[s]=1;
q.push(s);
while(!q.empty()){
int top=q.front();
q.pop();
for(int i=first[top];~i;i=edge[i].next){
int to=edge[i].to;
if(edge[i].w>0&&d[to]==0){
d[to]=d[top]+1;
if(to==t)
return 1;
q.push(to);
}
}
}
return d[t]!=0;
}
int dfs(int top,int flow){
if(top==t)
return flow;
int ans=0,x=0;
for(int i=cur[top];~i;i=edge[i].next){
int to=edge[i].to;
if(edge[i].w>0&&d[to]==d[top]+1){
x=dfs(to,min(flow-ans,edge[i].w));
edge[i].w-=x;
edge[i^1].w+=x;
if(edge[i].w)
cur[top] = i;
ans+=x;
if(ans==flow)
return flow;
}
}
if(ans==0)
d[top]=0;
return ans;
}
int dinic(int n){
int ans=0;
while(bfs()){
for(int i=0;i<=n;i++)
cur[i]=first[i];
ans+=dfs(s,inf);
}
return ans;
}
int fa[maxn], vis[maxn];
int main(){
register int n, i, j, now, sav, m, u, v;
scanf("%d%d", &n, &m);
init();
s=0,t=2*n+1;
for(i=1; i<=n; ++i){
add_edge(s, i, 1);
add_edge(i+n, t, 1);
}
while(m--){
scanf("%d%d", &u, &v);
add_edge(u, v+n, 1);
}
int ans=dinic(t);
now=i-1;
for(i=1; i<=now; ++i){
for(sav=i, j=first[i]; ~j; j=edge[j].next)
if(edge[j].to!=s&&edge[j^1].w)
fa[sav]=edge[j].to-n, sav=edge[j].to-n;
}
for(i=1; i<=now; ++i){
if(!vis[i]){
printf("%d", i);
vis[i]=1;
for(j=fa[i]; j; j=fa[j]){
printf(" %d", j);
vis[j]=1;
}
puts("");
}
}
printf("%d\n", n-ans);
return 0;
}
6、最长上升子序列问题
这个问题一开始完全想不到解法,查了才知道是最多不相交路径数的模型;
这题的建图其实就是在dp已经完成的基础上建立的;
第一问dp结束;
首先,我们知道我们对于第二问是计算从给定的序列中最多可取出多少个长度为maxn的递增子序列(每个元素都只能用一次,所以说流量很明显全是1)。
那么既然是求子序列个数,很明显要是一条一条的长度为maxn的路径;也就是说要建立长度为maxn的路径,那么很明显了,dp[i]为1的很明显是终点/起点,然后dp[i]为maxn的点很明显是起点/终点,然后对于每个满足j<i&&a[j]<a[i]&&dp[j]+1==dp[i]条件的i,j点建立一条边,如果终点你取dp[i]==1那么就从i->j建立边,如果终点你取dp[i]==maxn那么就从j->i建立边。然后流流流结束。
第三问的话,我是直接拆了图重新建,只要改s、t位置和1、n的连接流量为INF(前提是有那个边);
再跑一变流流流结束;
这里有一点要注意,每次DFS增广出来的答案如果是INF,那么说明有长度为1的序列,这个序列只能算一次;
具体看代码:
#include<queue>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int MAXN=1007, MAXM=1e6+7, INF=0x3fffffff;
int first[MAXN],sign,cur[MAXN];
int s,t,dis[MAXN];
struct node{
int to,w,next;
}edge[MAXM];
void init(){
memset(first,-1,sizeof(first));
sign=0;
}
void add_edge(int u,int v,int w){
edge[sign].to=v;
edge[sign].w=w;
edge[sign].next=first[u];
first[u]=sign++;
edge[sign].to=u;
edge[sign].w=0;
edge[sign].next=first[v];
first[v]=sign++;
}
int bfs(){
queue<int>q;
memset(dis, 0, sizeof(dis));
dis[s]=1;
q.push(s);
while(!q.empty()){
int top=q.front();
q.pop();
for(int i=first[top];~i;i=edge[i].next){
int to=edge[i].to;
if(edge[i].w>0&&dis[to]==0){
dis[to]=dis[top]+1;
if(to==t)
return 1;
q.push(to);
}
}
}
return dis[t]!=0;
}
int dfs(int top,int flow){
if(top==t)
return flow;
int ans=0,x=0;
for(int i=cur[top];~i;i=edge[i].next){
int to=edge[i].to;
if(edge[i].w>0&&dis[to]==dis[top]+1){
x=dfs(to,min(flow-ans,edge[i].w));
edge[i].w-=x;
edge[i^1].w+=x;
if(edge[i].w)
cur[top] = i;
ans+=x;
if(ans==flow)
return flow;
}
}
if(ans==0) dis[top]=0;
return ans;
}
int dinic(int n){
int ans=0, sum;
while(bfs()){
for(int i=0;i<=n;i++)
cur[i]=first[i];
sum=dfs(s,INF);
if(sum==INF)///判断处
sum=1;
ans+=sum;
}
return ans;
}
int dp[MAXN], a[MAXN];
void create(int flow, int &n, int &maxn){///建边
init();
register int i, j;
for(i=1; i<=n; ++i){
for(j=1; j<i; ++j){
if(dp[j]+1==dp[i]&&a[j]<a[i]){
add_edge(i, j, 1);
}
}
if(dp[i]==1){
add_edge(i, t, flow);
}
maxn=max(maxn, dp[i]);
}
for(i=1; i<=n; ++i){
if(dp[i]==maxn){
add_edge(s, i, flow);
}
}
}
int main(){
register int i, j, n, maxn=1;
scanf("%d", &n);
s=0, t=n+1;
for(i=1; i<=n; ++i){
dp[i]=1;
scanf("%d", &a[i]);
for(j=1; j<i; ++j){
if(a[i]>a[j]){
dp[i]=max(dp[i], dp[j]+1);
}
}
maxn=max(maxn, dp[i]);
}
printf("%d\n", maxn);
create(1, n, maxn);
printf("%d\n", dinic(t));
create(INF, n, maxn);
printf("%d\n", dinic(t));
return 0;
}