题意:
给你一个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);
}
}