2016-2017 ACM-ICPC CHINA-Final C - Mr. Panda and Strips (dp/暴力尺取+剪枝)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_37025443/article/details/84336362

题目链接

题意:

给你一个n长度的彩带,让你取其中的一段/两段然后拼接起来,使得最后得到彩带中的每一个数字都是不重复的,问你最后的彩带的最大长度是多少

解析:

这道题...我自己是用dp做的,O(n*n)的复杂度,但是看网上都是直接用O(n*n*n)+剪枝过的.....

首先我自己的方法

首先就是将每一个值都离散化,使得a[i]<=1000

然后再处理出ind[i][j]:表示在[1,i-1]区间里从右往左第一个j的位置

然后就是dp了。dp[i][j]:第2个区间以i结尾,第1个区间以j结尾的最大长度(j<i).

最大长度对应下dp[i][j].fi=以i结尾的区间的长度,dp[i][j].se=以j结尾的长度

dp[i][j].to=dp[i][j].fi+dp[i][j].se

然后就是递推公式,dp[i][j]可以由两个状态递推过来dp[i-1][j]+a[i],dp[i][j-1]+a[j]

对于+a[i],我们首先找dp[i-1][j]所表示的2个区间里面有没有a[i]

如果在第一个区间(j结尾)中找到a[i],位置为an,那么对应把第一个区间的长度减少dp[i][j].se=j-an

dp[i][j].fi=dp[i-1][j].fi+1;

如果在第2个区间找到a[i],位置为w,那么第2个区间的长度减少dp[i][j].fi=i-w

dp[i][j].se=dp[i-1][j].se;

都找不到的话,直接就把dp[i][j].fi=dp[i-1][j].fi+1;   dp[i][j].se=dp[i-1][j].se;

然后这里还需要考虑一个情况就是加入a[i]的时候,第2个区间的长度可以为0,即dp[i][j].fi=0;

那么我们就直接将dp[j][0].to与之前的答案比较,哪个大返回哪个,相等返回之前的答案

同理从dp[i][j-1]推到dp[i][j]类似,这里需要注意的是当让第一个区间长度=0,dp[i][j].se=0

同时我们还要维护j<i,的性质,所以比较的答案是int tmp=min(i-j,dp[i][0].to);,这里注意

最后dp[i][j]取从这两个状态转移回来的最大值就可以了

这里需要注意的是因为dp[i][i]是特殊情况,专门用于dp[i+1][i]从dp[i][i]转移的情况,所以这个直接让第2个区间=0

然后变成dp[i][0]就可以了。

这道题..写的时候一直不是很自信,感觉自己的思路有问题,但最终调了一个下午+晚上的时间把自己代码里的bug挑出来了

....真的最近写代码真的太差了....

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;

const int MAXN = 1e3+10;
const int MAX = 1E5+10;
typedef struct node
{
    int fi;
    int se;
    int to;
}node;

node dp[MAXN][MAXN];

int a[MAXN],b[MAXN];
int mp[MAX];
int ind[MAXN][MAXN];

int vis[MAXN];


inline node Max_node(node a,node b)
{
    return a.to>=b.to?a:b;
}
node cali_1(int i,int j)  //i-1,j
{
    node res1;
    int ch=mp[a[i]];
    int w=ind[i][ch];
    int an=ind[j+1][ch];

    if(w>i-1-(dp[i-1][j].fi)&&w<=i-1)
    {
        res1.se=dp[i-1][j].se;
        res1.fi=i-w;
        res1.to=res1.fi+res1.se;
    }
    else if(an>j-(dp[i-1][j].se)&&an<=j)
    {
        res1.fi=dp[i-1][j].fi+1;
        res1.se=j-an;
        res1.to=res1.fi+res1.se;
    }
    else
    {
        res1.fi=dp[i-1][j].fi+1;
        res1.se=dp[i-1][j].se;
        res1.to=res1.fi+res1.se;
    }
    res1=Max_node(res1,node{0,dp[j][0].to,dp[j][0].to});
    return res1;
}

node calj_1(int i,int j)  //i,j-1
{
    node res1;
    int ch=mp[a[j]];
    int w=ind[j][ch];
    int an=ind[i+1][ch];

    if(w>j-1-(dp[i][j-1].se)&&w<=j-1)
    {
        res1.fi=dp[i][j-1].fi;
        res1.se=j-w;
        res1.to=res1.fi+res1.se;
    }
    else if(an>i-(dp[i][j-1].fi)&&an<=i)
    {
        res1.fi=i-an;
        res1.se=dp[i][j-1].se+1;
        res1.to=res1.fi+res1.se;
    }
    else
    {
        res1.fi=dp[i][j-1].fi;
        res1.se=dp[i][j-1].se+1;
        res1.to=res1.fi+res1.se;
    }
    int tmp=min(i-j,dp[i][0].to);
    res1=Max_node(res1,node{tmp,0,tmp});
    return res1;
}


int main()
{
    int t;
    scanf("%d",&t);
    int cas=0;
    while(t--)
    {
        cas++;
        int n;
        scanf("%d",&n);
        for(int i=1;i<=n;i++)
        {
            scanf("%d",&a[i]);
            b[i]=a[i];
        }
        sort(b+1,b+1+n);
        int cnt=1;
        int pre=-1;
        memset(mp,0,sizeof(mp));
        for(int j=1;j<=n;j++)
        {
            if(pre!=b[j])
            {
                mp[b[j]]=cnt;
                cnt++;
                pre=b[j];
            }
        }
        memset(vis,0,sizeof(vis));
        for(int i=1;i<=n+1;i++)
        {
            int ch=mp[a[i]];
            for(int j=1;j<=n;j++)
            {
                ind[i][j]=vis[j];
            }
            vis[ch]=i;
        }
        dp[0][0].fi=dp[0][0].se=dp[0][0].to=0;
        int ans=0;
        for(int i=1;i<=n;i++)
        {
            for(int j=0;j<i;j++)
            {
                //dp[i][j].fi=dp[i][j].se=dp[i][j].to=0;
                dp[i][j]=cali_1(i,j);
                if(j) dp[i][j]=Max_node(dp[i][j],calj_1(i,j));
                ans=max(ans,dp[i][j].to);
            }
            dp[i][i]=dp[i][0];
            //vis[1]=0;
        }
        printf("Case #%d: ",cas);
        printf("%d\n",ans);
    }
}

网上暴力+剪枝过的很多,方法虽然有些不同,但核心思想都是类似的——先O(n*n)枚举一个区间[i,j],然后再在[j+1,n]的区间

里找第二个区间O(n),所以总的复杂度是O(n*n*n),不过里面有很多可以剪得部分

我剪了两个地方直接从840ms降到了202ms,

方法一

方法二

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <bitset>
using namespace std;
const int MAXN = 1e3+10;

const int MAX = 1e5+10;
int a[MAXN],b[MAXN];
int dp[MAXN][MAXN];
bitset<MAXN> tmp;

int main()
{
	int t;
	scanf("%d",&t);
	int cas=0;
	while(t--)
	{
		int n;
		scanf("%d", &n);
        for(int i=1;i<=n;i++) scanf("%d", a + i), b[i] = a[i];
        sort(b + 1, b + n + 1);
        int cnt = unique(b + 1, b + n + 1) - b - 1;
        for(int i=1;i<=n;i++) a[i] = lower_bound(b + 1, b + cnt + 1, a[i]) - b;
		memset(dp,0,sizeof(dp));
		for(int i=1;i<=n;i++)
		{
			tmp.reset();
			for(int j=i;j<=n;j++)
			{
				if(tmp[a[j]]) break;
				tmp[a[j]]=1;
				dp[i][j]=j-i+1;
			}
		}
		
		for(int l=2;l<=n;l++)  //区间dp
		{
			for(int i=1;i<=n;i++)
			{
				int j=i+l-1;
				if(j>n) break;
				dp[i][j]=max(dp[i][j],max(dp[i+1][j],dp[i][j-1]));
			}
		}
		int ans=0;
		tmp.reset();
		for(int i=1;i<=n;i++)
		{
			if(n-i+1<=ans) break;  //剪枝
			tmp.reset();
			//r=max(r,i);
			for(int r=i;r<=n;r++)
			{
				if(dp[i][r]!=r-i+1)break;
				
				int res=r-i+1;
				ans=max(ans,res);
				tmp[a[r]]=1;
				int cl,cr;
				cl=cr=r+1;
				while(cl<=n)   //方法1:用x[i](从i出发最远可以得到的不重复的序列)来剪枝
				{             //方法2:用bitset将r后面的每一位标成0/1(1:已经在答案中 0:未被加入答案),然后找[r+1,n]中连续的最多的0的区间
					cr=max(cl,cr);  //我这里用的是类似第二种的
					while(!tmp[a[cr]]&&cr<=n) cr++; 
					if(cr-1>=cl) ans=max(ans,res+dp[cl][cr-1]);
					cl=max(cl,cr); //剪枝
					cl++;
				}


			}
		}
		cas++;
		printf("Case #%d: ",cas);
		printf("%d\n",ans);
	}
}

猜你喜欢

转载自blog.csdn.net/qq_37025443/article/details/84336362
今日推荐