洛谷P2766 最长递增子序列问题

DP 最大流 网络流

题目传送门

第一问直接DP水过啦啦啦(长度记为l)。
第二问需要建图。
1、把每个点i拆成i.a,i.b两个点,在之间连一条容量为1的边。
2、加一个超级源和一个超级汇(也就是起点和终点),若f[i]=1就在s和i.a之间连一条容量为1的边,若f[i]=l就在i.b和t之间连一条容量为1的边。
3、如果i>j且a[i]>a[j]且f[j]+1=f[i],就在j.b和i.a之间连一条容量为1的边。
然后跑个最大流就行啦(是不是很巧妙)!运用到了分层图的思想,且n的值题目没讲,保险起见就用dinic。
第三问的话把1.a和1.b、s和1.a、n.a和n.b、n.b到t这四条边的容量改为无穷大就好啦(注意如果f[n]!=l的话后两条边就别连)!然后愉快地跑个最大流(把第二问的答案也加上去)。

贴上代码:

#include<cstdio>
#include<cstring>
#include<algorithm>
#define MAXN 5000
#define MAXM 2000005
using namespace std;
struct edge{
    int next;
    int to;
    int v;
    int flow;
};
int a[MAXN+5],f[MAXN+5];
int n,k,s;
edge ed[MAXM+5];
int h[MAXN+5],cop[MAXN+5],b[MAXN+5],dis[MAXN+5];
bool pd[MAXN+5];
void read(int x,int y,int z){
    ed[k].next=h[x]; ed[k].to=y; ed[k].v=z; ed[k].flow=0; h[x]=k; k++; 
    ed[k].next=h[y]; ed[k].to=x; ed[k].v=0; ed[k].flow=0; h[y]=k; k++;
}
bool bfs(int st,int end){
    memset(pd,false,sizeof(pd));
    int r=0,w=1;
    b[1]=st; pd[st]=true;
    dis[st]=0;
    while (r<w){
        int x=b[++r];
        for (int i=h[x];i!=-1;i=ed[i].next)
            if (!pd[ed[i].to]&&ed[i].v>ed[i].flow){
                b[++w]=ed[i].to;
                pd[ed[i].to]=true;
                dis[ed[i].to]=dis[x]+1;
            }
    }
    return pd[end];
}
int dfs(int x,int y,int rem){
    if (x==y||rem==0)
        return rem;
    int sum=0;
    for (int &i=cop[x];i!=-1;i=ed[i].next)
        if (dis[ed[i].to]==dis[x]+1){
            int p=dfs(ed[i].to,y,min(ed[i].v-ed[i].flow,rem));
            if (p){
                ed[i].flow+=p; ed[i^1].flow-=p;
                sum+=p; rem-=p;
            }
        }
    return sum;
}
int maxflow(int x,int y){
    int sum=0;
    while (bfs(x,y)){
        memcpy(cop,h,sizeof(cop));
        sum+=dfs(x,y,0x7fffffff);
    }
    return sum;
}
int main(){
    scanf("%d",&n);
    for (int i=1;i<=n;i++)
    scanf("%d",&a[i]);
    f[1]=1;
    for (int i=1;i<=n;i++){
        for (int j=1;j<i;j++)
            if (a[i]>=a[j])
                f[i]=max(f[i],f[j]+1);
        if (!f[i]) f[i]=1;
    }
    for (int i=1;i<=n;i++)
        s=max(s,f[i]);
    printf("%d\n",s);
    memset(h,-1,sizeof(h));
    int st=0,end=5003;
    for (int i=1;i<=n;i++)
        if (f[i]==1)
            read(st,i,1);
    for (int i=1;i<=n;i++)
        if (f[i]==s)
            read(i+n,end,1);
    for (int i=1;i<=n;i++)
        read(i,i+n,1);
    for (int i=1;i<=n;i++)
        for (int j=1;j<i;j++)
            if (a[i]>=a[j]&&f[j]+1==f[i])
                read(j+n,i,1);
    int ans=maxflow(st,end);
    printf("%d\n",ans);
    read(1,1+n,0x7fffffff);
    read(st,1,0x7fffffff);
    if (f[n]==s){
        read(n,n+n,0x7fffffff);
        read(n+n,end,0x7fffffff);
    }
    ans+=maxflow(st,end);
    printf("%d\n",ans);
    return 0;
} 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值