转移自一段连续的区间的最小值的可以确定要用线段树。关键点是如何构造这个线段树的问题。
用一个单调栈来维护这一结构,维护使队列单调递减所以 q[tail - 2] + 1 到q[tail - 1]的最大值是energy[q[tail - 1]]在线段树中插入这个段值,如果弹栈的话把这段值减去。
每次转移dp[i] = query(next[i] + 1, i, 1);求区间的最小值。
由于朴素的转移方程为dp[i] = dp[k] + minn[k + 1][i]
然后把dp[i]插入到i + 1节点中以为后面的转移做准备。
这个题还是很难想的,我的思维深度是没有到这里,只能想到求区间最值,却想不到用一个单调的栈维护区间,但是相比于那个上次的那个最短路还是简单一些的(个人感觉)。
代码如下:
/*
* =====================================================================================
*
* Filename: dp.cpp
*
* Description: a hard dp with segment tree
*
* Version: 1.0
* Created: 2011年07月27日 10时37分59秒
* Revision: none
* Compiler: gcc
*
* Author: ronaflx
* Company: hit-ACM-Group
*
* =====================================================================================
*/
#include <cstdlib>
#include <cctype>
#include <cstring>
#include <cstdio>
#include <cmath>
#include <ctime>
#include <climits>
#include <algorithm>
#include <functional>
#include <numeric>
#include <vector>
#include <map>
#include <set>
#include <queue>
#include <stack>
#include <bitset>
#include <list>
#include <string>
#include <iostream>
#include <sstream>
#include <fstream>
#include <iomanip>
#include <stdexcept>
#include <utility>
#include <cassert>
#include <complex>
using namespace std;
#define LEFT(i) ((i) << 1)
#define RIGHT(i) (((i) << 1) | 1)
#define MID(i) ((l[i] + r[i]) >> 1)
#define CC(i, v) memset(i, v, sizeof(i))
#define REP(i, l, n) for(int i = l;i < int(n);++i)
#define FOREACH(con, i) for(__typeof(con.begin()) i = con.begin();i != con.end();++i)
typedef long long LL;
const int N = 100001;
const LL INF = 1000000000;
class segmentTree
{
public:
LL minn[N * 4], delta[N * 4];
int l[N * 4], r[N * 4];
void build(int ll, int rr, int n)
{
l[n] = ll, r[n] = rr, minn[n] = 0, delta[n] = 0;
int mid = MID(n);
if(ll == rr) return;
build(ll, mid, LEFT(n));
build(mid + 1, rr, RIGHT(n));
}
void updateSon(int n, LL v)
{
minn[n] += v;
delta[n] += v;
}
void update(int n)
{
if(delta[n] == 0) return;
updateSon(LEFT(n), delta[n]);
updateSon(RIGHT(n), delta[n]);
delta[n] = 0;
}
void insert(int ll, int rr, long long v, int n)
{
if(l[n] == ll && r[n] == rr)
{
updateSon(n, v);
return;
}
update(n);
int mid = MID(n);
if(rr <= mid)
insert(ll, rr, v, LEFT(n));
else if(mid < ll)
insert(ll, rr, v, RIGHT(n));
else
{
insert(ll, mid, v, LEFT(n));
insert(mid + 1, rr, v, RIGHT(n));
}
minn[n] = min(minn[LEFT(n)], minn[RIGHT(n)]);
}
LL query(int ll, int rr, int n)
{
if(l[n] == ll && r[n] == rr)
return minn[n];
int mid = MID(n);
update(n);
LL ans;
if(rr <= mid)
ans = query(ll, rr, LEFT(n));
else if(mid < ll)
ans = query(ll, rr, RIGHT(n));
else ans = min(query(ll, mid, LEFT(n)), query(mid + 1, rr, RIGHT(n)));
minn[n] = min(minn[LEFT(n)], minn[RIGHT(n)]);
return ans;
}
}tree;
int type[N], next[N], q[N], last[N];
LL dp[N], energy[N] = {INF};
int main()
{
int t, n;
scanf("%d", &t);
while(t--)
{
scanf("%d", &n);
for(int i = 1;i <= n;i++)
scanf("%d", &type[i]);
for(int i = 1;i <= n;i++)
scanf("%lld", &energy[i]);
CC(last, 0);
tree.build(0, n + 1, 1);
for(int i = 1;i <= n;i++)
{
next[i] = last[type[i]];
last[type[i]] = i;
}
int tail = 0;
q[tail++] = 0;
for(int i = 1;i <= n;i++)
{
while(tail && energy[q[tail - 1]] <= energy[i])
{
tree.insert(q[tail - 2] + 1, q[tail - 1], -energy[q[tail - 1]], 1);
tail--;
}
q[tail++] = i;
tree.insert(q[tail - 2] + 1, i, energy[i], 1);
dp[i] = tree.query(next[i] + 1, i, 1);
tree.insert(i + 1, i + 1, dp[i], 1);
}
printf("%lld\n", dp[n]);
}
return 0;
}
ORZ 70教主,每次都能按时完成题目……祝你今年拿金牌!!