2013: 最大连续和(mx)
时间限制: 8 Sec 内存限制: 128 MB提交: 27 解决: 20
[ 提交][ 状态][ 讨论版]
题目描述
一个长度为n数组A的最大连续和,是指所有满足1≤L≤R≤n的L和R中A[L]+A[L+1]+...+A[R]的最大值。
一次交换操作是指:
(1) 选择两个下标i和j(i ≠ j)
(2)进行赋值,tmp=A[i];A[i]=A[j],A[j]=tmp;
给定一个长度为n的数字,最多进行m次交换操作后,该数组的最大连续和。
输入
第一行两个两个整数n和m。
第二行n个数字,表示数组中的元素。
输出
输出题目要求的答案。
样例输入
10 2
10 -1 2 2 2 2 2 2 -1 10
样例输出
32
提示
对于前100% 的数据,n的范围 [1,1100],m的范围[0,100];
嗯……这道其实说白了是道想法题,本身难度并不是很大,主要考的是思维能力。首先,如果没有交换这个操作,那么这就是道简单的dp,求最大连续和。而本题则多了一个交换的操作。首先,我想到能不能先用dp求出最大连续和,然后将子序列中的小值和子序列外的大值进行交换,来使和更大。这差不多是个贪心,然而,事实证明,这是个错误贪心。如:1 50 -100 50 这个数列求出的最大连续和为1+50=51,再将1与另一个50进行交换,最大值变为50+50=100,然而,正确答案应该是先选1 50 -100,然后将-100与50交换,最大值为1+50+50=101。所以,这是个错误贪心。接下来,别很容易想到暴力的打法,枚举l和r(等于枚举区间),然后将当前区间中的数建立小根堆,区间外的数建立大根堆,然后比较、交换直到大根堆中的最大值小于小根堆中的最小值。这是个n^3 log(n) 的算法,这是个不错的算法,但难以过掉全部的数据。而主要问题的所在就是在与堆的操作需要n log (n)的时间复杂度,能不能把其优化成n的复杂度甚至更少。所以,我们可以想到将堆舍去。直接在刚开始对原数列记录ID并进行排序(从大到小和从小到大都可以,只是在后面的写法上有一点不同)。进行过预处理后,只要在枚举区间的循环内将排好序的数列从大到小扫一遍,如果当前的数的ID在区间内,则答案直接加上该数,如果该数不再区间内,则可交换次数减一,并将该数加入答案(如果可交换次数已经为0则跳过该数)。最后再将答案与max比较,最后输出max即可。(这样可以过掉,但并非最快的算法,正解要用到链表,很难打)。
#include<cstdio>
int a[10000],b[10000];
void kp(int l,int r) //结构体不会用所以手打快排了,这里可以用结构体替代
{
int i=l;
int j=r;
int x=a[(l+r)/2];
int x1=b[(l+r)/2];
int t=0;
while (i<=j)
{
while (a[i]<x || (a[i]==x && b[i]>x1)) i++;
while (a[j]>x || (a[j]==x && b[j]<x1)) j--;
if (i<=j){
t=a[i];
a[i]=a[j];
a[j]=t;
t=b[i];
b[i]=b[j];
b[j]=t;
i++;
j--;
}
}
if (l<j) kp(l,j);
if (i<r) kp(i,r);
}
int main()
{
int n,m,n1,m1,ans=0,max1=0;
scanf("%d%d",&n,&m);
for (int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
b[i]=i;
}
kp(1,n);
for (int i=1;i<=n;i++)
for (int j=i;j<=n;j++)
{
ans=0;
n1=j-i+1;//区间内可放的数的个数
m1=m;//可交换次数
for (int k=n;k>=1;k--)
{
if (n1==0) break;
if (b[k]>=i && b[k]<=j) { n1=n1-1;ans+=a[k];}
else { if (m1>0) {m1=m1-1;ans+=a[k];n1=n1-1; } } //不在区间内的情况
}
if (ans>max1) max1=ans;
}
printf("%d",max1);
}