[网络流24题]最长不下降子序列问题
[luogu 2766] 最长不下降子序列问题
第一问:
\(O(n^2)\) 的DP求LIS
为了下面叙述方便,我们将DP过程讲一遍
子状态:dp[i]表示以a[i]结尾的LIS长度
初始条件:dp[i]=1
状态转移方程:\(dp[i]=dp[j]+1(j<i,a[j]\leq a[i])\)
第二问:
我们发现若a[j]加上a[i]可以构成一个不下降子序列,则\(j<i,a[j] \leq a[i]\)
又发现每个元素只能在一个序列中,考虑拆点
建图方法:
原点S=0,T=2n+1
(1)每个点拆成两个点,i向i+n连边,容量1
(2)如果dp[i]=1,则s向i连边,容量1
(3)如果\(j<i,a[j] \leq a[i]\),则j+n向i连边,容量1
(4)如果dp[i]=k(k为LIS长度),则i+n向t连边,容量1
最大流即为答案
第三问:
除了1和n对应节点有关的边容量为INF,其他建图和方案二一样
注意一种特殊情况需要特判
当序列严格递减时,dp[i]=1,s=1,并且不存在i,j满足\(j<i,a[j] \leq a[i]\)
由于dp[1]=1=k,则s会向1连一条INF的边,1向1+n连一条INF的边,1+n向t连一条INF的边,最大流为INF
此时第三问的答案应等于第二问,为n
#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
#define maxn 505
#define maxm 500005
#define INF 0x3f3f3f3f
using namespace std;
int n;
struct edge{
int from;
int to;
int next;
int flow;
}E[maxm<<1];
int sz=1;
int head[maxn*2];
void add_edge(int u,int v,int w){
// printf("%d->%d %d\n",u,v,w);
sz++;
E[sz].from=u;
E[sz].to=v;
E[sz].flow=w;
E[sz].next=head[u];
head[u]=sz;
sz++;
E[sz].from=v;
E[sz].to=u;
E[sz].flow=0;
E[sz].next=head[v];
head[v]=sz;
}
int deep[maxn*2];
bool bfs(int s,int t){
queue<int>q;
q.push(s);
for(int i=s;i<=t;i++) deep[i]=0;
deep[s]=1;
while(!q.empty()){
int x=q.front();
q.pop();
for(int i=head[x];i;i=E[i].next){
int y=E[i].to;
if(E[i].flow&&!deep[y]){
deep[y]=deep[x]+1;
q.push(y);
if(y==t) return 1;
}
}
}
return 0;
}
int dfs(int x,int t,int minf){
if(x==t) return minf;
int k,rest=minf;
for(int i=head[x];i;i=E[i].next){
int y=E[i].to;
if(E[i].flow&&deep[y]==deep[x]+1){
k=dfs(y,t,min(rest,E[i].flow));
rest-=k;
E[i].flow-=k;
E[i^1].flow+=k;
if(k==0) deep[y]=0;
if(rest==0) break;
}
}
return minf-rest;
}
int dinic(int s,int t){
int nowflow=0,maxflow=0;
while(bfs(s,t)){
while(nowflow=dfs(s,t,INF)) maxflow+=nowflow;
}
return maxflow;
}
int len;
int a[maxn];
int dp[maxn];
int solve1(){
int ans=0;
for(int i=1;i<=n;i++){
dp[i]=1;
for(int j=1;j<i;j++){
if(a[i]>=a[j]){
dp[i]=max(dp[j]+1,dp[i]);
}
}
ans=max(ans,dp[i]);
}
return ans;
}
int solve2(){
int s=0,t=n*2+1;
for(int i=1;i<=n;i++){
if(dp[i]==1) add_edge(s,i,1);
}
for(int i=1;i<=n;i++){
add_edge(i,i+n,1);//限制每个点只被选一次
}
for(int i=1;i<=n;i++){
for(int j=1;j<i;j++){
if(a[i]>=a[j]&&dp[i]==dp[j]+1){
add_edge(j+n,i,1);
}
}
}
for(int i=1;i<=n;i++){
if(dp[i]==len){
add_edge(i+n,t,1);
}
}
return dinic(s,t);
}
bool is_decrease(){
for(int i=2;i<=n;i++){
if(a[i]>=a[i-1]) return 0;
}
return 1;
}
int solve3(){
if(is_decrease()) return solve2();
sz=1;
memset(E,0,sizeof(E));
memset(head,0,sizeof(head));
int s=0,t=n*2+1;
for(int i=1;i<=n;i++){
if(dp[i]==1){
if(i==1||i==n) add_edge(s,i,INF);
else add_edge(s,i,1);
}
}
for(int i=1;i<=n;i++){
if(i==1||i==n) add_edge(i,i+n,INF);
else add_edge(i,i+n,1);//限制每个点只被选一次
}
for(int i=1;i<=n;i++){
for(int j=1;j<i;j++){
if(a[i]>=a[j]&&dp[i]==dp[j]+1){
add_edge(j+n,i,1);
}
}
}
for(int i=1;i<=n;i++){
if(dp[i]==len){
if(i==1||i==n) add_edge(i+n,t,INF);
else add_edge(i+n,t,1);
}
}
return dinic(s,t);
}
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
}
len=solve1();
printf("%d\n",len);
printf("%d\n",solve2());
printf("%d\n",solve3());
}
版权声明:因为我是蒟蒻,所以请大佬和神犇们不要转载(有坑)的文章,并指出问题,谢谢