• 「$mathcal{Atcoder}$」$mathcal{ARC101}$


    闲来无事,写了一场比赛题,大家都认为题目质量很高,所以才去做的,不过题确实不错

    ——前言

    (mathcal{Candles})

    算是本场的签到题

    我们可以先找到 (0) 的位置

    容易发现,走的路径一定是一段包含 (0) 点的区间

    一段区间的答案是:向左走的距离 (dis_l) 加上向右走的距离 (dis_r) 加上 (min(dis_l,dis_r))

    显然区间的个数是 (mathcal{O}(n)) 的,对这 (n) 个区间答案取个 (min) 就是最后的答案,复杂度是 (mathcal{O}(n))

    代码
    #include <cstdio>
    #include <cstring>
    #include <iostream>
    #include <algorithm>
    
    using namespace std;
    
    const int maxn = 1e5 + 50, INF = 0x3f3f3f3f;
    
    inline int read () {
    	register int x = 0, w = 1;
    	register char ch = getchar ();
    	for (; ch < '0' || ch > '9'; ch = getchar ()) if (ch == '-') w = -1;
    	for (; ch >= '0' && ch <= '9'; ch = getchar ()) x = x * 10 + ch - '0';
    	return x * w;
    }
    
    int n, m, L, R = 1, ans = INF, a[maxn], b[maxn];
    
    int main () {
    	n = read(), m = read();
    	for (register int i = 1; i <= n; i ++) a[i] = read(), b[i] = abs (a[i]);
    	for (register int i = 1; i <= n; i ++)
    		if (a[i] < 0 && a[i + 1] >= 0) L = i, R = i + 1;
    	if (L >= m) {
    		register int l = L - m + 1, r = R - 1;
    		while (r <= n && l <= R) {
    			if (a[l] < 0 && a[r] > 0) ans = min (ans, b[l] + b[r] + min (b[l], b[r]));
    			else if (a[l] < 0 && a[r] <= 0) ans = min (ans, b[l]);
    			else if (a[l] >= 0 && a[r] >= 0) ans = min (ans, b[r]);
    			l ++, r ++;
    		}
    	} else {
    		register int l = 1, r = R + m - L - 1;
    		while (r <= n && l <= R) {
    			if (a[l] < 0 && a[r] > 0) ans = min (ans, b[l] + b[r] + min (b[l], b[r]));
    			else if (a[l] < 0 && a[r] <= 0) ans = min (ans, b[l]);
    			else if (a[l] >= 0 && a[r] >= 0) ans = min (ans, b[r]);
    			l ++, r ++;
    		}
    	}
    	return printf ("%d
    ", ans), 0;
    }
    

    (mathcal{Median;of;Medians})

    首先定义中位数,将 (n) 个数排序后,如果有奇数个数,则中位数为 (a[frac{n}{2}]),否则中位数为 (a[frac{n}{2}+1])

    让你求出 (frac{n(n+1)}{2}) 个区间中位数的中位数

    首先考虑一下中位数的性质:

    • 如果 (n) 为奇数,中位数满足

    [num_{a[i]geq a[mid]}=num_{a[i]leq a[mid]} ]

    • 如果 (n) 为偶数,中位数满足

    [num_{a[i]geq a[mid]}=num_{a[i]leq a[mid]}-1 ]

    不妨我们二分答案 (x),然后用中位数的性质 (check) 答案是否正确,现在问题是如何求出有多少个区间的中位数大于等于 (x),有多少个区间的中位数小于等于 (x)

    我们将 (geq x) 的数置为 (1)(< x) 的数置为 (-1)

    容易发现,一个区间的和 (geq 0),说明这个区间的中位数 (geq x),如果这个和 (< 0),说明这个区间的中位数 (leq x)

    根据求区间和 (sum[r]-sum[l-1]) 的式子,我们可以对于每个右端点求出左边有多少个点的 (sum[l] leq sum[r]),同时也可以求出 (sum[l] > sum[r]) 的个数,这个用树状数组维护一下就行了

    最后复杂度是二分内套一个 (mathcal{O}(nlog n)),所以是 (mathcal{O}(nlog^2 n))

    代码
    #include <cstdio>
    #include <cstring>
    #include <iostream>
    #include <algorithm>
    
    typedef long long ll;
    
    using namespace std;
    
    const int maxn = 2e5 + 50, INF = 0x3f3f3f3f, base = 1e5;
    
    inline int read () {
    	register int x = 0, w = 1;
    	register char ch = getchar ();
    	for (; ch < '0' || ch > '9'; ch = getchar ()) if (ch == '-') w = -1;
    	for (; ch >= '0' && ch <= '9'; ch = getchar ()) x = x * 10 + ch - '0';
    	return x * w;
    }
    
    int n, typ, a[maxn], b[maxn], c[maxn], tree[maxn];
    
    inline void Insert0 (register int x) {
    	for (; x <= maxn; x += x & -x) tree[x] ++;
    }
    
    inline int Query0 (register int x, register int ans = 0) {
    	for (; x; x -= x & -x) ans += tree[x];
    	return ans;
    }
    
    inline void Insert1 (register int x) {
    	for (; x; x -= x & -x) tree[x] ++;
    }
    
    inline int Query1 (register int x, register int ans = 0) {
    	for (; x <= maxn; x += x & -x) ans += tree[x];
    	return ans;
    }
    
    inline bool Check (register int x, register ll num0 = 0, register ll num1 = 0) {
    	memset (tree, 0, sizeof tree), Insert0 (0 + base);
    	for (register int i = 1; i <= n; i ++) c[i] = a[i] < x ? -1 : 1;
    	for (register int i = 1, res = 0; i <= n; i ++) 
    		res += c[i], num0 += Query0 (res + base), Insert0 (res + base);
    	memset (tree, 0, sizeof tree), Insert1 (0 + base);
    	for (register int i = 1, res = 0; i <= n; i ++) 
    		res += c[i], num1 += Query1 (res + base + 1), Insert1 (res + base);
    	return num0 >= num1 - typ;
    }
    
    int main () {
    	n = read(), typ = 1ll * n * (n + 1) / 2 % 2 == 0;
    	for (register int i = 1; i <= n; i ++) a[i] = b[i] = read();
    	sort (b + 1, b + n + 1);
    	register int L = 1, R = n;
    	while (L <= R) {
    		register int mid = (L + R) >> 1;
    		if (Check (b[mid])) L = mid + 1;
    		else R = mid - 1;
    	}
    	return printf ("%d
    ", b[L - 1]), 0;
    }
    

    (mathcal{Ribbons;on;Tree})

    首先可以很容易想到一个 (mathcal{O}(n^3))(dp),在子树归并的同时枚举这次匹配的点对数,然后转移

    发现这个 (dp) 不太好进行优化,考虑换一个思路去做

    题目中要求的是恰好所有边都被经过,即恰好有 (0) 条边不被经过,不妨考虑子集反演,钦定集合 (Sin E)(F(S)) 表示集合 (S) 中的边都不被经过的方案数,那我们最后要求的答案就变成了

    [sum(-1)^{|S|}F(S) ]

    考虑如何求 (F(S)),我们将这些边都断掉,会形成 (|S|-1) 个联通块,因为定义中并没有钦定剩下的边一定被经过,所以直接在同一个联通块里随便选两个点连就行了,要求的就是是这 (|S|-1) 个联通块中,每个联通块里的每两个点匹配且要完全匹配的方案数,显然这 (|S|-1) 个联通块是独立的,可以分开求

    定义 (g(n)) 表示一个联通块里有 (n) 个点,两两随便匹配的方案数,因为点对是无序的,点对与点对之间也是无序的,所以我们直接钦定它有序,对每个点选个点匹配就行,直接可以得出式子

    [g(n)=[nequiv 0pmod2](n-1)(n-3)......3 imes 1 ]

    所以对于一个 (F(S)),其实就是它分成的若干个联通块的 (g(n)) 乘起来

    接下来我们可以考虑继续在树上 (dp) 来解决这个问题

    定义 (f[i][j]) 表示以 (i) 为根的子树,(i) 所在的联通块大小为 (j) 的方案数

    然后子树归并,转移是决策 (u) 所在的联通块是否与 (v) 所在的联通块合并就行了

    特别的,根据定义和转移,我们发现 (f[i][0]) 其实就是不与上面的联通块合并,即断掉了 (fa[i])(i) 之间的边,因为断边集合 (S) 大小增加了 (1),需要乘上个 (-1),而且现在一个联通块考虑完了,同时在乘上一个 (g(j)) 即可,即

    [f[u][0]=-1 imes sum_{j=1}^{size[u]}f[u][j] imes g(j) ]

    最后特殊考虑一下根节点,因为它没有 (fa),所以无边可断,不需要乘 (-1),最后答案即为 (-1 imes f[1][0])

    因为只剩下了子树归并,所以复杂度是 (mathcal{O}(n^2))

    代码
    #include <cstdio>
    #include <cstring>
    #include <iostream>
    #include <algorithm>
    
    typedef long long ll;
    
    using namespace std;
    
    const int maxn = 5e3 + 50, INF = 0x3f3f3f3f, mod = 1e9 + 7;
    
    inline int read () {
    	register int x = 0, w = 1;
    	register char ch = getchar ();
    	for (; ch < '0' || ch > '9'; ch = getchar ()) if (ch == '-') w = -1;
    	for (; ch >= '0' && ch <= '9'; ch = getchar ()) x = x * 10 + ch - '0';
    	return x * w;
    }
    
    inline int addmod (register int a, register int b) {
    	return a += b, a >= mod ? a - mod : a;
    }
    
    inline ll mulmod (register ll a, register int b) {
    	return a *= b, a >= mod ? a % mod : a;
    }
    
    int n;
    
    struct Edge {
    	int to, next;
    } e[maxn << 1];
    
    int tot, head[maxn];
    
    inline void Add (register int u, register int v) {
    	e[++ tot].to = v;
    	e[tot].next = head[u];
    	head[u] = tot;
    }
    
    int siz[maxn], f[maxn][maxn], g[maxn], p[maxn];
    
    inline void Init () {
    	g[0] = 1;
    	for (register int i = 2; i <= n; i += 2) 
    		g[i] = mulmod (g[i - 2], i - 1);
    }
    
    inline void DFS (register int u, register int fa) {
    	siz[u] = 1, f[u][1] = 1;
    	for (register int i = head[u]; i; i = e[i].next) {
    		register int v = e[i].to;
    		if (v == fa) continue;
    		DFS (v, u);
    		for (register int j = 1; j <= siz[u] + siz[v]; j ++) p[j] = 0;
    		for (register int j = 1; j <= siz[u]; j ++) 
    			for (register int k = 0; k <= siz[v]; k ++) 
    				p[j + k] = addmod (p[j + k], mulmod (f[u][j], f[v][k]));
    		for (register int j = 1; j <= siz[u] + siz[v]; j ++) f[u][j] = p[j];
    		siz[u] += siz[v];
    	}
    	for (register int j = 1; j <= siz[u]; j ++) 
    		f[u][0] = addmod (f[u][0], mulmod (f[u][j], g[j]));
    	f[u][0] = mod - f[u][0];
    }
    
    int main () {
    	n = read(), Init ();
    	for (register int i = 1, u, v; i < n; i ++)
    		u = read(), v = read(), Add (u, v), Add (v, u);
    	DFS (1, 0), printf ("%d
    ", mod - f[1][0]);
    	return 0;
    }
    

    (mathcal{Robots;and;Exits})

    容易发现,这 (m) 个出口会把 (n) 个机器人分成若干段

    对于某一段的这些机器人,要么从左边最近的出口消失,要么从右边最近的出口消失,从而我们可以得到每个机器人到左边的距离 (x),到右边的距离 (y),形成了若干个这样的点对 ((x,y))

    我们考虑两个机器人 ((x_0,y_0),(x_1,y_1)),如果 (x_0<x_1),那么只要机器人 (1) 从左边消失了,那么机器人 (0) 也一定是从左边消失的

    把这些点对放到坐标系上,问题其实就是每次将 (x) 轴往上移,或者将 (y) 轴往右移,先被 (x) 轴覆盖掉的为白色,先被 (y) 轴覆盖掉的为黑色

    我们可以将覆盖掉的点按顺序转化成一条路径,路径上的点满足 (x_{i-1}<x_iwedge y_{i-1}<y_i),我们要求的就是不同的路径条数

    具体操作时,先将左边和右边只有可能消失在一个出口的机器人去掉,然后求出剩下每个机器人的 ((x,y)),按照 (x_i) 排序,因为是严格小于,所以 (x_i) 相同的按照 (y_i) 从大到小排序

    最后做一遍最长上升子序列即可,用树状数组优化一下可以做到 (mathcal O({nlog n})) 的复杂度

    代码
    #include <cstdio>
    #include <cstring>
    #include <iostream>
    #include <algorithm>
    
    using namespace std;
    
    const int maxn = 1e5 + 50, INF = 0x3f3f3f3f, mod = 1e9 + 7;
    
    inline int read () {
    	register int x = 0, w = 1;
    	register char ch = getchar ();
    	for (; ch < '0' || ch > '9'; ch = getchar ()) if (ch == '-') w = -1;
    	for (; ch >= '0' && ch <= '9'; ch = getchar ()) x = x * 10 + ch - '0';
    	return x * w;
    }
    
    inline int addmod (register int a, register int b) {
    	return a += b, a >= mod ? a - mod : a;
    }
    
    int n, m, num, ans, lenx, leny, x[maxn], y[maxn], bx[maxn], by[maxn], tree[maxn];
    
    struct Node {
    	int x, y;
    	inline friend bool operator < (register const Node &a, register const Node &b) { return a.x == b.x ? a.y > b.y : a.x < b.x; }
    } a[maxn];
    
    inline void Insert (register int x, register int val) {
    	for (; x <= leny; x += x & -x) tree[x] = addmod (tree[x], val);
    }
    
    inline int Query (register int x, register int ans = 0) {
    	for (; x; x -= x & -x) ans = addmod (ans, tree[x]);
    	return ans;
    }
    
    int main () {
    	n = read(), m = read();
    	for (register int i = 1; i <= n; i ++) x[i] = read();
    	for (register int i = 1; i <= m; i ++) y[i] = read();
    	for (register int i = 1, res; i <= n; i ++) {
    		res = upper_bound (y + 1, y + m + 1, x[i]) - y;
    		if (res - 1 >= 1 && res <= m) num ++, a[num].x = bx[++ lenx] = x[i] - y[res - 1], a[num].y = by[++ leny] = y[res] - x[i];
    	}
    	sort (bx + 1, bx + lenx + 1), lenx = unique (bx + 1, bx + lenx + 1) - bx - 1;
    	sort (by + 1, by + leny + 1), leny = unique (by + 1, by + leny + 1) - by - 1;
    	for (register int i = 1; i <= num; i ++)
    		a[i].x = lower_bound (bx + 1, bx + lenx + 1, a[i].x) - bx, a[i].y = lower_bound (by + 1, by + leny + 1, a[i].y) - by;
    	sort (a + 1, a + num + 1);
    	for (register int i = 1, res; i <= num; i ++) {
    		if (a[i].x == a[i - 1].x && a[i].y == a[i - 1].y) continue;
    		res = Query (a[i].y) + 1, Insert (a[i].y + 1, res), ans = addmod (ans, res);
    	}
    	return printf ("%d
    ", addmod (ans, 1)), 0;
    }
    
  • 相关阅读:
    php模拟数据请求
    vue指令
    vue指令问题
    属性(property)的特性(attribute)
    数据属性和访问器属性
    json数组
    js--基础(对象、数组、函数、if语句、while语句、do while语句、continue语句、break语句)
    typeof操作符--undefined与null
    js自定义格式时间输出当前时间,封装时间函数
    js 要求传入两个日期时间,返回两个日期时间之间,相差多少天多少小时多少分钟多少秒
  • 原文地址:https://www.cnblogs.com/Rubyonly233/p/14906336.html
Copyright © 2020-2023  润新知