题目:http://codeforces.com/problemset/problem/455/E
题意:给定数组a,及f的定义:
f[1][j] = a[j]; 1 <= j <= n
f[i][j] = min(f[i-1][j-1], f[i-1][j]) + a[j]; 2 <= i <= j <= n
给定q个询问,每个询问为l,r,求f[l][r]
My solution:
- 写一些小数据就可以发现,其实对于一个询问l,r,其实等价于:
从[r-l+1, r]这个区间内取l个数,使其和最小。但是比较特殊的是,一个数可以取多次,并且如果取了一个x,那么[x+1,r]间的所有数必须得取。
例如,对于n=3, a = {2, 1, 3}
询问l=3, r=3的取法有:3+3+3=9, 3+3+1=7, 3+1+1=5, 3+1+2= 6;答案为3+1+1=5
2.设答案f[l][r]的询问答案合法区间是在[x, r]这一段取得的,我们还可以发现如下的性质:
1)a[x]一定是[x,r]中最小的,否则存在 x<=y<=r, a[y] <= a[x],比[x,r]更优的区间[y, r]
除[y, r]的共同区间外,剩下的l-y-r个[y,r]区间可以全取a[y],显然比[x,r]更小
2)a[x+1]~a[y]各取一个,剩下的全取a[x],因为a[x]是区间最小元素,取越多答案越小
3.基于2我们可以维护一个递增的序列来求答案,但是这样还是不够,妥妥TlE
只能看下决策之间有什么关系了;
对于询问(l,r)不妨设两个决策k<j,并且决策k优于j
那么 (sum[r] - sum[k]) - (l - (r - k)) * a[k] <= (sum[r] - sum[j]) - (l - (r - j)) * a[j];
整理一下式子:
(sum[k] - a[k]*k) - (sum[j] - a[j]*j) / (a[k] - a[j]) <= l - r;
这不就是个斜率的式子,剩下的就是维护一个凸壳即可
4.具体的话对于询问按照r排序,然后离线做
然后二分一左边界,找到合法区间完,再二分找到合法的斜率。具体看代码吧
code:
1 #include <iostream> 2 #include <cstdio> 3 #include <cstring> 4 #include <string> 5 #include <cmath> 6 #include <algorithm> 7 #include <vector> 8 #include <cstdlib> 9 #include <sstream> 10 #include <fstream> 11 #include <list> 12 #include <deque> 13 #include <queue> 14 #include <stack> 15 #include <map> 16 #include <set> 17 #include <bitset> 18 #include <cctype> 19 #include <ctime> 20 #include <utility> 21 #define M0(x) memset(x, 0, sizeof(x)) 22 #define pb push_back 23 #define mp make_pair 24 #define x first 25 #define y second 26 #define vii vector< pair<int, int> >::iterator 27 using namespace std; 28 const int maxn = 100010; 29 vector< pair<int, int> > ask[maxn]; 30 int n, m; 31 long long sum[maxn], ans[maxn]; 32 int s[maxn], top, a[maxn]; 33 34 inline double slope(int k, int j){ 35 double yk = sum[k] - (double)k * a[k], yj = sum[j] - (double)j * a[j]; 36 return (yk - yj) / (a[k] - a[j]); 37 } 38 39 void init(){ 40 for (int i = 1; i <= n; ++i) 41 scanf("%d", a+i), sum[i] = sum[i-1] + a[i]; 42 for (int i = 0; i <= n; ++i) ask[i].clear(); 43 scanf("%d", &m); 44 int l, r; 45 for (int i = 0; i < m; ++i){ 46 scanf("%d%d", &l, &r); 47 ask[r].pb(mp(l, i)); 48 } 49 } 50 51 void solve(){ 52 int top = 0; 53 int l, r, mid, pos; 54 for (int i = 1; i <= n; ++i){ 55 while (top > 0 && a[s[top]] >= a[i]) --top; 56 while (top > 1 && slope(s[top], i) >= slope(s[top-1], i)) --top; 57 s[++top] = i; 58 for (vii it = ask[i].begin(); it != ask[i].end(); ++it){ 59 l = lower_bound(s+1, s+top+1, i-it->x+1) - s; 60 r = top-1, pos = i; 61 while (l <= r){ 62 mid = (l + r) >> 1; 63 if (slope(s[mid], s[mid+1]) <= it->x - i) pos = s[mid], r = mid - 1; 64 else l = mid + 1; 65 } 66 ans[it->y] = sum[i] - sum[pos] + (long long)a[pos]*(it->x+pos-i); 67 } 68 } 69 for (int i = 0; i < m; ++i) 70 printf("%I64d ", ans[i]); 71 } 72 int main(){ 73 // freopen("a.in","r",stdin); 74 // freopen("a.out","w",stdout); 75 while (scanf("%d", &n) != EOF){ 76 init(); 77 solve(); 78 } 79 return 0; 80 }