海亮 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\)

分两种情况转移:

  1. \(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]]));
  1. \(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;
}
posted @ 2023-07-17 16:10  Echo_Long  阅读(5)  评论(0编辑  收藏  举报