题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=3998
题目大意是:给你一个数列,要你求最长的上升子序列,并且求最多有几个,且每个数只能在每个子序列中出现一次。对于第一问题很容易用DP解决,那么第二个问题,如果每个数可以出现多次的话,DP也很容易解决,但这里必须只能出现一次,否则的话就得判重,复杂度O(n3)。把问题简化一下,就是求长度为s的点不想交的路径有多少条,即每个点的容量为1(拆点),dp值相差为1的建立边,增加源汇点s、t,s向dp值为1的建立边,dp值最大的向t建立边,然后求最大流。
在网上看到这题贪心也可以搞,具体做法就是从左往右扫描,能组成最长的就吧子序列删除。这里加一个优化,用一个数组记录每个数的位置,那么复杂度只有O(n)了,,Orz。
DP+最大流:
1 //STATUS:G++_AC_15MS_1620KB 2 #include<stdio.h> 3 #include<stdlib.h> 4 #include<string.h> 5 #include<math.h> 6 #include<iostream> 7 #include<string> 8 #include<algorithm> 9 #include<vector> 10 #include<queue> 11 #include<stack> 12 #include<map> 13 using namespace std; 14 #define LL __int64 15 #define pii pair<int,int> 16 #define Max(a,b) ((a)>(b)?(a):(b)) 17 #define Min(a,b) ((a)<(b)?(a):(b)) 18 #define mem(a,b) memset(a,b,sizeof(a)) 19 #define lson l,mid,rt<<1 20 #define rson mid+1,r,rt<<1|1 21 const int MAX=10100,INF=0x3f3f3f3f; 22 const LL LLNF=0x3f3f3f3f3f3f3f3fLL; 23 24 struct Edge{ 25 int u,v,cap; 26 }; 27 28 vector<Edge> e; 29 vector<int> g[MAX]; 30 int p[MAX],cur[MAX],num[MAX],dp[MAX],d[MAX]; 31 int n,s,t,mt,maxlen; 32 33 void adde(int a,int b,int val){ 34 e.push_back((Edge){a,b,val}); 35 g[a].push_back(mt++); 36 e.push_back((Edge){b,a,0}); 37 g[b].push_back(mt++); 38 } 39 40 void gets() 41 { 42 int i,j; 43 dp[1]=1; 44 for(i=2;i<=n;i++){ 45 dp[i]=1; 46 for(j=1;j<i;j++) 47 if(num[j]<num[i] && dp[j]>=dp[i])dp[i]=dp[j]+1; 48 } 49 } 50 51 int augment() 52 { 53 int x=t,a=INF; 54 while(x!=s){ 55 a=Min(a,e[p[x]].cap); 56 x=e[p[x]].u; 57 } 58 x=t; 59 while(x!=s){ 60 e[p[x]].cap-=a; 61 e[p[x]^1].cap+=a; 62 x=e[p[x]].u; 63 } 64 return a; 65 } 66 67 int isap() 68 { 69 int i,x,ok,min,flow=0; 70 mem(d,0);mem(num,0); 71 num[0]=t+1; 72 mem(cur,0); 73 x=s; 74 while(d[s]<=t){ 75 if(x==t){ 76 flow+=augment(); 77 x=s; 78 } 79 ok=0; 80 for(i=cur[x];i<g[x].size();i++){ 81 Edge& et=e[g[x][i]]; 82 if(et.cap && d[x]==d[et.v]+1){ 83 ok=1; 84 p[et.v]=g[x][i]; 85 cur[x]=i; 86 x=et.v; 87 break; 88 } 89 } 90 if(!ok){ 91 min=t; 92 for(i=0;i<g[x].size();i++){ 93 Edge& et=e[g[x][i]]; 94 if(et.cap && d[et.v]<min)min=d[et.v]; 95 } 96 if(--num[d[x]]==0)break; 97 num[d[x]=min+1]++; 98 cur[x]=0; 99 if(x!=s)x=e[p[x]].u; 100 } 101 } 102 return flow; 103 } 104 105 int main() 106 { 107 // freopen("in.txt","r",stdin); 108 int i,j; 109 while(~scanf("%d\n",&n)) 110 { 111 mt=0; 112 s=0,t=n<<1|1; 113 e.clear(); 114 for(i=0;i<=t;i++)g[i].clear(); 115 for(i=1;i<=n;i++) 116 scanf("%d",&num[i]); 117 gets(); 118 maxlen=1; 119 for(i=2;i<=n;i++) 120 if(dp[i]>maxlen)maxlen=dp[i]; 121 for(i=1;i<=n;i++){ 122 adde(i,n+i,1); 123 for(j=i+1;j<=n;j++) 124 if(dp[i]+1==dp[j]) 125 adde(n+i,j,1); 126 } 127 for(i=1;i<=n;i++){ 128 if(dp[i]==1)adde(s,i,1); 129 if(dp[i]==maxlen)adde(n+i,t,1); 130 } 131 132 printf("%d\n%d\n",maxlen,isap()); 133 } 134 return 0; 135 }
DP+贪心(某牛代码):
1 /*hdu 3998 求最长上升序列的个数 贪心 2 先暴力 n ^ 2 预处理出 每个dp 值,当然也可以用线段树 降低复杂度 3 如 1 2 3 1 1 2 3 2 3 1 2 3 4 现在 分别要找到 1 2 3, 从前往后, 找到了就不能在用 5 所以就开数组 保存每个1 2 3 的位置 , 类似于 指针扫描的做法就可以在 (o)(n) 的时间内计算出 个数*/ 6 #include<map> 7 #include<set> 8 #include<cmath> 9 #include<queue> 10 #include<stack> 11 #include<cstdio> 12 #include<vector> 13 #include<string> 14 #include<cstring> 15 #include<iostream> 16 #include<algorithm> 17 const double pi = acos(-1.0); 18 using namespace std; 19 #define L(x) (x << 1) 20 #define R(x) (x << 1 | 1) 21 #define MID int mid = (l + r) >> 1; 22 const int maxn = 608; 23 int dp[maxn], a[maxn], now[maxn]; 24 int pos[maxn][maxn]; 25 26 int main(){ 27 int n; 28 // freopen("hdu 3998.in","r",stdin); 29 while(scanf("%d",&n)!=EOF){ 30 for(int i = 1; i <= n; i++){ 31 scanf("%d", a + i); 32 } 33 if(n == 0) {printf("0\n0\n");continue;} 34 int s = 1; 35 dp[1] = 1; 36 for(int i = 2; i <= n; i++){ 37 dp[i] = 1; 38 for(int j = 1; j < i; j++){ 39 if(a[i] > a[j] && dp[i] <= dp[j]){ 40 dp[i] = dp[j] + 1; 41 } 42 } 43 s = max(s, dp[i]); 44 } 45 for(int i = 1; i <= s; i++) pos[i][0] = 0; 46 for(int i = 1; i <= n; i++){ 47 pos[dp[i]][0]++; 48 pos[dp[i]][pos[dp[i]][0]] = i; 49 } 50 int ans = 0; 51 for(int i = 1; i <= s; i++) now[i] = 1; 52 while(1){ 53 int flag = 0, tmp; 54 if(now[1] > pos[1][0]) break; 55 else { 56 tmp = pos[1][now[1]]; now[1]++; 57 } 58 for(int i = 2; i <= s; i++){ 59 while(now[i] <= pos[i][0] && pos[i][now[i]] <= tmp) now[i]++; 60 if(now[i] > pos[i][0]){ 61 flag = 1; break; 62 } 63 tmp = pos[i][now[i]]; 64 now[i]++; 65 } 66 if(flag) break; 67 ans++; 68 } 69 printf("%d\n%d\n", s, ans); 70 } 71 } 72 /* 73 4 74 3 6 2 5 75 */