• [题解]JOI 2020 Final 题解


    • 省选临近却无比颓废,逼自己去做 JOI

    A

    题意

    • 给定一个长度为 (N+1) 的数组 (A) 和一个长度为 (N) 的数组 (B)

    • 对于每个 (1le ile N+1),求出:对每个 (1le jle N) 求出一个 (k_j),满足 (k) 数组包含 (1)(N+1) 中除 (i) 外的所有数,最小化 (max_{i=1}^Nmax(A_{k_i}-B_i,0))

    • (1le Nle 2 imes10^5)(1le A_i,B_ile10^9)

    Solution

    • 可以发现 (a<b,c<d)(b-c>a-c)(b-c>b-d),故 (a)(c)(b)(d)(a)(d)(b)(c)

    • 故把两个数组排序,预处理出排序以后两个数组长度为 (i) 的前缀匹配的 (max(a-b)),后缀同理,即可求出答案

    • (O(Nlog N))

    Code

    #include <bits/stdc++.h>
    
    template <class T>
    inline void read(T &res)
    {
    	res = 0; bool bo = 0; char c;
    	while (((c = getchar()) < '0' || c > '9') && c != '-');
    	if (c == '-') bo = 1; else res = c - 48;
    	while ((c = getchar()) >= '0' && c <= '9')
    		res = (res << 3) + (res << 1) + (c - 48);
    	if (bo) res = ~res + 1;
    }
    
    template <class T>
    inline T Max(const T &a, const T &b) {return a > b ? a : b;}
    
    typedef long long ll;
    
    const int N = 2e5 + 5;
    
    int n, b[N];
    ll pre[N], suf[N], ans[N];
    
    struct elem
    {
    	int x, id;
    } a[N];
    
    inline bool comp(elem a, elem b)
    {
    	return a.x < b.x;
    }
    
    int main()
    {
    	read(n);
    	for (int i = 1; i <= n + 1; i++) read(a[i].x), a[i].id = i;
    	std::sort(a + 1, a + n + 2, comp);
    	for (int i = 1; i <= n; i++) read(b[i]);
    	std::sort(b + 1, b + n + 1);
    	for (int i = 1; i <= n + 1; i++) pre[i] = Max(pre[i - 1], 1ll * a[i].x - b[i]);
    	for (int i = n + 1; i >= 1; i--) suf[i] = Max(suf[i + 1], 1ll * a[i].x - b[i - 1]);
    	for (int i = 1; i <= n + 1; i++) ans[a[i].id] = Max(pre[i - 1], suf[i + 1]);
    	for (int i = 1; i <= n + 1; i++) printf("%lld ", ans[i]);
    	return puts(""), 0;
    }
    

    B

    题意

    • 有一个只包含 JOI 的长度为 (N) 的字符串

    • 每次可以删掉一个字符,但如果删掉的字符不在串首或串尾,则要花费 (1) 的代价

    • 求把原串变成 JJ... JOO...OII... I(三种字母均有 (K) 个)的最小代价,或者判断无解

    • (3le Nle2 imes10^5)(1le Klefrac N3)

    Solution

    • 易得选出一个最短的子串包含 JJ... JOO... OII... I 为子序列,这个子串长度减去 (3K) 即为答案

    • 于是我们只需求出对于每个 (i),以 (i) 开头的后缀中该子序列最后一个字符所在的下标

    • 预处理 (i) 之后字符 JOI 分别第 (K) 次出现的位置即可

    • (O(N))

    Code

    #include <bits/stdc++.h>
    
    template <class T>
    inline T Min(const T &a, const T &b) {return a < b ? a : b;}
    
    const int N = 2e5 + 5, INF = 0x3f3f3f3f;
    
    int n, k, nxt[N][3], top[3], stk[3][N], ans = INF;
    char s[N];
    
    int main()
    {
    	std::cin >> n >> k;
    	scanf("%s", s + 1);
    	for (int i = n; i >= 1; i--)
    	{
    		int c = s[i] == 'I' ? 2 : (s[i] == 'O' ? 1 : 0);
    		stk[c][++top[c]] = i;
    		for (int c = 0; c < 3; c++)
    			if (top[c] >= k) nxt[i][c] = stk[c][top[c] - k + 1] + 1;
    	}
    	for (int i = 1; i <= n; i++)
    	{
    		int lst = nxt[nxt[nxt[i][0]][1]][2];
    		if (lst) ans = Min(ans, lst - i - k * 3);
    	}
    	return std::cout << (ans == INF ? -1 : ans) << std::endl, 0;
    }
    

    C

    题意

    • 有一个长为 (L) 的环,环上有 (N) 个点,第 (i) 个位置为 (X_i),两两不同且不为原点

    • 如果一个点在时刻 (T_i) 之前经过则会得到 (1) 的收益

    • 一开始在原点,每次可以花费 (1) 的时间向某个方向走 (1) 个长度单位

    • 求最大收益

    • (1le Nle 200)(1le L,T_ile10^9)(1le X_i<L)(X) 按严格递增序给出

    Solution

    • 显然每次走到的点一定是一段前缀并上一段后缀

    • DP:(f_{l,r,i})(g_{l,r,i}) 分别表示走到了 ([1,l]cup[r,n]),获得 (i) 收益需要的最少时间,(f) 表示最后停在 (l)(g) 表示最后停在 (r)

    • 转移即枚举下一次到达 (l+1) 还是 (r-1),具体方程略

    • (O(N^3))

    Code

    #include <bits/stdc++.h>
    
    template <class T>
    inline void read(T &res)
    {
    	res = 0; bool bo = 0; char c;
    	while (((c = getchar()) < '0' || c > '9') && c != '-');
    	if (c == '-') bo = 1; else res = c - 48;
    	while ((c = getchar()) >= '0' && c <= '9')
    		res = (res << 3) + (res << 1) + (c - 48);
    	if (bo) res = ~res + 1;
    }
    
    template <class T>
    inline void GMin(T &a, const T &b) {if (b < a) a = b;}
    
    template <class T>
    inline void GMax(T &a, const T &b) {if (b > a) a = b;}
    
    const int N = 205, INF = 0x3f3f3f3f;
    
    int n, L, X[N], T[N], f[N][N][N], g[N][N][N], ans;
    
    int main()
    {
    	read(n); read(L); X[n + 1] = L;
    	for (int i = 1; i <= n; i++) read(X[i]);
    	for (int i = 1; i <= n; i++) read(T[i]);
    	memset(f, INF, sizeof(f)); memset(g, INF, sizeof(g));
    	f[0][n + 1][0] = 0; g[0][n + 1][0] = 0;
    	for (int l = 0; l <= n; l++)
    		for (int r = n + 1; r > l + 1; r--)
    			for (int i = 0; i <= l + n - r + 1; i++)
    			{
    				int tf = f[l][r][i] + X[l + 1] - X[l],
    					tg = g[l][r][i] + X[r] - X[r - 1],
    					wf = g[l][r][i] + L - X[r] + X[l + 1],
    					wg = f[l][r][i] + L - X[r - 1] + X[l];
    				GMin(f[l + 1][r][i + (tf <= T[l + 1])], tf);
    				GMin(g[l][r - 1][i + (tg <= T[r - 1])], tg);
    				GMin(f[l + 1][r][i + (wf <= T[l + 1])], wf);
    				GMin(g[l][r - 1][i + (wg <= T[r - 1])], wg);
    			}
    	for (int l = 0; l <= n; l++)
    		for (int r = n + 1; r > l; r--)
    			for (int i = 0; i <= l + n - r + 1; i++)
    			{
    				if (l >= 1 && f[l][r][i] <= T[l]) GMax(ans, i);
    				if (r <= n && g[l][r][i] <= T[r]) GMax(ans, i);
    			}
    	return std::cout << ans << std::endl, 0;
    }
    

    D

    题意

    • 给定一个 (N)(M) 边的有向图,每条边有长度 (C) 和翻转代价 (D),求翻转最多一条边之后,(1)(N) 的最短路加上 (N)(1) 的最短路的最小值

    • (2le Nle 200)(1le Mle 5 imes10^4)(0le Cle10^6)(0le Dle 10^9),可能存在重边

    Solution

    • 考虑翻转 (<u,v>) 之后 (1)(N) 最短路的可能情况:

    • (1)(1) 先到 (v)(不能经过 (u)),然后走 (<v,u>),最后 (u)(N)(不能经过 (v)

    • (2)(1)(u),然后通过该边的一条重边到达 (v),最后 (v)(N)

    • (3)(1)(N),不经过点 (u)

    • (4)(1)(N),不经过点 (v)

    • (5)(1)(N)(v)(u) 前,也就是说先 (1)(u),再 (u)(N)(不能经过 (v)

    • (6)(1)(N)(u)(v) 之间隔了至少一个点,也就是枚举 (x),先 (1)(x)(不经过 (v)),再 (x)(N)(不经过 (u)

    • (N)(1) 同理

    • 于是只需对于任意 (u) 预处理不经过点 (u) 的单源最短路,(N)Dijkstra 即可,注意可以使用不加优先队列的 (O(N^2+M)) 暴力 Dijkstra

    • (O(N^3+NM))

    Code

    #include <bits/stdc++.h>
    
    template <class T>
    inline void read(T &res)
    {
    	res = 0; bool bo = 0; char c;
    	while (((c = getchar()) < '0' || c > '9') && c != '-');
    	if (c == '-') bo = 1; else res = c - 48;
    	while ((c = getchar()) >= '0' && c <= '9')
    		res = (res << 3) + (res << 1) + (c - 48);
    	if (bo) res = ~res + 1;
    }
    
    template <class T>
    inline T Min(const T &a, const T &b) {return a < b ? a : b;}
    
    const int N = 205, M = 5e4 + 5, INF = 0x3f3f3f3f;
    
    int n, m, d[M], ans, d1[N][N], d2[N][N], d3[N][N], d4[N][N], alt[M], lf[M], rf[M];
    bool vis[N];
    std::vector<int> eg[N][N];
    
    struct graph
    {
    	int ecnt, nxt[M], adj[N], st[M], go[M], val[M];
    
    	void add_edge(int u, int v, int w)
    	{
    		nxt[++ecnt] = adj[u]; adj[u] = ecnt; st[ecnt] = u; go[ecnt] = v; val[ecnt] = w;
    	}
    	
    	void dij(int S, int X, int *dis)
    	{
    		for (int i = 1; i <= n; i++) dis[i] = INF;
    		memset(vis, 0, sizeof(vis));
    		if (S != X) dis[S] = 0;
    		while (1)
    		{
    			int mn = INF, u = 0;
    			for (int v = 1; v <= n; v++)
    				if (v != X && !vis[v] && dis[v] < mn) mn = dis[v], u = v;
    			if (!u) return; vis[u] = 1;
    			for (int e = adj[u], v = go[e]; e; e = nxt[e], v = go[e])
    				if (v != X) dis[v] = Min(dis[v], dis[u] + val[e]);
    		}
    	}
    } G1, G2;
    
    int main()
    {
    	int x, y, z;
    	read(n); read(m);
    	for (int i = 1; i <= m; i++)
    		read(x), read(y), read(z), read(d[i]), G1.add_edge(x, y, z),
    			G2.add_edge(y, x, z), eg[x][y].push_back(i);
    	for (int i = 0; i <= n; i++) G1.dij(1, i, d1[i]), G1.dij(n, i, d2[i]),
    		G2.dij(1, i, d3[i]), G2.dij(n, i, d4[i]);
    	ans = d1[0][n] + d2[0][1];
    	for (int u = 1; u <= n; u++)
    		for (int v = 1; v <= n; v++)
    		{
    			int len = eg[u][v].size();
    			lf[0] = rf[len + 1] = INF;
    			for (int i = 1; i <= len; i++)
    				lf[i] = Min(lf[i - 1], G1.val[eg[u][v][i - 1]]);
    			for (int i = len; i >= 1; i--)
    				rf[i] = Min(rf[i + 1], G1.val[eg[u][v][i - 1]]);
    			for (int i = 1; i <= len; i++) alt[eg[u][v][i - 1]] =
    				Min(lf[i - 1], rf[i + 1]);
    		}
    	for (int e = 1; e <= m; e++)
    	{
    		int u = G1.st[e], v = G1.go[e], dis1 = Min(INF, d1[u][v] + d4[v][u] + G1.val[e]),
    			dis2 = Min(INF, d2[u][v] + d3[v][u] + G1.val[e]);
    		if (alt[e] < INF) dis1 = Min(dis1, d1[0][u] + d4[0][v] + alt[e]),
    			dis2 = Min(dis2, d2[0][u] + d3[0][v] + alt[e]);
    		dis1 = Min(dis1, Min(d1[u][n], d1[v][n]));
    		dis2 = Min(dis2, Min(d2[u][1], d2[v][1]));
    		dis1 = Min(dis1, d1[0][u] + d4[v][u]);
    		dis2 = Min(dis2, d2[0][u] + d3[v][u]);
    		for (int x = 1; x <= n; x++) if (x != u && x != v)
    			dis1 = Min(dis1, d1[v][x] + d4[u][x]),
    			dis2 = Min(dis2, d2[v][x] + d3[u][x]);
    		if (dis1 < INF && dis2 < INF) ans = Min(ans, dis1 + dis2 + d[e]);
    	}
    	return std::cout << (ans < INF ? ans : -1) << std::endl, 0;
    }
    

    E

    题意

    • 一个 (N) 个数的序列 (S)(Q) 组询问

    • 每次询问给出 (T,L,R),回答对于所有 (Lle ile R)(S) 的区间 ([max(1,i-T),i]) 最大值之和

    • (1le N,Qle2 imes10^5)(1le Sle10^9)

    Solution

    • 为了方便讨论,下面 (T) 加上一,即表示以 (i) 为结尾的长度为 (K) 的区间

    • 先预处理 (pre_i) 表示 (i) 左边第一个严格大于 (a_i) 的数所在下标(没有则为 (-infty))(由于左端点是 (max(1,i-T+1)),故不存在时不能为 (0)

    • (suf_i) 表示 (i) 右边第一个不小于 (a_i) 的数所在下标(没有则为 (N+1))(注意一个是严格大于一个是不小于,为避免重复)

    • 考虑固定一个 (T) 时,(S_k) 成为以 (i) 为结尾的区间最大值的条件:

    • [iin[k+max(0,T-k+pre_k),min(suf_k-1,k+T-1)] ]

    • 设这个区间为 ([l_k,r_k]),考虑这两个端点随 (T) 递增时的变化:(r) 先递增 (1),然后维持不变,(l) 先维持不变,然后递增 (1)

    • 回到询问,我们要求的是:

    • [sum_{i=L}^Rsum_k[l_kle ile r_k]S_k ]

    • 考虑容斥,用 (ile r) 减去 (i<l),下面只考虑 (r)

    • 如果在当前的 (T)(r_k) 维持不变,则可以用线段树,区间 ([le r_k]) 加一,询问区间 ([L,R])

    • 否则把 (r_k) 表示成 (r'_k+T) 的形式,再开一棵线段树,区间 ([le r'_k]) 加一,询问区间 ([L-T,R-T])

    • (T) 是不断变化的,故可以把询问离线按 (T) 排序之后,边询问边按 (T) 从小到大处理 (l)(r) 的变化,注意 (l) 不能大于 (r),遇到这种情况需要把对应区间删掉

    • (O((N+Q)log N))

    Code

    #include <bits/stdc++.h>
    #define p2 p << 1
    #define p3 p << 1 | 1
    
    template <class T>
    inline void read(T &res)
    {
    	res = 0; bool bo = 0; char c;
    	while (((c = getchar()) < '0' || c > '9') && c != '-');
    	if (c == '-') bo = 1; else res = c - 48;
    	while ((c = getchar()) >= '0' && c <= '9')
    		res = (res << 3) + (res << 1) + (c - 48);
    	if (bo) res = ~res + 1;
    }
    
    typedef long long ll;
    
    const int N = 2e5 + 5, M = N << 3;
    
    int n, q, a[N], top, stk[N], pre[N], suf[N];
    ll ans[N];
    
    struct seg
    {
    	ll sum[M], add[M];
    	
    	void down(int l, int r, int mid, int p)
    	{
    		add[p2] += add[p]; add[p3] += add[p];
    		sum[p2] += add[p] * (mid - l + 1); sum[p3] += add[p] * (r - mid);
    		add[p] = 0;
    	}
    	
    	void change(int l, int r, int s, int e, ll v, int p)
    	{
    		if (e < l || s > r) return;
    		if (s <= l && r <= e) return (void) (add[p] += v, sum[p] += v * (r - l + 1));
    		int mid = l + r >> 1; down(l, r, mid, p);
    		change(l, mid, s, e, v, p2); change(mid + 1, r, s, e, v, p3);
    		sum[p] = sum[p2] + sum[p3];
    	}
    	
    	ll ask(int l, int r, int s, int e, int p)
    	{
    		if (e < l || s > r) return 0;
    		if (s <= l && r <= e) return sum[p];
    		int mid = l + r >> 1; down(l, r, mid, p);
    		return ask(l, mid, s, e, p2) + ask(mid + 1, r, s, e, p3);
    	}
    } L1, L2, R1, R2;
    
    struct query
    {
    	int l, r, id;
    };
    
    std::vector<query> que[N];
    std::vector<int> t1[N], t2[N], t3[N];
    
    int main()
    {
    	int t, l, r;
    	read(n); read(q);
    	for (int i = 1; i <= n; i++) read(a[i]);
    	stk[top = 0] = 0;
    	for (int i = 1; i <= n; i++)
    	{
    		while (top && a[stk[top]] <= a[i]) top--;
    		pre[i] = stk[top]; stk[++top] = i;
    	}
    	stk[top = 0] = n + 1;
    	for (int i = n; i >= 1; i--)
    	{
    		while (top && a[stk[top]] < a[i]) top--;
    		suf[i] = stk[top]; stk[++top] = i;
    	}
    	for (int i = 1; i <= q; i++)
    	{
    		read(t); read(l); read(r); if (t < n) t++;
    		que[t].push_back((query) {l, r, i});
    	}
    	for (int i = 1; i <= n; i++)
    		L1.change(-n, n, -n, i, a[i], 1), R2.change(-n, n, -n, i - 1, a[i], 1);
    	for (int i = 1; i <= n; i++)
    	{
    		t1[suf[i] - i].push_back(i);
    		if (pre[i]) t2[i - pre[i]].push_back(i), t3[suf[i] - pre[i]].push_back(i);
    	}
    	for (int i = 1; i <= n; i++)
    	{
    		for (int j = 0; j < t1[i].size(); j++)
    			R2.change(-n, n, -n, t1[i][j] - 1, -a[t1[i][j]], 1),
    			R1.change(-n, n, -n, suf[t1[i][j]] - 1, a[t1[i][j]], 1);
    		for (int j = 0; j < t2[i].size(); j++)
    			L1.change(-n, n, -n, t2[i][j], -a[t2[i][j]], 1),
    			L2.change(-n, n, -n, t2[i][j] - i, a[t2[i][j]], 1);
    		for (int j = 0; j < t3[i].size(); j++)
    			R1.change(-n, n, -n, suf[t3[i][j]] - 1, -a[t3[i][j]], 1),
    			L2.change(-n, n, -n, suf[t3[i][j]] - i, -a[t3[i][j]], 1);
    		for (int j = 0; j < que[i].size(); j++)
    		{
    			int l = que[i][j].l, r = que[i][j].r;
    			ans[que[i][j].id] = R1.ask(-n, n, l, r, 1) + R2.ask(-n, n, l - i, r - i, 1)
    				- L1.ask(-n, n, l + 1, r + 1, 1)
    					- L2.ask(-n, n, l - i + 1, r - i + 1, 1);
    		}
    	}
    	for (int i = 1; i <= q; i++) printf("%lld
    ", ans[i]);
    	return 0;
    }
    
  • 相关阅读:
    [HNOI2015]亚瑟王(概率dp)
    [IOI1998]Polygon(区间dp)
    [SCOI2003]字符串折叠(区间dp)
    [BJOI2006]狼抓兔子(网络流)
    [NOIP2017普及组]跳房子(二分,单调队列优化dp)
    洛谷 P2863 [USACO06JAN]牛的舞会The Cow Prom(Tarjan)
    [SCOI2010]股票交易(单调队列优化dp)
    [POJ1821]Fence(单调队列优化dp)
    [Luogu2365]任务安排(斜率优化)
    股神小L
  • 原文地址:https://www.cnblogs.com/xyz32768/p/13171680.html
Copyright © 2020-2023  润新知