zoukankan      html  css  js  c++  java
  • JOI Open 2019 题解

    题目传送门:LOJ「JOI Open 2019」

    三级跳 / 三段跳び / Triple Jump

    考虑一组合法的 (a, b, c),如果在 (a, b) 之间存在一个下标 (i) 满足 (A_i ge A_a)(A_i ge A_b),则显然选择 (i) 不更劣。

    也就是说,如果只考虑这样的 (a, b):满足它们之间的数都小于 (min(A_a, A_b));答案也不会改变。

    可以发现这样的 (a, b) 只有 (mathcal O (N)) 组,也就是对于每个点来说,左边和右边第一个大于等于本身的数。

    (mathcal O (N))(a, b) 可以通过单调栈求出。

    对于询问,考虑离线,按照左端点从大到小排序。把那些可行的 (a, b) 也记录在左端点((a))上。

    在进行扫描线的同时,对于每个右端点,维护一个 (B) 值,表示如果选它作为 (c),最大的 (A_a + A_b) 的值。

    在处理询问前先加入这些新的 (a, b),相当于把所有在 (2 b - a) 右侧的位置的 (B) 值与 (A_a + A_b)(max)

    询问时则是询问 ([l + 2, r]) 之间的所有位置的最大的 (B_i + A_i) 之和。

    因为 (B) 只会变大,可以用线段树维护区间中最大的 (A_i) 以及最大的 (B_i + A_i),使用一个标记下传对 (B) 的取 (max) 操作即可。

    代码如下,时间复杂度为 (mathcal O ((N + Q) log N))

    #include <cstdio>
    #include <algorithm>
    #include <vector>
    
    typedef long long LL;
    const int Inf = 0x3f3f3f3f;
    const int MN = 500005, MQ = 500005, MS = 1 << 20 | 7;
    
    void chkmx(LL &x, LL y) { x = x < y ? y : x; }
    
    int N, Q;
    LL A[MN];
    int stk[MN], tp;
    std::vector<int> V[MN], W[MN];
    int qr[MQ]; LL Ans[MQ];
    
    #define li (i << 1)
    #define ri (li | 1)
    #define mid ((l + r) >> 1)
    #define ls li, l, mid
    #define rs ri, mid + 1, r
    LL v2[MS], v3[MS], tg[MS];
    inline void P(int i, LL x) { chkmx(tg[i], x), chkmx(v3[i], x + v2[i]); }
    inline void Pushdown(int i) { if (tg[i]) P(li, tg[i]), P(ri, tg[i]), tg[i] = 0; }
    void Build(int i, int l, int r) {
    	if (l == r) return v2[i] = v3[i] = A[l], void();
    	Build(ls), Build(rs);
    	v2[i] = v3[i] = std::max(v2[li], v2[ri]);
    }
    void Mdf(int i, int l, int r, int a, int b, LL x) {
    	if (r < a || b < l) return ;
    	if (a <= l && r <= b) return P(i, x);
    	Pushdown(i), Mdf(ls, a, b, x), Mdf(rs, a, b, x);
    	v3[i] = std::max(v3[li], v3[ri]);
    }
    LL Qur(int i, int l, int r, int a, int b) {
    	if (r < a || b < l) return 0;
    	if (a <= l && r <= b) return v3[i];
    	Pushdown(i);
    	return std::max(Qur(ls, a, b), Qur(rs, a, b));
    }
    
    int main() {
    	scanf("%d", &N);
    	for (int i = 1; i <= N; ++i) scanf("%lld", &A[i]);
    	A[stk[tp = 1] = 0] = Inf;
    	for (int i = 1; i <= N; ++i) {
    		while (A[stk[tp]] < A[i]) V[stk[tp--]].push_back(i);
    		if (stk[tp]) V[stk[tp]].push_back(i);
    		stk[++tp] = i;
    	} while (tp) --tp;
    	scanf("%d", &Q);
    	for (int i = 1, x; i <= Q; ++i) scanf("%d%d", &x, &qr[i]), W[x].push_back(i);
    	Build(1, 1, N);
    	for (int i = N - 2; i >= 1; --i) {
    		for (int j : V[i]) if (j + j - i <= N) Mdf(1, 1, N, j + j - i, N, A[i] + A[j]);
    		for (int j : W[i]) Ans[j] = Qur(1, 1, N, i + 2, qr[j]);
    	}
    	for (int i = 1; i <= Q; ++i) printf("%lld
    ", Ans[i]);
    	return 0;
    }
    

    汇款 / 送金 / Remittance

    汇款的系数矩阵满秩,通过高斯消元(稀疏矩阵)解出每个房子要向下一个房子汇多少钱,在模意义下计算可以防止浮点数。

    如果解出来是小数或者负数,一定不可能,求出模意义下的数值后这可以很好地判断。

    如果初始钱数非零,但是最终总钱数为零,也不可能。

    否则一定可行。

    证明?不知道。反正其他做法(就是模拟 (log N) 轮的做法)也没证明。

    下面是代码,时间复杂度为 (mathcal O (N + log mathrm{MOD}))

    #include <cstdio>
    
    typedef long long LL;
    const int Mod = 1000000007;
    const int MN = 1000005;
    
    inline int qPow(int b, int e) {
    	int a = 1;
    	for (; e; e >>= 1, b = (LL)b * b % Mod)
    		if (e & 1) a = (LL)a * b % Mod;
    	return a;
    }
    inline int gInv(int b) { return qPow(b, Mod - 2); }
    
    int N, A[MN], B[MN], X[MN];
    int xk[MN], xb[MN];
    int ok;
    
    int main() {
    	scanf("%d", &N);
    	for (int i = 1; i <= N; ++i) scanf("%d%d", &A[i], &B[i]);
    	ok = 1;
    	for (int i = 1; i <= N; ++i) if (A[i] != B[i]) ok = 0;
    	if (ok) return puts("Yes"), 0;
    	xk[N] = 1, xb[N] = 0;
    	for (int i = N - 1; i >= 1; --i) {
    		xk[i] = 2 * xk[i + 1];
    		if (xk[i] >= Mod) xk[i] -= Mod;
    		xb[i] = (2ll * xb[i + 1] + B[i + 1] - A[i + 1] + Mod) % Mod;
    	}
    	X[N] = (2ll * xb[1] + B[1] - A[1] + Mod) * gInv((1 + 2 * (Mod - xk[1])) % Mod) % Mod;
    	for (int i = 1; i < N; ++i) X[i] = ((LL)xk[i] * X[N] + xb[i]) % Mod;
    	ok = 1;
    	for (int i = 1; i <= N; ++i)
    		if (X[(i + N - 2) % N + 1] - 2ll * X[i] != B[i] - A[i]) ok = 0;
    	if (!ok) return puts("No"), 0;
    	ok = 0;
    	for (int i = 1; i <= N; ++i) if (B[i]) ok = 1;
    	if (!ok) return puts("No"), 0;
    	puts("Yes");
    	return 0;
    }
    

    病毒实验 / ウイルス実験 / Virus Experiment

    假定我们已知任意两个格子之间的关系:如果仅 (u) 感染了病毒,那么 (v) 在若干时刻后也会被感染 / 永远不会被感染。

    显然这个图强连通分量缩点之后就是一个 DAG,我们需要考虑所有的无出度的强连通分量的大小。

    然而我们并不已知这些关系,也无法存储。那么我们到底能够求什么?

    我们可以写一个 BFS 来求出如果初始某格子被感染,最终会感染到哪些格子,这是如何做到的呢:

    • 首先,一个格子是否会被感染,仅和它上下左右的格子是否会被感染有关。

    • 我们用 (16 imes mathcal O (M)) 的时间,预处理出上下左右四个格子是否被感染共 (2^4 = 16) 种情况下,被有病毒的风吹的最长时间。

    那么每次拓展一个新格子的时候,就可以在 (mathcal O (1)) 的时间内确定它周围的格子目前是否会被感染。

    这样在每个点运行一遍 BFS 就可以得到 (mathcal O (R^2 C^2)) 的时间复杂度。

    考虑这样一个事实:如果初始 (u) 感染了,可以让 (v) 也感染,则 (u) 导致的感染人数一定不少于 (v) 导致的感染人数。

    而且再考虑到这个关系是有传递性的:如果 (u o v) 并且 (v o w),那么一定有 (u o w)

    这提示我们不一定每次运行 BFS 都需要运行“满”(不能再拓展新点),可以在某些条件下中断,以减少复杂度。

    考虑维护这样的一些区域:每个区域中有一个关键点,表示从这个区域内的任何一点出发,都能到达这个关键点。

    一开始每个格子自成一个区域,然后这些区域会逐渐合并:

    我们任取一个区域,从它的关键点出发 BFS,一旦到达了一个不在本区域内的格子,就把该区域并入到达的区域中。

    可以发现这样的过程是满足区域的性质的,但是复杂度仍然不变,考虑优化。

    考虑求最小生成树的 Boruvka 算法,它保证每层的时间是 (mathcal O (n)) 的,并且让连通块个数除以 (2),所以复杂度为 (mathcal O (n log n))

    把这个思想运用到本题中:枚举每个区域,如果 BFS 到了新区域就合并,但是如果该区域已经被合并过了,就不进行 BFS 了。

    可以发现,每次从一个区域进行 BFS 的时间复杂度为 (mathcal O (mathrm{size})),其中 (mathrm{size}) 为该区域大小(因为只要找到了新格子就立刻退出)。

    这样可以保证每层的总时间复杂度不会超过 (mathcal O (RC))

    并且也最多执行 (mathcal O (log(RC))) 层,这是因为:

    如果某次 BFS 没有到达任何新区域,则它一定对应了 DAG 中没有出度的一个连通分量。

    对于其它区域,它们要么找到一个还没被合并过的区域,这两个区域在这一层都不能用了,或者找到一个已经被合并过的区域。

    前者让 (2) 个区域变成 (1) 个,后者让 (1) 个区域变成 (0) 个。

    所以无论如何,每一层至少少掉一半的区域(除了对应最后的无出度的连通分量的区域),最多执行 (mathcal O (log (RC))) 层。

    下面是代码,时间复杂度为 (mathcal O (M + RC log(RC)))

    #include <cstdio>
    #include <algorithm>
    
    const int MK = 100005, MN = 805, MS = 640005;
    const char dir[5] = "NSWE";
    const int dx[4] = {-1, 1, 0, 0}, dy[4] = {0, 0, -1, 1};
    
    int K, S[MK * 2], N, M, A[MN][MN], tim[16];
    void Init() {
    	static char Str[MK];
    	scanf("%d%d%d%s", &K, &N, &M, Str + 1);
    	for (int i = 1; i <= K; ++i)
    		for (int j = 0; j < 4; ++j)
    			if (Str[i] == dir[j]) S[i] = S[K + i] = j;
    	for (int i = 1; i <= N; ++i)
    		for (int j = 1; j <= M; ++j) {
    			scanf("%d", &A[i][j]);
    			if (A[i][j] == 0) A[i][j] = 100001;
    		}
    	for (int d = 1; d < 16; ++d) {
    		int lst = 0, mxl = 0;
    		for (int i = 1; i <= 2 * K; ++i) {
    			if (d >> S[i] & 1) mxl = std::max(mxl, ++lst);
    			else lst = 0;
    		}
    		if (mxl == 2 * K) mxl = 100000;
    		tim[d] = mxl;
    	}
    }
    
    int bx[MN][MN], by[MN][MN], tx[MN][MN], ty[MN][MN];
    
    int col[MN][MN], tot, qx[MS], qy[MS], lb, rb;
    inline bool chk(int x, int y) {
    	int stat = 0;
    	for (int d = 0; d < 4; ++d) {
    		int nx = x + dx[d], ny = y + dy[d];
    		if (nx < 1 || nx > N || ny < 1 || ny > M) continue;
    		if (col[nx][ny] == tot) stat |= 1 << d;
    	}
    	return A[x][y] <= tim[stat];
    }
    inline bool BFS(int sx, int sy) {
    	if (A[sx][sy] > 100000) {
    		tx[sx][sy] = sx;
    		ty[sx][sy] = sy;
    		rb = N * M + 1;
    		return 0;
    	}
    	col[sx][sy] = ++tot;
    	lb = rb = 1, qx[1] = sx, qy[1] = sy;
    	while (lb <= rb) {
    		int x = qx[lb], y = qy[lb]; ++lb;
    		for (int d = 0; d < 4; ++d) {
    			int nx = x + dx[d], ny = y + dy[d];
    			if (nx < 1 || nx > N || ny < 1 || ny > M) continue;
    			if (col[nx][ny] == tot) continue;
    			if (!chk(nx, ny)) continue;
    			int nbx = bx[nx][ny], nby = by[nx][ny];
    			if (nbx != sx || nby != sy) {
    				int ntx = tx[nbx][nby], nty = ty[nbx][nby];
    				if (ntx) {
    					tx[sx][sy] = ntx, ty[sx][sy] = nty;
    					return 1;
    				}
    				tx[sx][sy] = tx[nbx][nby] = nbx;
    				ty[sx][sy] = ty[nbx][nby] = nby;
    				return 1;
    			}
    			col[nx][ny] = tot;
    			++rb, qx[rb] = nx, qy[rb] = ny;
    		}
    	}
    	tx[sx][sy] = sx;
    	ty[sx][sy] = sy;
    	return 0;
    }
    
    int ans, cnt;
    
    int main() {
    	Init();
    	ans = N * M + 1;
    	for (int i = 1; i <= N; ++i)
    		for (int j = 1; j <= M; ++j)
    			bx[i][j] = i, by[i][j] = j;
    	while (1) {
    		int ok = 0;
    		for (int i = 1; i <= N; ++i)
    			for (int j = 1; j <= M; ++j)
    				tx[i][j] = ty[i][j] = 0;
    		for (int i = 1; i <= N; ++i)
    			for (int j = 1; j <= M; ++j) {
    				int x = bx[i][j], y = by[i][j];
    				if (tx[x][y]) continue;
    				int ret = BFS(x, y);
    				if (ret) ok = 1;
    			}
    		for (int i = 1; i <= N; ++i)
    			for (int j = 1; j <= M; ++j) {
    				int bi = bx[i][j], bj = by[i][j];
    				bx[i][j] = tx[bi][bj];
    				by[i][j] = ty[bi][bj];
    			}
    		if (!ok) break;
    	}
    	for (int i = 1; i <= N; ++i)
    		for (int j = 1; j <= M; ++j)
    			tx[i][j] = ty[i][j] = 0;
    	for (int i = 1; i <= N; ++i)
    		for (int j = 1; j <= M; ++j) {
    			int x = bx[i][j], y = by[i][j];
    			if (tx[x][y]) continue;
    			BFS(x, y);
    			if (ans > rb) cnt = ans = rb;
    			else if (ans == rb) cnt += rb;
    		}
    	printf("%d
    %d
    ", ans, cnt);
    	return 0;
    }
    
  • 相关阅读:
    【SSM】Eclipse使用Maven创建Web项目+整合SSM框架(转自:http://www.cnblogs.com/DoubleEggs/p/6243216.html)
    odoo Wkhtmltopdf failed (error code: -11). Memory limit too low or maximum file number of subprocess reached. Message : b''
    odoo 补货规则,当某个位置的产品匹配到多个规则时优先使用序列号最小的规则
    odoo 销售订单确认怎么触发的发货规则
    linux 内核升级
    linux 运维工具之jumpserver
    odoo 关于odoo 的批量拣货
    JIT
    sping+redis实现消息队列的乱码问题
    JVM参数设置
  • 原文地址:https://www.cnblogs.com/PinkRabbit/p/JOIOpen2019.html
Copyright © 2011-2022 走看看