问题描述
给定正整数序列x1,...,xn 。
(1)计算其最长不下降子序列的长度s。
(2)计算从给定的序列中最多可取出多少个长度为s的不下降子序列。
(3)如果允许在取出的序列中多次使用x1和xn,则从给定序列中最多可取出多少个长度为s的不下降子序列。
«编程任务:
设计有效算法完成(1)(2)(3)提出的计算任务。
思路与想法
看到这道题,我想到的只是推DP方程,只要保持n的平方就可以完成了(但是不行,最少n的三次方)。
网络流中的最大流可以解决这个东西,只要保证从原点流到汇点的流量满足s长度的最长非下降就可以了。怎么保证呢?
解法
我们把第一问的答案用n的平方(或者大佬可以用单调队列来维护这个max)。f[i]表示以i结尾的最长非下降序列长度是多少。(拆点,把 i 拆成 i 和 i’)
1.当f[i]==1时,那么表示i不能作为一个序列的结尾,只能作为开头,所以从begin(源点)到i连一条流量为1的边。
2.当f[i]==mmax时,表示i能作为一个最长非下降序列的结尾,所以从i’到end(汇点)连一条流量为1的边。
4.为了能使流量能连续流通,我们从i’到i连一条流量为1的边。
(注意,当mmax==1 时,要一定要特判,从i到i’连一条流量为1的边;因为不这样子的话,i到end就没有边可以通,一开始我就卡了两个点。。。)
第三个问,就干脆将begin到1点连一条INF的边,n’点到end连一条INF的边。
想看具体图片吗?。。像下面这组样例
4
3 6 2 5
#include
#include
#include
#include
using namespace std;
int n;
int a[510];
int f[510];
struct edge{int y,next,c;};
edge s[600010];
int begin,end;
int first[10010];
int len=1;
int q[10010];
int h[10010];
bool tf[10010];
int mmax=0;
int st,ed;
int mmin(int x,int y)
{
return x0)
{
h[y]=h[x]+1;
q[ed]=y;
ed++;
if(ed==n+n+n) ed=1;
}
}
st++;
if(st==n+n+n) st=1;
}
if(h[end]!=0) return true;
return false;
}
int dfs(int x,int t)
{
if(x==end) return t;
int tot=0;
for(int i=first[x];i!=0;i=s[i].next)
{
if(t==tot) return t;
int y=s[i].y;
if(h[y]==h[x]+1)
{
int get=dfs(y,mmin(t-tot,s[i].c));
tot+=get;s[i].c-=get;s[i^1].c+=get;
}
}
if(tot==0) h[x]=0;
return tot;
}
int max_flow()
{
int flow=0,now=0;
while(bfs())
{
now=dfs(begin,1e9);
while(now!=0)
{
flow+=now;
now=dfs(begin,1e9);
}
}
return flow;
}
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
scanf("%d",&a[i]);
for(int i=1;i<=n;i++)
{
f[i]=1;
for(int j=1;jf[i]) f[i]=f[j]+1;
if(f[i]>mmax) mmax=f[i];
}
printf("%d\n",mmax);
begin=0;
end=2*n+1;
build_edge(1);
printf("%d\n",max_flow());
build_edge(1e9);
printf("%d\n",max_flow());
}