11.18题解

climb

首先我们有一个状态时 \(O(nV)\) 的 DP,即考虑到第 i 个点,当前高度是 j 的最小代价。

这个状态太大了,我们考虑哪些状态时冗余的。

考虑我一个点经过调整,如果前一个点确定是 t,那么当前这个点可能且只可能是:t - d, t + d, a_i。

那么这个东西是一个 a_i + kd 的形式的,我们就可以将状态精简为 \(O(n^3)\) 的。

然后考虑到这个转移是一个滑动窗口的形式,则最终复杂度是 \(O(n^3)\)

代码
#include <bits/stdc++.h>
using namespace std;

namespace myb {
	
    #define int long long
	const int N = 305;
	
	int a[N];
	int f[2][N * N * 2];
	
	void solve() {
		int n, d;
		cin >> n >> d;
		for (int i = 1;i <= n;i++) cin >> a[i];
		vector<int> x;
		for (int i = 1;i <= n;i++) {
            // if (d == 0) {
            //     x.push_back(a[i]);
            //     continue;
            // }
			for (int j = -n;j <= n;j++) {
                if (a[i] + 1ll * j * d < 0) continue;
				x.push_back(a[i] + 1ll * j * d);
			}
		}
		sort(x.begin(), x.end());
		x.erase(unique(x.begin(), x.end()), x.end());
		int m = x.size();
		for (int i = 0;i <= 1;i++) {
			for (int j = 0;j < m;j++) {
				f[i][j] = 1e16;
			}
		}
        int ind = 0;
		for (int i = 0;i < m;i++) {
			if (x[i] == a[1]) f[ind][i] = 0;
		}
        ind = 1 - ind;
		for (int i = 2;i <= n;i++, ind = 1 - ind) {
//			cout << i << ":\n";
            // cout << i << " " << ind << "\n";
			deque<int> q;
			int pos = 0;
			for (int j = 0;j < m;j++) {
				while (pos < m && x[pos] <= x[j] + d) {
					while (q.size() && f[1 - ind][q.back()] >= f[1 - ind][pos]) q.pop_back();
					q.push_back(pos);
					pos++;
				}
				while (q.size() && x[q.front()] < x[j] - d) q.pop_front();
				if (q.size() == 0) break;
				f[ind][j] = f[1 - ind][q.front()] + abs(a[i] - x[j]);
                // cout << f[ind][j] << " ";
//				cout << x[j] << ": " << f[i][j] << "\n";
			}
            // cout << "\n";
//			cout << "?\n";
		}
        // cout << ind << "\n";
		for (int i = 0;i < m;i++) {
			if (x[i] == a[n]) {
				if (f[1 - ind][i] > 1e15) cout << "-1\n";
				else cout << f[1 - ind][i] << "\n";
				return ;
			}
		}
	}
	void main() {
		int T;
        T = 1;
		// cin >> T;
		while (T--) {
			solve();
		}
	}
}

signed main() {
    freopen("climb.in", "r", stdin);
    freopen("climb.out", "w", stdout);
	myb::main();
	return 0;
}

fun game

首先清掉是别人子串的字符串,这些一定无用。

然后我们需要注意到一个关键性质:完成以上操作后,一定不会有一个字串绕过最终答案的那个环一整圈。

因为如果绕过去了,就意味着所有串都是他的子串。

然后我们就是一个字符串拼接的顺序问题,这个直接状压 DP。

代码

#include <bits/stdc++.h>
using namespace std;

namespace myb {
	
	const int N = 20;
	
	int n;
	string s[N];
	int len[N];
	int nxt[40005];
    int cov[N][N][2][2];
	int f[1 << 18][N][2];
    void get_nxt(string &S, int n) {
        for (int j = 1;j <= n;j++) nxt[j] = 0;
        for (int i = 2, j = 0;i <= n;i++) {
            while (j && S[j + 1] != S[i]) j = nxt[j];
            if (S[j + 1] == S[i]) j++;
            nxt[i] = j;
        }
    }
	void solve() {
		for (int i = 0;i < n;i++) {
			cin >> s[i];
		}
		sort(s, s + n, [] (string &a, string &b) {
			return a.size() > b.size();
		});
		int m = -1;
		for (int i = 0;i < n;i++) {
			int siz = s[i].size();
            string S = " " + s[i];
            get_nxt(S, siz);

			int flag = 0;
			for (int j = 0;j <= m;j++) {
				int l = len[j];
                for (int a = 0, b = 0;a < l;a++) {
                    while (b && S[b + 1] != s[j][a]) b = nxt[b];
                    if (S[b + 1] == s[j][a]) b++;
                    if (b == siz) {
                        flag = 1;
                        break;
                    }
                }
                if (flag) break;
			}
            if (flag) continue;

            reverse(s[i].begin(), s[i].end());
            S = " " + s[i];
            get_nxt(S, siz);

			for (int j = 0;j <= m;j++) {
				int l = len[j];
                for (int a = 0, b = 0;a < l;a++) {
                    while (b && S[b + 1] != s[j][a]) b = nxt[b];
                    if (S[b + 1] == s[j][a]) b++;
                    if (b == siz) {
                        flag = 1;
                        break;
                    }
                }
                if (flag) break;
			}

            if (flag) continue;

            reverse(s[i].begin(), s[i].end());
            s[++m] = s[i];
            len[m] = s[i].size();
		}

        if (m == 0) {
            string S = " " + s[0];
            int siz = s[0].size();
            get_nxt(S, siz);
            cout << max(2, siz - nxt[siz]) << "\n";
            return ;
        }

        for (int i = 0;i <= m;i++) {
            for (int j = 0;j <= m;j++) {
                string s1 = s[i], s2 = s[j];
                for (int a = 1;a >= 0;a--) {
                    if (a == 0) reverse(s1.begin(), s1.end());
                    for (int b = 1;b >= 0;b--) {
                        if (b == 0) reverse(s2.begin(), s2.end());
                        string S = s2 + "#" + s1;
                        int siz = S.size();
                        S = " " + S;
                        get_nxt(S, siz);
                        cov[i][j][a][b] = nxt[siz];
                        if (b == 0) reverse(s2.begin(), s2.end());
                    }
                    if (a == 0) reverse(s1.begin(), s1.end());
                }
            }
        }
		
		memset(f, 0x3f, sizeof f);
		f[1][0][0] = len[0];
		for (int i = 1;i < (1 << m + 1);i++) {
			vector<int> ok;
			for (int j = 0;j <= m;j++) {
				if ((i >> j) & 1) ok.push_back(j);
			}
			for (int x : ok) {
				for (int y : ok) {
                    if (x == y) continue;
                    for (int dir = 0;dir <= 1;dir++) {
                        for (int t = 0;t <= 1;t++) {
                            f[i][x][dir] = min(f[i][x][dir], f[i ^ (1 << x)][y][t] + len[x] - cov[x][y][dir][t]);
                        }
                    }
                }
					
			}
		}
		int ans = 1e9;
		for (int i = 0;i <= m;i++) {
			ans = min(ans, min(f[(1 << m + 1) - 1][i][0] - cov[0][i][0][0], f[(1 << m + 1) - 1][i][1] - cov[0][i][0][1]));
		}
        cout << max(2, ans) << "\n";
	}
	void main() {
		cin >> n;
		solve();
	}
}

int main() {
    freopen("cycle.in", "r", stdin);
    freopen("cycle.out", "w", stdout);
	myb::main();
	return 0;
}

line game

可以发现,我们要选择的线不会交叉,i 和 \(p_i\) 是分别递增的。

那我可以令 \(f_i\) 是选择第 i 条边删去,前面的全部都删掉了的最小花费,那么这个就应该从一个 j,满足 \(p_j < p_i\),且不存在 \(j < k < i\)\(p_j < p_k < p_i\)

那么如果将一个点放在坐标系中,x 轴是 i,y 轴是 \(p_i\),那么考虑将 dp 值放到 \((i, p_i)\) 上,那么能转移到的点是一些单不增的点。这样我们考虑从小到大加入这些点。

屏幕截图 2025-11-23 104152

因为每个点是从一个单调栈转移而来,那你考虑如何维护这个单调栈,我们将点加入线段树,然后在节点上维护这个单调栈。

区间合并的时候我们去合并两个点的单调栈,然后这其实就和维护区间前缀最大值是一样的了,我们线段树单侧递归,每个节点维护最大值,最小值和最小 dp 值。

posted @ 2025-11-20 16:06  yanbinmu  阅读(8)  评论(0)    收藏  举报