首页 > 其他分享 >区间 DP

区间 DP

时间:2024-02-14 22:33:58浏览次数:31  
标签:int ll mn 最小值 110 区间 mx DP

思路:按区间的 \(len\) 从小到大 DP,枚举左端点,算出右端点,再枚举中间的分界点转移

有可能是向左右两端各扩展 \(1\) 步

还有时要记录在左/右端点,分开转移

把问题划分为区间长度更短的最优子结构

注意 \(len=1\) 时初始化及边界

  1. P4342 [IOI1998]Polygon

这题已经说了去掉一条边后执行计算,明显提示要断环为链

枚举断开的边,然后转化为序列上的问题,容易想到区间 DP

设 \(f(i,j)\) 为边的区间 \((i,j)\) 断开后能算出的最大值

加法最大能由加法的最大值转移过来,但乘法会出现负负得正这种情况导致最小值变最大值

那就再记录 \(g(i,j)\) 为最小值

转移时不用人工分类讨论,直接丢掉 \(\min/\max\) 函数中

注意边界情况,当断开某个区间最边上的边时要涉及旁边单独的数,需要单独处理

code:

typedef long long ll;
ll n, a[110], f[110][110], g[110][110], d, mx = -1e15, lsh;
char ch, b[110];
vector<ll> lin; // f[i][j] max(i~j)	g[i][j] min(i~j)
inline ll work(ll l, ll r)
{
	memset(f, 0xcf, sizeof(f)), memset(g, 0x3f, sizeof(g));
	for(reg ll i = 1; i <= n * 2; ++i)	
	{
		if(b[i] == 't')	f[i][i] = g[i][i] = a[i - 1] + a[i];
		else	f[i][i] = g[i][i] = a[i - 1] * a[i];
	}
	for(reg ll i = 2; i < n; ++i)
	{
		for(reg ll j = l; j <= r - i + 1; ++j)
		{
			reg ll k = j + i - 1;
			if(b[j] == 't')	f[j][k] = max(f[j][k], a[j - 1] + f[j + 1][k]), g[j][k] = min(g[j][k], a[j - 1] + g[j + 1][k]);
			else
			{
			    f[j][k] = max({f[j][k], a[j - 1] * f[j + 1][k], a[j - 1] * g[j + 1][k]});
				g[j][k] = min({g[j][k], a[j - 1] * f[j + 1][k], a[j - 1] * g[j + 1][k]});	
			}
			if(b[k] == 't') f[j][k] = max(f[j][k], a[k] + f[j][k - 1]), g[j][k] = min(g[j][k], a[k] + g[j][k - 1]);
			else
			{
				f[j][k] = max({f[j][k], a[k] * f[j][k - 1], a[k] * g[j][k - 1]});
				g[j][k] = min({g[j][k], a[k] * f[j][k - 1], a[k] * g[j][k - 1]});
			}
			for(reg ll u = j + 1; u < k; ++u)
			{ 
			    if(b[u] == 't')
			    {
			    	f[j][k] = max(f[j][k], f[j][u - 1] + f[u + 1][k]); 
					g[j][k] = min(g[j][k], g[j][u - 1] + g[u + 1][k]);
				}
			    else
			    {
			    	f[j][k] = max({f[j][k], f[j][u - 1] * f[u + 1][k], g[j][u - 1] * g[u + 1][k], f[j][u - 1] * g[u + 1][k], g[j][u - 1] * f[u + 1][k]});
			    	g[j][k] = min({g[j][k], g[j][u - 1] * f[u + 1][k], f[j][u - 1] * g[u + 1][k], g[j][u - 1] * g[u + 1][k], f[j][u - 1] * f[u + 1][k]});
				}
			}
		}
	}
	return f[l][r];
}
int main()
{
	ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
	cin >> n;
	for(reg ll i = 1; i <= 2 * n; ++i)
	{
		if(i & 1)	cin >> ch, b[i / 2 + 1] = ch;
		else	cin >> d, a[i / 2] = d;
	}
	for(reg ll i = n + 1; i <= 2 * n; ++i)	a[i] = a[i - n], b[i] = b[i - n];
	for(reg ll i = 1; i <= n; ++i)
	{
		lsh = work(i + 1, n + i - 1);
		if(lsh > mx)	lin.clear(), lin.pb(i), mx = lsh;
		else if(lsh == mx)	lin.pb(i);
	}
	cout << mx << endl;
	for(reg ll i = 0; i < (ll)lin.size(); ++i)	cout << lin[i] << " ";
	return 0;
}
  1. P3592 [POI2015] MYJ

发现最终一定有一种方案满足值均在 \(c_i\) 中出现,值域降至 \(O(m)\)

转移跟区间的最小值有关,设 \(f(l,r,i)\) 表示 \([l,r]\) 最小值为 \(i\) 时,完全包含在区间 \([l,r]\) 中的询问产生的最大收益

枚举最小值取到的地方为中间断点,两边的最小值 \(\ge i\),处理跨过最小值的区间贡献,按 \(c_i\) 排序后用指针移动 \(O(1)\) 计算

会超时,可以预处理后缀最大值优化转移

同时记录转移点,递归还原方案

复杂度 \(O(n^3m)\)

void find(int l, int r, int val) // 找 [l,r],最小值至少为 val时取哪个为最小值,递归找方案 
{
	if(l > r)	return;
	int mxa = 0, mn = 0;
	for(int i = val; i <= cnt; ++i)
		if(f[l][r][i] >= mxa)	mxa = f[l][r][i], mn = i;
	a[fr[l][r][mn]] = lsh[mn];
	find(l, fr[l][r][mn] - 1, mn), find(fr[l][r][mn] + 1, r, mn);
}
int main()
{
	n = read(), m = read();
	for(int i = 1; i <= m; ++i)
	{
		p[i].a = read(), p[i].b = read(), p[i].c = read(), lsh[i] = p[i].c, p[i].id = i;
		for(int j = p[i].a; j <= p[i].b; ++j)	crs[j].pb(i);
	}
	sort(lsh + 1, lsh + m + 1), cnt = unique(lsh + 1, lsh + m + 1) - (lsh + 1);
	for(int i = 1; i <= m; ++i)	p[i].num = lower_bound(lsh + 1, lsh + cnt + 1, p[i].c) - lsh;
	for(int i = 1; i <= m; ++i)
		if(p[i].a == p[i].b)
		{
			for(int j = 1; j <= p[i].num; ++j)	f[p[i].a][p[i].b][j] += lsh[j];
			for(int j = p[i].num; j > 0; --j)	
				mx[p[i].a][p[i].b][j] = max(f[p[i].a][p[i].b][j], mx[p[i].a][p[i].b][j + 1]);
		}
	for(int i = 1; i <= n; ++i)
		for(int j = 1; j <= cnt; ++j)	fr[i][i][j] = i;
	for(int len = 2; len <= n; ++len)
	{
		for(int l = 1; l + len - 1 <= n; ++l)
		{
			int r = l + len - 1;
			for(int k = l; k <= r; ++k) // 枚举最小值所在位置 
			{
				idx = 0;
				for(int i : crs[k])	
					if(p[i].a >= l && p[i].b <= r)	lin[++idx] = i;
				sort(lin + 1, lin + idx + 1, cmp); 
				for(int i = 1, j = 1; i <= cnt; ++i) // 枚举最小值为 lsh[i] 
				{
					int tot = 0; 
					while(j <= idx && p[lin[j]].num < i)	++j;
					if(k > l)	tot += mx[l][k - 1][i];
					if(k < r)	tot += mx[k + 1][r][i];
					if(f[l][r][i] <= tot + (idx - j + 1) * lsh[i]) 
					{
						fr[l][r][i] = k; // 记录转移点 
						f[l][r][i] = tot + (idx - j + 1) * lsh[i];
					}
				}
			}
			for(int i = cnt; i > 0; --i)	mx[l][r][i] = max(mx[l][r][i + 1], f[l][r][i]);
		}
	}
	printf("%d\n", mx[1][n][1]);
	find(1, n, 1);
	for(int i = 1; i <= n; ++i)	printf("%d ", a[i]);
	return 0;
}
  1. P1220 关路灯

由于代价与人的位置有关,因此记录 \(f(l,r,0/1)\) 表示关掉 \([l,r]\) 中的路灯,最后人在左/右端点的最小代价

而且因为跨过一段一定不优,人在走过去的同时完全可以顺便把经过的灯关掉,可以看作一次多处理一盏灯,只需向两边扩展即可

注意代价的计算要携带上没有关掉的灯,初始时只关了 \(c\) 处的灯

复杂度 \(O(n^2)\),\(n\) 后可以加 \(2\) 个 \(0\)

int main()
{
	n = read(), c = read();
	for(int i = 1; i <= n; ++i)	a[i] = read(), b[i] = read(), sum[i] = sum[i - 1] + b[i];
	memset(f, 0x3f, sizeof(f));
	f[c][c][0] = f[c][c][1] = 0;
	for(int i = c; i > 0; --i)
	{
		for(int j = (i == c) ? c + 1 : c; j <= n; ++j)
  		{
  			f[i][j][0] = min(f[i][j][0], f[i + 1][j][0] + (a[i + 1] - a[i]) * (sum[n] - sum[j] + sum[i]));
  			f[i][j][0] = min(f[i][j][0], f[i + 1][j][1] + (a[j] - a[i]) * (sum[n] - sum[j] + sum[i]));
  			f[i][j][1] = min(f[i][j][1], f[i][j - 1][0] + (a[j] - a[i]) * (sum[n] - sum[j - 1] + sum[i - 1]));
  			f[i][j][1] = min(f[i][j][1], f[i][j - 1][1] + (a[j] - a[j - 1]) * (sum[n] - sum[j - 1] + sum[i - 1]));
        }
	}
	printf("%d", min(f[1][n][0], f[1][n][1]));
	return 0;
}

标签:int,ll,mn,最小值,110,区间,mx,DP
From: https://www.cnblogs.com/KellyWLJ/p/18015737

相关文章

  • 高维前缀和(SOS DP)
    高维前缀和(SOSDP)通常求二维前缀和,用容斥来求但其实,完全可以先做一遍行的前缀和,再做一遍列的前缀和拓展到\(k\)维也是如此,可以在\(O(nk)\)的复杂度求前缀和但怎么和DP扯上关系?可以把第\(i\)维当作阶段,每一维的具体信息是状态先枚举阶段,表示当前固定其它维,只统计这一......
  • TCP和UDP面试题提问
    @目录TCPUDP总结应用TCP(传输控制协议)和UDP(用户数据报协议)是两种计算机网络通信协议,它们在网络通信中起着不同的作用。TCPTCP是面向连接的协议,它在数据传输之前需要在发送端和接收端建立一条连接。TCP提供可靠的数据传输,它使用确认和重传机制来确保数据的可靠性和完整性。T......
  • P1941-DP【绿】
    题目本身只是一道有些难度的普通dp题,题解中有人说可以把这个看作是背包,我不是这么做的便没细看,感觉能把他联想为背包问题的特例的人的发散思维能力真强。不过倒也没必要,常规做即可,用二维数组即可描述状态,dp[i][j]表示只由前i个横向单位长度组成的游戏中以(i,j)结尾游戏所需的最小游......
  • 数位 DP 做题记录
    数位DP数位DP的常见套路就是记录当前到哪一位,是否抵着上界,转移时枚举当前可以填哪些数,做一遍记忆化搜索。P3413SAC#1-萌数题意:求\([l,r]\)中有多少个数中含有回文子串。思路:如果存在回文子串,那么必然有相邻两位相同或者间隔一位相同,在数位DP时额外记录前2位就可以......
  • Codeforces Round 113 (Div. 2)E. Tetrahedron(dp、递推)
    目录题面链接题意题解代码总结题面链接E.Tetrahedron题意从一个顶点出发走过路径长度为n回到出发点的方案总数题解考虑dp\(f[i][0|1|2|3]\):走了i步,现在在j点的方案总数转移:\(f[i][0]=f[i-1][1]+f[i-1][2]+f[i-1][3]\)\(f[i][1]=f[i-1][0]+f[i-1][2]+f[i-1][3]\)\(f......
  • 树状数组区间修改区间查询
    树状数组区间修改区间查询问题——两种操作给定\(l,r,x\),将\([l,r]\)这个区间内的所有值都加上\(x\)给定\(l,r\)求出\([l,r]\)的区间和这道题肯定要用前缀和和差分,那么大体框架可以出来了//操作一:update(l,x),update(r+1,-x);//操作二query(r)-query(l......
  • 区间满足条件的子区间计数
    区间满足条件的子区间计数一般是扫描线,可能会有线段树维护历史版本信息。CF526FPuddingMonsters题意:给定一个\(n\timesn\)的棋盘,其中有\(n\)个棋子,每行每列恰好有一个棋子。对于所有的\(1\leqk\leqn\),求有多少个\(k\timesk\)的子棋盘中恰好有\(k\)个棋子。......
  • 连续性区间位置查询——链式并查集
    目录问题概述思路分析参考代码做题总结问题概述这里给出两个题目,一个是上一篇的新春漫步(其实当时给的官方题解就是链式并查集的写法,但是当时我懒得写了,emmm),二是最近vp的一场cf_div3_923场的d题,准确来说,就是因为这个我才准备写这个的,题目大概就是给出一个长度为n的数组和q组询......
  • Atcoder Educational DP Contest 题解
    比赛链接:Atcoderr或者题单A:Frog1常规线性dp,注意到对于每个落点来说,青蛙只能由第\(i-1\)个石头或者第\(i-2\)个石头转移得到,那么我们让\(dp[i]\)为当跳到第\(i\)个石头上的最小费用。此时显然有:\[dp_i=\min(dp_{i-1}+\left|h_i-h_{i-1}\right|,dp_{i-2}+\left|h_......
  • 【Java 并发】【队列应用】【二】Tomcat的NioEndPoint中ConcurrentLinkedQueue 的使用
    1 前言这一节我们讲解Tomcat的NioEndPoint中ConcurrentLinkedQueue的使用。2  Tomcat的容器结构本节讲解apache-tomcat-7.0.32-src源码中ConcurrentLinkedQueue的使用。首先介绍Tomcat的容器结构以及NioEndPoint的作用,以便后面能够更加平滑地切入话题,如图11-4所示......