1、单调递增子序列(dp)。2、最长公共子序列(dp)。3、最大连续子序列的和(dp)。
上述问题,都是经典的dp算法实例,但是针对问题的变形,就很难再用dp来处理了,反正我不会,暴力出奇迹,可以用分治的方法同样解决问题1和3。
①最大连续子序列的和;最大连续子序列的和,要求个数必须大于1;最大连续子序列的和的绝对值,要求可以改变k个元素的正负号。
思路:前两种,我以前总结过。第三种,这几天刚遇到,刚开始毫无思路,最后看到分治的思路,暴力了一波。分治解法:先找出mid,左右找(left,mid)和(mid+1,right)的最大值leftSum和rightSum;再找横跨mid的最大值sum,最后比较leftSum、rightSum、sum三个值的大小,返回最大值。左右找的同时,递归的k值不同,左右改变的正负号之后为k。
#include <iostream>
#include <algorithm>
#include <math.h>
using namespace std;
int n,k;
int a[1000005];
int Max(int l,int r,int k){
int sum=0;
if(l==r){
if(a[l]>0) sum=a[l]; //可以判断k的值
else if(k>0){
sum=-a[l];
k--;
}
else sum=0;
}else{
int mid=(l+r)/2;
for(int kk=0;kk<k;kk++){
int leftsum=Max(l,mid,kk);
int rightsum=Max(mid+1,r,k-kk);
int lefts=0,s1 = 0;
int judge=kk;
for(int i=mid ;i>=l;i--){
if(a[i]<0&&judge>0){
lefts-=a[i];
judge--;
}else
lefts+=a[i];
if(lefts>s1){
s1=lefts;
}
}
int rights=0,s2=0;
for(int j=mid+1;j<=r;j++){
if(a[j]<0&&(k-judge)>0){
rights-=a[j];
judge--;
}else
rights+=a[j];
if(rights>s2){
s2=rights;
}
}
sum=s1+s2;
if(max(leftsum,rightsum)>sum)
sum=max(leftsum, rightsum);
}
}
return sum;
}
int Min(int l,int r,int k){
int sum=0;
if(l==r){
if(a[l]<0) sum=a[l]; //可以判断k的值
else if(k>0){
sum=-a[l];
k--;
}
else sum=0;
}else{
int mid=(l+r)/2;
for(int kk=0;kk<k;kk++){
int leftsum=Min(l,mid,kk);
int rightsum=Min(mid+1,r,k-kk);
int lefts=0,s1 = 0;
int judge=kk;
for(int i=mid ;i>=l;i--){
if(a[i]>0&&judge>0){
lefts-=a[i];
judge--;
}else
lefts+=a[i];
if(lefts<s1){
s1=lefts;
}
}
int rights=0,s2=0;
for(int j=mid+1;j<=r;j++){
if(a[j]>0&&(k-judge)>0){
rights-=a[j];
judge--;
}else
rights+=a[j];
if(rights<s2){
s2=rights;
}
}
sum=s1+s2;
if(min(leftsum,rightsum)<sum)
sum=min(leftsum,rightsum);
}
}
return sum;
}
int main()
{
while(cin>>n){
for(int i=0;i<n;i++){
cin>>a[i];
}
cin>>k;
cout<<max(abs(Min(0,n-1,k)),Max(0,n-1,k))<<endl;
}
}
/*
8
1 -2 3 10 -4 7 2 -5
3
8
1 -2 -3 -10 4 -7 2 -5
3
*/
参考资料:
https://blog.csdn.net/u014257192/article/details/52971343
②那么我就想了,如果求单调递增子序列,也可以改变k个值得大小,那么求最长的长度?能用同样的方法吗?
感觉没意义了,只要统计两个递增数字自己的最大间隔就行了,然后与k对比。在原基础要么加上最大间隔,要么加上k。
如:7、6、5、8、4、3、9、2、1
但是针对连续递增子序列的最大长度,注意这里就不能用到dp了,起始位置不同决定长度的不同,可以暴力起始位置,时间复杂度为O(N^2),同理也可以用分治。好处是对于修改k个值后,判断最长长度。
参考资料:
https://blog.csdn.net/blue_hpu/article/details/63320016
③三角行的判断(任意两边之和大于第三边或最小两边之和大于第三边)
思路:方法一和方法二都是用到了简单的暴力方法,方法三在方法二的基础上增加了剪枝。定两条最小边,如果找不到第三条边,那么整个程序就可以终止了。因为是有序的序列,在小值得时候不满足了,那么往后值越来越大就更不可能满足条件了。找到第一个满足条件的k,那么后面的n-k个也满足。
#include <iostream>
using namespace std;
int main()
{
int t,n,sum;
int a[2001]; //存储木棒长度
cin>>t; //t组数据
while(t--){
cin>>n; //每组n个木棒
sum=0;
for(int i=0;i<n;i++){
cin>>a[i];
}
for(int i=0;i<n;i++){
for(int j=i+1;j<n;j++){
for(int k=j+1;k<n;k++){
//任意两边之和大于第三边
if(a[i]+a[j]>a[k]&&a[i]+a[k]>a[j]&&a[j]+a[k]>a[i]){
sum++;
}
}
}
}
cout<<sum<<endl;
}
return 0;
}
#include <iostream>
#include <algorithm>
using namespace std;
int main()
{
int t,n,sum;
int a[2001]; //存储木棒长度
cin>>t; //t组数据
while(t--){
cin>>n; //每组n个木棒
sum=0;
for(int i=0;i<n;i++){
cin>>a[i];
}
sort(a,a+n); //排序
for(int i=0;i<n;i++){
for(int j=i+1;j<n;j++){
for(int k=j+1;k<n;k++){
//最小两边之和大于第三边
if(a[i]+a[j]>a[k]){
sum++;
}else{
break;
}
}
}
}
cout<<sum<<endl;
}
return 0;
}
#include <iostream>
#include <algorithm>
using namespace std;
int main()
{
int t,n,sum,flag;
int a[2001]; //存储木棒长度
cin>>t; //t组数据
while(t--){
cin>>n; //每组n个木棒
sum=0;
flag=0;
for(int i=0;i<n;i++){
cin>>a[i];
}
sort(a,a+n); //排序
for(int i=0;i<n;i++){
for(int j=i+1;j<n;j++){
//排序后,强力剪枝
int k; //选择第一个符合条件的k值
for(k=j+1;k<n;k++)
if(a[i]+a[j]>a[k]) break;
if(k==n){ //一旦出现找不到的情况,数据越来越大,后面的更没戏
flag=1;break;
}else{
sum+=(n-k);
}
}
if(flag==1) break;
}
cout<<sum<<endl;
}
return 0;
}