海亮 7.14 dp专题2
海亮 7.14 dp专题2
学考日 但是题还是要补的()
New Year Domino
不难发现区间内的答案就是将区间内所有多米诺骨牌放倒 剩余的空位个数
那么我们可以倍增优化一下 \(f[i][j]\) 表示从第\(i\)个节点 越过\(2^j\)段空位能到达的节点编号 \(g[i][j]\)表示从节点 \(i\) 到节点 \(f[i][j]\) 需要的最小花费
查询时直接从大向小跳即可
注意 如果可以覆盖到所有点 那么\(f[i][0]\)就是\(0\) 需要特判这一点
#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
#define int long long
const int N = 2e5 + 5;
int read ()
{
int x = 0 , f = 1;
char ch = cin.get();
while ( !isdigit ( ch ) ) { if ( ch == '-' ) f = -1; ch = cin.get(); }
while ( isdigit ( ch ) ) { x = ( x << 1 ) + ( x << 3 ) + ( ch ^ 48 ); ch = cin.get(); }
return x * f;
}
int n , p[N] , l[N] , q , f[N][31] , g[N][31] , len[N];
//如果越界 father就是0
signed main ()
{
ios::sync_with_stdio(false);
cin.tie(0) , cout.tie(0);
n = read();
for ( int i = 1 ; i <= n ; i ++ ) p[i] = read() , l[i] = read();
q = read();
for ( int i = n - 1 ; i ; i -- )
{
int temp = i + 1; len[i] = p[i] + l[i];
while ( temp && p[temp] <= p[i] + l[i] ) len[i] = max ( len[i] , len[temp] ) , temp = f[temp][0];
f[i][0] = temp , g[i][0] = p[temp] - len[i];
}
for ( int j = 1 ; j <= 30 ; j ++ ) for ( int i = 1 ; i <= n ; i ++ ) f[i][j] = f[f[i][j-1]][j-1] , g[i][j] = g[f[i][j-1]][j-1] + g[i][j-1];
while ( q -- )
{
int l = read() , r = read();
if ( f[l][0] > r ) cout << 0 << endl;
else
{
int res = 0;
for ( int j = 30 ; j >= 0 ; j -- ) if ( f[l][j] && f[l][j] <= r ) res += g[l][j] , l = f[l][j];
cout << res << endl;
}
}
return 0;
}
Sum Over Zero
非常艰难的一道题 因为需要用到\(sum[0]\)的值进行离散化而调了很长时间
最终决定弃用数组离散化 改用\(vector\)
考虑朴素\(dp\):\(f[i]=max(f[i-1],f[j]+i-j)\)\((sum[i]-sum[j]\ge0)\)
这个东西可以将\(f[j]-j\)拆出来 用动态开点线段树优化解决
将\(sum[i]\)作为值域下标 \(f[i]-i\)作为值插入
那么在查询的时候查询所有下标小于等于\(sum[j]\)的值的最大值即可
必须注意 \(sum[0]\)的值不一定是最小的 所以需要将\(sum[0]\)的值插进去离散化
#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
#define mid ((l+r)>>1)
#define ls(p) t[p].son[0]
#define rs(p) t[p].son[1]
#define lson ls(p),l,mid
#define rson rs(p),mid+1,r
#define int long long
const int N = 2e5 + 5;
const int inf = 0x3f3f3f3f3f3f3f3f;
int read ()
{
int x = 0 , f = 1;
char ch = cin.get();
while ( !isdigit ( ch ) ) { if ( ch == '-' ) f = -1; ch = cin.get(); }
while ( isdigit ( ch ) ) { x = ( x << 1 ) + ( x << 3 ) + ( ch ^ 48 ); ch = cin.get(); }
return x * f;
}
int n , a[N] , f[N] , rt , sum[N];
struct DQY
{
struct node { int son[2] , val; } t[N<<5];
int tot = 0;
int new_node ( int p ) { t[++tot] = { { 0 , 0 } , -inf }; return tot; }
void upd ( int &p , int l , int r , int x , int val )
{
if ( !p ) p = new_node(p);
t[p].val = max ( t[p].val , val );
if ( l == r ) return;
if ( x <= mid ) upd ( lson , x , val );
else upd ( rson , x , val );
}
int query ( int p , int l , int r , int x , int y )
{
if ( !p ) return -inf;
if ( x <= l && r <= y ) return t[p].val;
int res = -inf;
if ( x <= mid ) res = max ( res , query ( lson , x , y ) );
if ( mid + 1 <= y ) res = max ( res , query ( rson , x , y ) );
return res;
}
}T;
vector<int> lsh;
signed main ()
{
ios::sync_with_stdio(false);
cin.tie(0) , cout.tie(0);
n = read();
for ( int i = 1 ; i <= n ; i ++ ) a[i] = read() , sum[i] = sum[i-1] + a[i];//维护一个前缀数组 这样一个合法的子段[j+1,i]就是a[i]-a[j]>=0的了
for ( int i = 0 ; i <= n ; i ++ ) lsh.push_back(sum[i]);
sort ( lsh.begin() , lsh.end() );
unique ( lsh.begin() , lsh.end() );
int sz = lsh.size();
for ( int i = 0 ; i <= n ; i ++ ) sum[i] = lower_bound ( lsh.begin() , lsh.end() , sum[i] ) - lsh.begin() + 1;
T.upd ( rt , 1 , sz , sum[0] , 0 );
for ( int i = 1 ; i <= n ; i ++ )
{
f[i] = max ( f[i-1] , T.query ( rt , 1 , sz , 1 , sum[i] ) + i );
T.upd ( rt , 1 , sz , sum[i] , f[i] - i );
}
cout << f[n] << endl;
return 0;
}
Hot Start Up (hard version)
首先可以想到朴素\(dp\): \(f[i][j][k]\)表示到第\(i\)个程序 \(CPU1\)上一个跑的是\(j\) \(CPU2\)上一个跑的是\(k\)的最短时间
考虑优化 \(a[i]\)这个程序一定会被\(CPU\)跑 那么我们可以钦定一个\(CPU\)跑这个程序(两个\(cpu\)本质相同 可以交换)
也就是\(dp[i][j]\)表示当前进行到第\(i\)个程序 在一个\(CPU\)上运行完 另一个 \(CPU\) 上一次运行的程序是\(j\)
分两种情况转移:
- \(a[i]\)和\(a[i-1]\)在同一个\(cpu\)上运行 也就是这次的另一个\(cpu\)上运行的是程序\(a[i-1]\) 这个\(cpu\)上次运行\(a[i-1]\)
dp[i][j] = min(dp[i][j],dp[i - 1][j] + (a[i] == a[i - 1] ? hot[a[i]] : cold[a[i]]));
- \(a[i]\)和\(a[i-1]\)在不同的\(cpu\)上运行 也就是这次的另一个\(cpu\)上运行的是程序\(a[i-1]\) 这个\(cpu\)上次运行\(j\) 这次运行\(a[i]\)
dp[i][a[i - 1]] = min(dp[i][a[i - 1]],dp[i - 1][j] + (a[i] == j ? hot[a[i]] : cold[a[i]]));
这个已经可以过\(easy\ version\)了
那么对于\(hard\ version\) 我们需要用线段树来优化
代码里加了一些解释()
#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
#define mid (l+r>>1)
#define ls p<<1
#define rs p<<1|1
#define lson ls,l,mid
#define rson rs,mid+1,r
#define int long long
const int N = 3e5 + 5;
const int inf = 0x3f3f3f3f3f3f3f3f;
int read ()
{
int x = 0 , f = 1;
char ch = cin.get();
while ( !isdigit ( ch ) ) { if ( ch == '-' ) f = -1; ch = cin.get(); }
while ( isdigit ( ch ) ) { x = ( x << 1 ) + ( x << 3 ) + ( ch ^ 48 ); ch = cin.get(); }
return x * f;
}
int n , k , a[N] , cold[N] , hot[N];
struct DQY
{
struct node { int val , add; } t[N<<2];
void up ( int p ) { t[p].val = min ( t[p<<1].val , t[p<<1|1].val ); }
void changeadd ( int p , int val ) { t[p].val += val , t[p].add += val; }
void down ( int p )
{
if ( t[p].add )
{
changeadd ( ls , t[p].add );
changeadd ( rs , t[p].add );
t[p].add = 0;
}
}
void build ( int p , int l , int r )
{
if ( l == r ) return t[p].val = inf , void ();
build ( lson ) , build ( rson ) , up(p);
}
void minn ( int p , int l , int r , int x , int val )
{
if ( l == r ) return t[p].val = min ( t[p].val , val ) , void();
down(p);
if ( x <= mid ) minn ( lson , x , val );
else minn ( rson , x , val );
up(p);
}
void upd ( int p , int l , int r , int x , int y , int val )
{
if ( x <= l && r <= y ) return changeadd(p,val) , void();
down(p);
if ( x <= mid ) upd ( lson , x , y , val );
if ( mid + 1 <= y ) upd ( rson , x , y , val );
up(p);
}
int query ( int p , int l , int r , int x , int y )
{
if ( x <= l && r <= y ) return t[p].val;
down(p); int res = inf;
if ( x <= mid ) res = min ( res , query ( lson , x , y ) );
if ( mid + 1 <= y ) res = min ( res , query ( rson , x , y ) );
return res;
}
}T;
signed main ()
{
ios::sync_with_stdio(false);
cin.tie(0) , cout.tie(0);
int t = read();
while ( t -- )
{
n = read() , k = read();
for ( int i = 1 ; i <= n ; i ++ ) a[i] = read();
for ( int i = 1 ; i <= k ; i ++ ) cold[i] = read();
for ( int i = 1 ; i <= k ; i ++ ) hot[i] = read();
/*
for(int j = 0; j <= k; j++) {
dp[i][j] = min(dp[i][j],dp[i - 1][j] + (a[i] == a[i - 1] ? hot[a[i]] : cold[a[i]]));//ith program on CPU2
dp[i][a[i - 1]] = min(dp[i][a[i - 1]],dp[i - 1][j] + (a[i] == j ? hot[a[i]] : cold[a[i]]));//ith program on CPU1
}
这是朴素dp
*/
T.build ( 1 , 1 , k + 1 );//为了方便线段树 我们强制将所有下标加一
T.minn ( 1 , 1 , k + 1 , 1 , 0 );//注意这里 dp[0][0]状态也是合法的 否则你无法开始转移 dp[0][1--k]状态都是不合法的 因为这些点上还没跑过程序
for ( int i = 1 ; i <= n ; i ++ )
{
int temp = min ( T.query ( 1 , 1 , k + 1 , 1 , k + 1 ) + cold[a[i]] , T.query ( 1 , 1 , k + 1 , a[i] + 1 , a[i] + 1 ) + hot[a[i]] );
//相当于预处理第二个转移 前半句代码相当于朴素dp的a[i]!=j的情况 后半句相当于a[i]==j的情况
T.upd ( 1 , 1 , k + 1 , 1 , k + 1 , a[i] == a[i-1] ? hot[a[i]] : cold[a[i]] );//这里我们全局加 (a[i] == a[i - 1] ? hot[a[i]] : cold[a[i]])(这是第一个转移)
T.minn ( 1 , 1 , k + 1 , a[i-1] + 1 , temp );
}
cout << T.query ( 1 , 1 , k + 1 , 1 , k + 1 ) << endl;
}
return 0;
}