POJ1692 Crossed Matchings

来源:互联网 发布:防止mysql 注入攻击 编辑:程序博客网 时间:2024/04/26 11:23

Crossed Matchings

Description

给定两个正整数数列,如果第一个数列中有一个数和第二个数列中的某个数相同,并且都为r,则我们可以将这两个数用线段连起来。我们称这条线段为 r-匹配线段。

我们想要对于给定的输入,找到画出最多匹配线段的方式,并且满足以下条件:

  1. 每条a-匹配线段恰好和一条b-匹配线段相交,且ab。我们称这样的匹配为交叉匹配。

  2. 不允许一点多线一线多交的情况出现:不允许两条线段从同一个数出发,不允许一条线段和多条其它线段相交。

编一个程序对于给定输入数据,计算匹配线段的最多条数。

Sample Input

3
6 6
1 3 1 3 1 3
3 1 3 1 3 1
4 4
1 1 3 3
1 1 3 3
12 11
1 2 3 3 2 4 1 5 1 3 5 10
3 1 2 3 2 4 12 1 5 5 3

Sample Output

6
0
8


本题重点在于如何通过推导和约束,实现时间复杂度的降维。本题的基本推导思想亦借鉴于模板型线性dp:LIS,LCS,LCIS等的推导方法。

首先根据题意,我们要定义dp[i][j]表示序列a[1...i],b[1...j]中最多匹配线段数。并且ai,bj不一定跟其他值构成匹配(所以这一点有别于一般的dp,我们都是要确定当前枚举的这个被选到,才方便转移)。

首先我们可以得到最简单的做法:

  • 欲匹配ai,bj,于是在a[1...i1]中找到ak=bjb[1...j1]中找到bp=ai,用当前枚举的{k,p}内的最大值去更新dp[i][j]。转移方程式:
    dp[i][j]=maxmax{dp[i1][j]dp[i][j1]dp[k1][p1]+2ak=bj,bp=ai
    此时复杂度为O(n4),非常暴力。

接下来进一步考虑优化,我此时借鉴了LCISO(n2)+一维的做法,假设我们枚举序列1的右侧匹配ai与左侧匹配ak,于是要在b序列中找到一组p,j,满足p<jbp=ai,bj=ak

套用LCIS的思路,对于这个bj,它可以直接从之前所有bp=aidp[k1][p1]的最大值转移过来,而这个是可以在扫描j的同时顺带维护的。于是维护一个局部变量mx,时间复杂度就降到了O(n3)

#include <cstdio>#include <cstring>#include <algorithm>using namespace std;static const int M=1005;int n,m,a[M],b[M];int dp[M][M];void check(int &a,int b){if(a<b)a=b;}int main(){    scanf("%d %d",&n,&m);    for(int i=1;i<=n;i++)scanf("%d",&a[i]);    for(int i=1;i<=m;i++)scanf("%d",&b[i]);    for(int i=1;i<=n;i++){        for(int j=1;j<=m;j++)dp[i][j]=dp[i-1][j];        for(int k=1;k<i;k++)            if(a[i]!=a[k]){                int mx=0;//局部变量,表示收集所有a[i]=b[j]时最大dp[k-1][p-1]                for(int j=1;j<=m;j++){                    check(dp[i][j],dp[i][j-1]);                    if(a[i]==b[j])check(mx,dp[k-1][j-1]+2);                    else if(a[k]==b[j])check(dp[i][j],mx);                }            }    }    printf("%d\n",dp[n][m]);}

之后想了一段时间,意识到这样一个小贪心:由于定义dp[i][j]表示序列a[1...i],b[1...j]中最多匹配线段数,所以对于k<i,p<j,必然有dp[k][p]dp[i][j]

于是对于bj,如果它能和ai构成匹配,就必然在ai之前出现过ak=bj,在bj之前出现过bp=ai。由于上述性质,我们只需要知道i,j最近的k,p即可。两层循环都是顺序的,所以这两个东西都可以维护。对于k的维护,我们还需要将数据离散化之后进行操作(当然POJ上的数据是不需要的)。

综上,转移方程式为dp[i][j]=maxdp[i1][j]dp[i][j1]dp[pre[bj]1][pre[ai]1]+2,时间复杂度为O(n2)

Code:

#include <cstdio>#include <cstring>#include <algorithm>using namespace std;#define clear(x,val) memset(x,val,sizeof(x))static const int M=2005;int n,m,a[M],b[M];int res[M<<1],rtop=0;int Top[M<<1],dp[M][M];void check(int &a,int b){if(a<b)a=b;}int main(){    scanf("%d %d",&n,&m);    for(int i=1;i<=n;i++)scanf("%d",&a[i]),res[++rtop]=a[i];    for(int i=1;i<=m;i++)scanf("%d",&b[i]),res[++rtop]=b[i];    sort(res+1,res+rtop+1);    rtop=unique(res+1,res+rtop+1)-(res+1);    for(int i=1;i<=n;i++)a[i]=lower_bound(res+1,res+rtop+1,a[i])-res;    for(int i=1;i<=m;i++)b[i]=lower_bound(res+1,res+rtop+1,b[i])-res;    clear(Top,-1);    for(int i=1;i<=n;i++){        int pre=-1;        for(int j=1;j<=m;j++){            check(dp[i][j],dp[i-1][j]);            check(dp[i][j],dp[i][j-1]);            if(b[j]==a[i])pre=j;            else if(Top[b[j]]!=-1&&pre!=-1)                check(dp[i][j],dp[Top[b[j]]-1][pre-1]+2);        }        Top[a[i]]=i;    }    printf("%d\n",dp[n][m]);}
0 0
原创粉丝点击