题解:qoj8225 最小值之和

感谢 @lsj2009 教会了我这个题 /bx

题意:很简单了,不再赘述。

做法:

首先感觉没啥想法,这个 \(f'\) 貌似没有什么性质,但是发现他是若干个 \(\min\) 加在一起,所以考虑到笛卡尔树上去做。

但是笛卡尔树发现还是不太好描述,考虑这个 \(f\) 是怎么生成的,可以认为是遍历笛卡尔树,然后让一段区间整体加上一个数,再递归到左右区间去做。考虑此时有区间 \([l,r]\) 加一,这样做的 \(f'\) 的改变是怎么样的,发现会整体加 \((r-l)\)。然后后面中间的有一个数变成 \(0\) 了,两侧就会分开互相独立。

所以以此我们可以进行一个 dp,\(dp_{l, r, k}\) 代表 \(f\) 数组区间 \([l,r)\) 目前已经减了 \(k\),是否可以成立,转移即枚举一个中点 \(p\),然后要求 \(dp_{l,p,k+(r-l)\times t},dp_{p+1,r,k+(r-l)\times t}\) 都成立。

但是这样有一个值域非常爆炸。我们观察到一个事情,如果 \(dp_{l,r,k}\) 可行,那么我 \(dp_{l,r,k-(r-l)}\) 也是可以的,把 \([l,r)\) 全体加一就可以实现。

所以我们就可以换一个状态,\(dp_{l,r,k}\) 代表减了的 \(v\) 满足模 \(r-l\)\(k\),最大的 \(v\) 是多少。转移时枚举中间的断点和两边区间的余数是多少,合并一下,具体可以见代码。然后因为我们可以在两侧区间加一的操作而不改变余数,所以这里要用一个同余最短路跑一下。

总复杂度 \(O(n^5)\)

给出代码实现:

#include <bits/stdc++.h>
using namespace std;
#define int long long
const int maxn = 105;
int n, a[maxn], dp[maxn][maxn][maxn], pre[maxn][maxn][maxn];
int dis[maxn];
int lcm(int x, int y) {
	return x * y / __gcd(x, y);
}
void renew(int l, int r, int k, int p) {
//	cout << l << " " << r << " " << k << " " << p << endl;
	if(dp[l][r][k % (r - l)] < k)
		dp[l][r][k % (r - l)] = k, pre[l][r][k % (r - l)] = p;
}
int vis[maxn];
void out(int l, int r, int p, int s) {
//	if(l <= r)
//		cout << l << " " << r << " " << p << " " << s << endl;
	if(l == r)
		return ;
	int t = (dp[l][r][p % (r - l)] - p) / (r - l), pos = pre[l][r][p % (r - l)];
	out(l, pos, p + t * (r - l), s + t), cout << s + t << " ", out(pos + 1, r, p + t * (r - l), s + t);
}
signed main() {
	cin >> n;
	for (int i = 1; i <= n; i++)
		cin >> a[i];
	if(n == 1) {
		cout << (a[1] ? "No" : "Yes") << endl;
		return 0;
	}
	memset(dp, -0x3f, sizeof(dp));
	for (int i = 1; i <= n - 1; i++)
		if(a[i] == a[i + 1])
			dp[i][i + 1][0] = a[i], pre[i][i + 1][0] = i;
	for (int len = 2; len <= n; len++)
		for (int i = 1; i + len <= n; i++) {
			int j = i + len;
			for (int k = i + 1; k < j - 1; k++) {
				int p = lcm(k - i, j - k - 1);
				for (int t = 0; t < j - i; t++)
					dis[t] = -0x3f3f3f3f, vis[t] = 0;
				for (int t = 0; t < p; t++) {
					int val = min(dp[i][k][t % (k - i)], dp[k + 1][j][t % (j - k - 1)]);
					if(val < t)
						continue;
					val = (val - t) / p * p + t;
					if(val >= 0)
						dis[val % (len)] = max(dis[val % len], val);
				}
				priority_queue<int> q;
				for (int t = 0; t < j - i; t++)
					q.push(dis[t]);
				while(!q.empty()) {
					int u = q.top(); q.pop();
					if(vis[u % (j - i)])
						continue;
					vis[u % (j - i)] = 1;
					if(u >= p) {
						if(dis[(u - p) % (j - i)] < u - p)
							dis[(u - p) % (j - i)] = u - p, q.push(u - p);
					}
				}
				for (int t = 0; t < j - i; t++)
					renew(i, j, dis[t], k);
			}
			if(a[i] <= dp[i + 1][j][a[i] % (j - i - 1)])
				renew(i, j, a[i], i);
			if(a[j] <= dp[i][j - 1][a[j] % (j - i - 1)])
				renew(i, j, a[j], j - 1);
		//	for (int k = 0; k < j - i; k++)
			//	cout << i << " " << j << " " << k << " " << dp[i][j][k] << " " << pre[i][j][k] << " " << a[i] << " " << a[j] << " " << dp[i][j - 1][a[j] % (j - i - 1)] << endl;
		}
	if(dp[1][n][0] < 0)
		cout << "No" << endl;
	else
		cout << "Yes" << endl, out(1, n, 0, 0), cout << endl;
	return 0;
}

一个我不太理解的是,在枚举两侧的余数时,我这里是写的是 \([0,lcm(x, y))\),但是有些代码里写的是 \([0,r-l]\) 并且换到我的代码里确实也是可以过的,求懂的大佬教教我为什么 qwq。

posted @ 2025-09-29 10:43  LUlululu1616  阅读(22)  评论(0)    收藏  举报