第一问dp就不说了。。
然后这个网络流的建图思路就有点神奇了。。
第一问的LIS用的是传统的N^2dp做。。然后可以算出以每个数为结尾的LIS,记为dp[i],将第一问答案记做s
然后其实答案最多就是dp[i]等于s的个数,然后剩下的就是看他们能不能找到自己前面的子列了。。
首先考虑找s-1位置的数字,显然是要找dp[i]==s-1的数字,如果这个i找不到s位置的数字,那么这个数还是否有用呢?答案是没有。。可能很多人想过这个数字如果不能放在s-1的位置那可以放在更靠前的位置继续使用,然而假设真的有更靠前的位置给它放,那么我们大可以把这个数以前的序列全部替换成以他为结尾的LIS序列,然后你会发现,替换之后整个序列的长度就大于s了。。这显然不合理。。
所以可以得到一个结论,对每个数i他只能放在序列中的dp[i]位置。。
然后建图思路就十分明了了,直接将dp值差1而且可以在dp的时候转移的2个点连起来,容量为1,然后将dp值为1的和源点连边,dp值为s的和汇点连边,容量均为1。。
看起来貌似建完了,不过需要注意的是每个数字只能用一次,因此对每个点还需要拆点限制流量。。
/** * ┏┓ ┏┓ * ┏┛┗━━━━━━━┛┗━━━┓ * ┃ ┃ * ┃ ━ ┃ * ┃ > < ┃ * ┃ ┃ * ┃... ⌒ ... ┃ * ┃ ┃ * ┗━┓ ┏━┛ * ┃ ┃ Code is far away from bug with the animal protecting * ┃ ┃ 神兽保佑,代码无bug * ┃ ┃ * ┃ ┃ * ┃ ┃ * ┃ ┃ * ┃ ┗━━━┓ * ┃ ┣┓ * ┃ ┏┛ * ┗┓┓┏━┳┓┏┛ * ┃┫┫ ┃┫┫ * ┗┻┛ ┗┻┛ */ #include<cstdio> #include<cstring> #include<algorithm> #include<iostream> #include<queue> #include<cmath> #include<map> #include<stack> #include<set> #define inc(i,l,r) for(int i=l;i<=r;i++) #define dec(i,l,r) for(int i=l;i>=r;i--) #define link(x) for(edge *j=h[x];j;j=j->next) #define mem(a) memset(a,0,sizeof(a)) #define ll long long #define eps 1e-12 #define succ(x) (1<<x) #define lowbit(x) (x&(-x)) #define sqr(x) ((x)*(x)) #define mid (x+y>>1) #define NM 20005 #define nm 500005 #define pi 3.1415926535897931 using namespace std; const int inf=1000000005; ll read(){ ll x=0,f=1;char ch=getchar(); while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();} while(isdigit(ch))x=x*10+ch-'0',ch=getchar(); return f*x; } struct edge{int t,v;edge*next,*rev;}e[nm],*h[NM],*o=e,*tmp[NM],*p[NM]; void _add(int x,int y,int v){o->t=y;o->v=v;o->next=h[x];h[x]=o++;} void add(int x,int y,int v){_add(x,y,v);_add(y,x,0);h[x]->rev=h[y];h[y]->rev=h[x];} int n,a[NM],dp[NM],ans,d[NM],cnt[NM],tot; int maxflow(){ int flow=0;edge*j; mem(d);mem(cnt);mem(tmp);mem(p);cnt[0]=tot=n+1; inc(i,0,n)tmp[i]=h[i]; for(int x=0,s=inf;d[x]<tot;){ for(j=tmp[x];j;j=j->next)if(j->v&&d[x]==d[j->t]+1)break; if(j){ s=min(s,j->v);p[j->t]=tmp[x]=j; if((x=j->t)==n){ for(;p[x];x=p[x]->rev->t)p[x]->v-=s,p[x]->rev->v+=s; flow+=s;s=inf; } }else{ if(!--cnt[d[x]])break;d[x]=tot; link(x)if(j->v&&d[x]>d[j->t]+1)tmp[x]=j,d[x]=d[j->t]+1; cnt[d[x]]++; if(p[x])x=p[x]->rev->t; } } return flow; } int main(){ n=read(); inc(i,1,n)a[i]=read(); inc(i,1,n){ dp[i]=1; inc(j,1,i-1)if(a[i]>=a[j])dp[i]=max(dp[i],dp[j]+1); ans=max(ans,dp[i]); } printf("%d\n",ans); inc(i,1,n)if(dp[i]==1)add(0,i,1); inc(i,1,n)if(dp[i]==ans)add(i+n,2*n+1,1); inc(i,1,n)inc(j,i+1,n)if(a[i]<=a[j]&&dp[i]+1==dp[j])add(i+n,j,1); inc(i,1,n)add(i,i+n,1); int _t=n;n=2*n+1;int _s; printf("%d\n",_s=maxflow()); add(0,_t+1,inf);if(dp[_t]==ans)add(_t,n,inf); printf("%d\n",_s+maxflow()); return 0; }
P2766 最长不下降子序列问题
题目描述
«问题描述:
给定正整数序列x1,...,xn 。
(1)计算其最长不下降子序列的长度s。
(2)计算从给定的序列中最多可取出多少个长度为s的不下降子序列。
(3)如果允许在取出的序列中多次使用x1和xn,则从给定序列中最多可取出多少个长度为s的不下降子序列。
«编程任务:
设计有效算法完成(1)(2)(3)提出的计算任务。
输入输出格式
输入格式:第1 行有1个正整数n,表示给定序列的长度。接下来的1 行有n个正整数n:x1, ..., xn。
输出格式:第1 行是最长不下降子序列的长度s。第2行是可取出的长度为s 的不下降子序列个数。第3行是允许在取出的序列中多次使用x1和xn时可取出的长度为s 的不下降子序列个数。
输入输出样例
说明
n≤500n\le 500n≤500