【poj3017】 Cut the Sequence
http://poj.org/problem?id=3017 (题目链接)
题意
给出一个数列要求将它分割成许多块,每块的数的和不超过m,要求每块中最大的数之和最小。
Solution
这道题真的很不错啊。
可以很快写出dp方程:${f[i]=min(f[j]+max(a[j+1],a[j+2]···a[i]))}$。数据范围太大,我们必须要想办法优化这个方程。${O(n)}$的状态肯定是没办法优化到${O(1)}$了,想想怎么把转移优化到${O(logn)}$甚至${O(1)}$呢?似乎完全不可做的样子。
终于还是膜拜了题解,发现:${f[i]}$一定是单调不降的。那么我们对于一个最大的数${a[k]}$,只要选择在它的管辖范围内尽可能靠前的点就好了。什么意思呢,就是:
设${a[j+1], a[j+2], ...a[i]}$中值最大的下标为${k}$
设${x}$为${[j+1,k]}$的任意一个下标,则${a[x],a[x+1],....a[i]}$的最大值的下标显然也是k了
由${f}$的非递减性,${f[j+1] + a[k] <= f[j+2]+a[k].....<= f[k - 1] + a[k]}$
很显然,我们只要取${f[j+1]+a[k]}$就可以了。
于是我们用单调队列记录一个递减的序列${a[k]}$,因为队首不一定是最优,所以我们还要用平衡树记录${a[k]+f[j]}$来维护它。
当当前${i}$到队首的距离超过m时,pop队首;当即将插入的元素大于队尾元素时,pop队尾。
细节
multiset的运用。
代码
// poj3017 #include<algorithm> #include<iostream> #include<cstdlib> #include<cstring> #include<cstdio> #include<cmath> #include<set> #define LL long long #define inf 2147483640 #define Pi acos(-1.0) #define free(a) freopen(a".in","r",stdin),freopen(a".out","w",stdout); using namespace std; const int maxn=100010; int q[maxn],a[maxn],n; LL m,sum[maxn],f[maxn]; int main() { scanf("%d%lld",&n,&m); for (int i=1;i<=n;i++) scanf("%d",&a[i]); for (int i=1;i<=n;i++) sum[i]=sum[i-1]+a[i]; multiset<int> s; int k=0,l=1,r=0; for (int i=1;i<=n;i++) { if (a[i]>m) {printf("-1");return 0;} while (sum[i]-sum[k]>m) k++; while (l<=r && q[l]<=k) {if (l<r) s.erase(a[q[l+1]]+f[q[l]]);l++;} //超过m while (l<=r && a[q[r]]<=a[i]) {if (l<r) s.erase(a[q[r]]+f[q[r-1]]);r--;} //当前a[i]比队尾元素大,pop队尾元素,并在平衡树中删除 q[++r]=i; if (l<r && i>q[r-1]) s.insert(a[i]+f[q[r-1]]); int tmp=*s.begin(); f[i]=a[q[l]]+f[k]; if (l<r && tmp<f[i]) f[i]=tmp; } printf("%lld",f[n]); return 0; }
This passage is made by MashiroSky.