题目描述:
为了迎接奥运,市体育局举行手拉手大包围活动,开始时N个人手拉手围成一个圈。后来这些人中的一些按顺序向里面出圈形成一个新圈。从而使原圈形成一个从高到低,最低与最高连接的圈。新圈重复相同的操作,直到没有人要出圈为止。问最少要形成多少个这样的圈。
首先翻译题意: 由题目很容易可以看出来题意为求n个数全部选取完,求里面最少有多少个下降子序列。
如果做过noip拦截导弹的读者最少下降子序列的个数等于最大不降子序列的长度,在此不多赘述。
现在问题来了有一个n<=1000的环,从任意一个点开始,求一个最大不降子序列。枚举起点需要n,裸求最大不降子序列需要n^2的复杂度,是会超时的,在我们想不出来贪心方法的时候,只能优化最大不降子序列复杂度了。
有一种nlogn求最大不降子序列的算法,dp[i]表示长度为i的最大不降子序列的最后一个位置的数值,如果长度相等则取最小值,那么我们会发现一个有意思的性质,就是这个dp[i]是单调不减的,那么我们枚举接的时候就可以使用二分的思想来找到这个位置了。如果mid的位置的值比hei[mid]小了就将右段点改为mid。注意在求出结果的时候注意要与hei[]判断是r还是l,还有一种情况是所有的都接不了,二分难以判断,所以记住特判即可。
下附AC代码。
#include<iostream>
#include<stdio.h>
#include<string.h>
#include<algorithm>
#define maxn 2005
using namespace std;
int n;
int hei[maxn];
int dp[maxn];
int callis(int start)
{
int len=1;
dp[len]=hei[start];
for(int i=start+1;i<start+n;i++)
{
if(hei[i]>=dp[len])
{
dp[++len]=hei[i];
continue;
}
int l=1;
int r=len;
while(r-l>1)
{
int mid=(l+r)>>1;
if(hei[i]>=dp[mid])
l=mid;
else r=mid;
}
int now;
if(hei[i]>=dp[r])
now=r;
else if(hei[i]>=dp[l])
now=l;
else
now=0;
dp[now+1]=min(dp[now+1],hei[i]);
}
return len;
}
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
scanf("%d",&hei[i]);
hei[i+n]=hei[i];
}
int ans=987654321;
for(int i=1;i<=n;i++)
{
memset(dp,0,sizeof(dp));
ans=min(ans,callis(i));
}
printf("%d\n",ans);
}
569 Milking Time:
题目描述:
在一个数轴上可以摆M个线段,每个线段的起始终止端点给定(为整数),且每个线段有一个分值,问如何从中选取一些线段使得任意两个线段之间的距离大于R。每一条线段属于[0,N]。如何选择这些线段,使得分值之和最大?
首先翻译题意:题意已经很直白了,没有什么需要翻译的。
我们应该可以很容易想到设计dp[i]表示考虑到第i个时的最大价值。对右端点进行排序后就是枚举在i之前的j里,j的右端点-i的左端点>=r 里面dp[j]的最大值即类似于最长上升子序列的求法。
下附AC代码。
#include<iostream>
#include<stdio.h>
#include<string.h>
#include<algorithm>
#define maxn 1005
using namespace std;
int l,n,r;
struct nod
{
int l,r,v;
}seg[maxn];
bool operator < (nod a,nod b)
{
if(a.r!=b.r)
return a.r<b.r;
return a.l<b.l;
}
int dp[maxn];
int main()
{
scanf("%d%d%d",&l,&n,&r);
for(int i=1;i<=n;i++)
scanf("%d%d%d",&seg[i].l,&seg[i].r,&seg[i].v);
sort(seg+1,seg+1+n);
dp[0]=0;seg[0].l=-123213;seg[0].r=-12345;
int ans=0;
for(int i=1;i<=n;i++)
{
dp[i]=seg[i].v;
for(int j=0;j<i;j++)
if(seg[i].l-seg[j].r>=r)
{
dp[i]=max(dp[i],dp[j]+seg[i].v);
}
ans=max(ans,dp[i]);
}
printf("%d\n",ans);
}
429词链:
题目描述
给定一个仅包含小写字母的英文单词表,其中每个单词最多包含50个字母。
如果一张由一个词或多个词组成的表中,每个单词(除了最后一个)都是排在它后面的单词的前缀,则称此表为一个词链。例如下面的单词组成了一个词链:
i
int
integer
而下面的单词不组成词链:
integer
intern
请在给定的单词表中取出一些词,组成最长的词链。最长的词链就是包含单词数最多的词链。
数据保证给定的单词表中,单词互不相同,并且单词按字典顺序排列。
题意为:如果一个词的前缀为另一个词,那么这一个词就可以接到另一个词后面,求可以连接成的最长的长度。
可以看出来这道题还是一道类似于最长上升子序列的题目,dp[i]表示考虑到以i结尾的最长长度,每次直接考虑可以接在它之前的词语j里dp[j]的最大值。由于n的范围过大,不能直接使用裸n^2复杂度的最长上升子序列,如果使用nlogn的方法也得不到一个单调可二分的性质。
由于已经排好序,所以我们可以推出一个有意思的性质,倒序从i开始枚举j,当我们遇到了一个能接到i前面的词语j时,这个j一定是所有j里面的最大值。证明如下:因为是已经排好序的,那么如果倒序枚举到了可匹配的一个点j,那么在它之后枚举到的可匹配的长度一定只会比单词j的长度更短,那么单词j已经由这个之前的答案更新而来,所以倒序枚举到的第一个可匹配的点就是我们可以更新答案的那个点了。
下附AC代码。
#include<iostream>
#include<stdio.h>
#include<string.h>
#define maxn 10005
using namespace std;
int n;
string s[maxn];
int dp[maxn];
int main()
{
cin>>n;
for(int i=1;i<=n;i++)
{
cin>>s[i];
}
int ans=0;
for(int i=1;i<=n;i++)
{
dp[i]=1;
for(int j=i-1;j>=1;j--)
{
if(s[i].find(s[j])==0)
{
dp[i]=max(dp[i],dp[j]+1);
break;
}
}
ans=max(ans,dp[i]);
}
cout<<ans<<endl;
}