标签:code 思想 正数 相交 style bind clipboard amp 压缩
第1行:2个数N和M,中间用空格分隔。N为整数的个数,M为划分为多少段。(2 <= N , M <= 50000) 第2 - N+1行:N个整数(-10^9 <= a[i] <= 10^9)
输出这个最大和
7 2
-2
11
-4
13
-5
6
-2
26
v2果然是难了,还用dp就超时了,学无止境,本道题采用特别巧妙的贪心思想的方法,把相邻的正数或者负数,都加到一起,形成新的序列,也就是正数和负数交叉的序列,假如原序列是1 2 -3 -4 3,压缩后:
3 -7 3,存到一个新的数组里,我们用ans记录所有正数的和,显然新数组里每一项都是一段,我们还要记录所有正数段得到个数,我们要保持正数段的个数在m之内,所以我们把所有的数段的绝对值,以及位置,存到一个set里,
也可以用链表,这样做的目的是用负数填补空缺,从而把两个正数段合并为一段,我们需要知道每一段左右分别是谁,方便合并,不是单纯的下表加1和减1,因为合并多了,中间会有很多无效的位置,当然了用链表就不需要考虑这些问题,直接删除节点即可。
那么怎么来合并呢,set里存绝对值和位置组成的pair,这样排序就按照绝对值排序了,每次选择绝对值最小的,假如这个数是正的,那么就用ans减去,然后跟两边的合并,因为set里所有的负数绝对值都比他大,所以ans肯定不包括它了,如果是负数,也是ans减去绝对值,负数跟两边的整数合并了,实际上让ans减少了。
代码:
#include <iostream> #include <cstdlib> #include <cstdio> #include <set> using namespace std; typedef long long ll; int n,m; ll d,last; ll s[50005]; int l[50005],r[50005]; int sc; void modify(int cur) {///修改左右相邻结点的下标 int ll = l[cur],rr = r[cur]; if(ll) { r[ll] = rr; } if(rr) { l[rr] = ll; } } int main() { while(~scanf("%d%d",&n,&m)) { ll sum = 0,ans = 0,c = 0; set<pair<ll,int> > ss; for(int i = 0;i < n;i ++) { scanf("%lld",&d); if(d * last < 0) { s[++ sc] = sum; if(sc == 1 && sum < 0) sc --; sum = d; } else sum += d; last = d; } if(sum > 0) s[++ sc] = sum; c = (sc + 1) / 2; for(int i = 1;i <= sc;i ++) { ss.insert(make_pair(abs(s[i]),i)); ans += (s[i] > 0 ? s[i] : 0); l[i] = i - 1; r[i] = i + 1; } r[sc] = 0; while(c > m) { int cur = ss.begin() -> second; ss.erase(ss.begin()); if(s[cur] < 0 && (!l[cur] || !r[cur])) continue;///如果是负数,而且是处在首尾的位置那么就没必要合并了。 ans -= abs(s[cur]); s[cur] += s[l[cur]] + s[r[cur]]; if(l[cur]) { ss.erase(make_pair(abs(s[l[cur]]),l[cur])); modify(l[cur]); } if(r[cur]) { ss.erase(make_pair(abs(s[r[cur]]),r[cur])); modify(r[cur]); } if(s[cur]) ss.insert(make_pair(abs(s[cur]),cur)); c --; } printf("%lld\n",ans); } return 0; }
标签:code 思想 正数 相交 style bind clipboard amp 压缩
原文地址:https://www.cnblogs.com/8023spz/p/10912135.html