国庆模拟赛题解

国庆欢乐赛 Day 1

T1

\qquad 签到题。引以为戒:好好看题!!!!!!!!!!!!越简单的题越容易错!!!!!!!!!!!!!!

T2

\qquad 签到题,贪心、 d p dp dp 均可。

T3

T3

\qquad 注意套路:看到平均数,考虑让所有的数减去平均数,然后找累加和为0的区间个数。看到值域很小,考虑直接枚举平均数。可能有负数,但是不能用 m a p map map 存,因为会 T。所以要整体加一个偏移量,开一个大桶记录。不能 m e m s e t memset memset

T4

T4

\qquad 距离考试结束 20 m i n 20min 20min,想到了至关重要的性质: m i n b i min_{b_i} minbi左边的数一定是前缀最小,后面的数一定是后缀最小。但是因为当时只剩 20 m i n 20min 20min 了,就没细想,非常可惜。所以本题就是判断最小值左边能否构成不升序列,最小值右边能否构成不降序列。考虑到可能有多个最小值,我们枚举最小值填哪即可。

\qquad 核心代码:

b[0] = 1e9, lst = 0, pre[0] = 1;
for(inti = 1; i <= n; i ++) {//判断能否构成前缀不升
	if(b[i] == minn) pre[i] = pre[i - 1], loc.push_back(i);
	else {
		if(b[i] <= b[lst]) pre[i] = pre[i - 1], lst = i;
	}
}
b[n + 1] = 1e9, lst = n + 1, bac[n + 1] = 1;
for(int i = n; i >= 1; i --) {//判断能否构成后缀不降
	if(b[i] == minn) bac[i] = bac[i + 1];
	else {
		if(b[i] <= b[lst]) bac[i] = bac[i + 1], lst = i;
	}
}
for(auto i : loc) {
	if(pre[i - 1] && bac[i + 1]) {//这个最小值合法
		for(int j = 1; j <= n; j ++) {
			if(b[j] == minn && j != i) printf("%d ", maxn);//其余最小值的位置赋成极大值即可
			else printf("%d ", b[j]);
		}
		puts("");
		return 0;
	}
}

T5

T5

\qquad 一道很水的构造题,整体思路就是:先判断满足约束条件的情况下边数超不超,如果边数合法就往里加边使得满足约束条件。如果满足约束条件之后边没用完,就按照:连通块内部连边,连通块之间连边的先后优先级往里面补边。若补到不能再补的时候还未达到边数限制,那么也是不合法的。最后输出即可。

国庆欢乐赛 Day 3

T1

\qquad 将军饮马。

T2

\qquad 简单二分。

T3

T3

\qquad 折半搜索 + trie树。但是因为赛时数据过水,放走了一大堆无 s o r t sort sort d f s dfs dfs 做法,导致我方构造超强数据卡掉了他们。顺便卡掉了我写的随机化……

\qquad 在建 trie树时,我们维护一下 m a x p max_p maxp:以 p p p 为根的子树中价值最大值,在查询的时候分类讨论往下下即可。

\qquad 核心代码:

void Insert(int v, LL w) {
	int p = 0;
	for(int i = 30; i >= 0; i --) {
		int x = ((v >> i) & 1);
		if(!tr[p][x]) tr[p][x] = ++ tot;
		dat[p][x] = max(dat[p][x], w);//在插入时直接维护子树内价值最大值
		p = tr[p][x];
	}
}

LL query(int v) {
	int p = 0; LL res = 0xcfcfcfcfcfcfcfcf;
	for(int i = 30; i >= 0; i --) {
		int wv = ((v >> i) & 1), wm = ((m >> i) & 1);
		if(wv) {
			if(wm) {
				res = max(res, dat[p][1]);//此时走1这条路之后,异或出来的值一定比m小,所以在tr[p][1]这棵子树中可以随便走,直接取max即可。下面的以此类推。
				if(!tr[p][0]) return res;//走不动了
				p = tr[p][0];
			}
			else {
				if(!tr[p][1]) return res;
				p = tr[p][1];
			}
		}
		else {
			if(wm) {
				res = max(res, dat[p][0]);
				if(!tr[p][1]) return res;
				p = tr[p][1];
			}
			else {
				if(!tr[p][0]) return res;
				p = tr[p][0];
			}
		}
	}
	return res;
}

T4

T4

\qquad 博弈论入门题。

\qquad 我们先用一个引子来引入博弈论:假设现在有 n n n 个石头,每次可以拿走 1 / 2 1/2 1/2 个石子,最后没有石子可拿的人输。问什么时候先手必胜?很显然,当 n n n 不是 3 3 3 的倍数时,先手必胜。为什么呢?我们放到一个图上来说:
博弈论图

\qquad 在上图中,标 0 0 0 的点都是必败点,标 1 1 1 的点都是必胜点。不难发现,一个 1 1 1 点一定至少指向一个 0 0 0 点,而一个 0 0 0 点一定指向两个 1 1 1 点。为什么呢?我们考虑,在这个游戏中,双方是轮流取石子的,所以若点 i i i 表示的是自己先取,那么点 i i i 指向的点 j j j 一定是对方先取。在一个博弈图中,假设点 i i i 一共有 k k k 条出边指向 j 1 , j 2 , … , j k j_1,j_2,\dots ,j_k j1,j2,,jk 点。若 ∃ p ∈ [ 1 , k ] \exists p\in [1,k] p[1,k] 并且 j p j_p jp 是一个必败点,那么我们在点 i i i 就可以做出走到点 j p j_p jp 的决策使得我们必胜,所以点 i i i 是一个必胜点。同理,若不存在这么一个点 p p p,那么点 i i i 无论怎么操作都不会必胜,那么点 i i i 就是一个必败点。

\qquad 有了上面的博弈论基础,我们直接套到这个题上也是适用的。在一个点 i i i 处我们有三种策略:单独往左,单独往右,同时往左右,然后先手权转移给对方。若对方在这三种状态中有必败局面,那我们就必胜。而且我们注意到:它一次会让所有棋子同时操作,所以在任意局面,所有棋子一定分布在同一层内。又因为它是一棵满二叉树,所以只有 l o g log log 层。所以我们在递归时直接把这一层的所有棋子的状态存在一个 v e c t o r vector vector 中,作为参数向下递归,在层数限制下也不会开太多的 v e c t o r vector vector,时空复杂度上没有问题。

\qquad 核心代码:

int dfs(vector < int > Now, int opt, int num) {//Now:当前状态的棋子,opt:先手是谁 num:当前黑棋数量的奇偶性
	int flag = num;
	for(int i = 0; i < Now.size(); i ++) flag ^= col[Now[i]];
	//left
	vector < int > tt;
	for(int i = 0; i < Now.size(); i ++) {
		if(to[Now[i]].size()) {
			tt.push_back(to[Now[i]][0]);
		}
	}
	if(!tt.size()) {
		if((opt && !flag) || (!opt && flag)) return 1;
		else return 0;
	}
	if(!dfs(tt, opt ^ 1, flag)) return 1;
	//right
	tt.clear();
	for(int i = 0; i < Now.size(); i ++) {
		tt.push_back(to[Now[i]][1]);
	}
	if(!dfs(tt, opt ^ 1, flag)) return 1;
	//left and right
	tt.clear();
	for(int i = 0; i < Now.size(); i ++) {
		tt.push_back(to[Now[i]][0]);
		tt.push_back(to[Now[i]][1]);
	}
	if(!dfs(tt, opt ^ 1, flag)) return 1;
	return 0;
}

T5

T5
\qquad 看完题目,不难发现每一个数的变换都是有循环的。那这不直接秒了吗? 一看数据范围:寄喽,高精度!😔。

\qquad 高精度固然是可以写的,但是因为 m m m 的位数高达 1 0 6 10^6 106,光写高精运算就得 T 飞。但是我们注意到,本题中需要的高精运算只有高精取模。高精取模可以怎么写呢?每添加一位高精数我们就取一次模(基于积的余数等于余数的积,和的余数等于余数的和),我们高精取模的时间复杂度就降到了 O ( l e n m ) O(len_m) O(lenm),就可以顺利拿到 70 p t s 70pts 70pts。对于 100 p t s 100pts 100pts 怎么办呢?我们发现,若两个循环的长度相同,那么我们做一遍高精取模是不是就够了呢?又因为 1 + 2 + 3 + ⋯ + n = n 1+2+3+\dots + \sqrt n = n 1+2+3++n =n,所以我们最多只用做 n \sqrt n n 次。但是 O ( n n ) O(n\sqrt n) O(nn ) 还是过不掉,怎么办呢?压位!我们将每 18 18 18 位压到一个 l o n g    l o n g long\;long longlong 里,这样我们的时间复杂度就变成了 O ( n n 18 ) O(\frac{n\sqrt[]{n} }{18} ) O(18nn ),略带常数。在数据随机构造的情况下是跑不满的,所以勉强可过。

\qquad 核心代码:

for(int i = lm; i >= 1; i -= 18) {//压位,一定要从低位开始!!!!!!!!
	cnt ++;
	for(int j = max(1, i - 17); j <= i; j ++) m[cnt] = m[cnt] * 10 + (LL)(s[j] - '0');
}
for(int i = 1; i <= n; i ++) {//预处理循环
	if(pos[i]) continue;
	tot ++; int x = a[i], tim = 0;
	do {
		pos[x] = tot, f[tot].push_back(x), loc[x] = tim ++, x = a[x];
	} while(x != a[i]);
}
for(int i = 1; i <= tot; i ++) {//压位下的高精取模
	LL Mod = 0, sze = 1LL * f[i].size();
	if(!vis[sze]) {
		for(int j = cnt; j >= 1; j --) {//Pow=10^18
			Mod = (((((Mod % sze) * (Pow % sze) % sze) + m[j]) % sze) % sze);
		}
		mod[sze] = Mod;
		vis[sze] = 1;
	}
}

多校联考 Day 1

T1

T1

\qquad 签到题。倒着考虑,把无限加速看成一次只能加 1 1 1,把只能减速 1 1 1 看成可以无限减速,倒着处理即可。

T2

T2

\qquad 赛时忘了写记忆化,痛失 92 p t s 92pts 92pts,警钟厥烂!!!!!!!!!!

\qquad 首先,雇佣工人的规则一眼就能看出来是最大点独立集。现在,我们考虑,如果一条边 ( i , j ) (i,j) (i,j) 是合法的,要满足什么条件呢?思考后发现,当且仅当: i i i 和点 j j j 在一个合法的最大点独立集方案中没有被同时选中时,边 ( i , j ) (i,j) (i,j) 是合法的。证明可以感性理解一下。我们想:若一个点 i i i 在某一个最大点独立集的方案中没有被选中,那么点 i i i 就一定可以往其他 n − 1 n-1 n1 个点连一条边。而若一条边 ( i , j ) (i,j) (i,j) 的两端点 i , j i,j i,j 分别在某一个最大点独立集中没被选中,那么边 ( i , j ) (i,j) (i,j) 就会被计算两次,最后去重即可。

C o d e : \qquad Code: Code:

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

typedef long long LL;
const int maxn = 2e5 + 5e4 + 10;
int n;
struct pic {
	int to, lst;
}edge[maxn << 1];
int head[maxn], tot = 0;
int dp[maxn][2];//最大点独立集
bool flag[maxn][2];//判断第i个点在最大点独立集中被选中/不被选中是否可行
int rem[maxn][2];//记忆化
LL ans = 0;

inline int read() {
	int x = 0; char ch = getchar();
	while(!isdigit(ch)) ch = getchar();
	while(isdigit(ch)) x = (x << 1) + (x << 3) + (ch ^ 48), ch = getchar();
	return x;
}

inline void add(int x, int y) {
	edge[++ tot] = {y, head[x]};
	head[x] = tot;
}

void dfs(int x, int fa) {
	dp[x][1] ++;
	for(int i = head[x]; i; i = edge[i].lst) {
		int v = edge[i].to;
		if(v == fa) continue;
		dfs(v, x);
		dp[x][0] += max(dp[v][0], dp[v][1]);
		dp[x][1] += dp[v][0];
	}
}

void dfs2(int x, int fa, int opt) {
	if(rem[x][opt]) return ;//记忆化!!!!!!!!!!!!!!!!!!!!!!!!!
	rem[x][opt] = 1;
	flag[x][opt] = 1;
	for(int i = head[x]; i; i = edge[i].lst) {
		int v = edge[i].to;
		if(v == fa) continue;
		if(opt) {//只能从dp[v][0]转来
			dfs2(v, x, 0);
		}
		else {
			if(dp[v][0] == dp[v][1]) {//两状态均可,同时递归
				dfs2(v, x, 1);
				dfs2(v, x, 0);
			}
			else if(dp[v][0] > dp[v][1]) {//选择更优的
				dfs2(v, x, 0);
			}
			else {
				dfs2(v, x, 1);
			}
		}
	}
}

int main() {
	freopen("road.in", "r", stdin);
	freopen("road.out", "w", stdout);
	n = read();
	for(int i = 1, u, v; i < n; i ++) {
		u = read(), v = read();
		add(u, v), add(v, u);
	}
	dfs(1, 0);
	if(dp[1][0] == dp[1][1]) {//两状态均可,同时递归
		dfs2(1, 0, 1), dfs2(1, 0, 0);
	}
	else if(dp[1][0] > dp[1][1]) {//选择更优的递归
		dfs2(1, 0, 0);
	}
	else {
		dfs2(1, 0, 1);
	}
	int yy = 0, nn = 0;
	for(int i = 1; i <= n; i ++) {
		if(flag[i][0]) {
			ans += 1LL * (n - 1);//向剩下n-1个点都连一条边
			nn ++;
		}
		else yy ++;
	}
	ans -= 1LL * nn * (nn - 1) / 2LL;//去掉重复的
	printf("%lld\n", ans);
	return 0;
}

T3

T3

\qquad 首先,对于 Q ≤ 10 Q\leq 10 Q10 的部分分,直接暴力 d p dp dp 即可(建议加一个单调队列之类的求前缀最小值的优化),这样就拿到了 25 p t s 25pts 25pts。其次,对于只有 a , b , c , d , e a,b,c,d,e a,b,c,d,e 五个字符的部分分,我们可以用线段树储存区间左右端点填 i , j ∈ a ∼ e i,j\in a\sim e i,jae 时的最小值,区间合并复杂度为 5 4 5^4 54,总体复杂度为 O ( n log ⁡ 2 n × 5 4 ) O(n\log_{2}{n}\times5^4) O(nlog2n×54),综合上面的就可以拿到 55 p t s 55pts 55pts

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

const int INF = 0x3f3f3f3f;
const int maxn = 1e5 + 10;
int n, q;
char s[maxn];
struct Segment {
	int l, r, num[7][7];//num[i][j]:左端点填i,右端点填j,最小值
	#define l(x) tree[x].l
	#define r(x) tree[x].r
	#define num(x, i, j) tree[x].num[i][j]
}tree[maxn << 2];

namespace task1 {
	deque < int > qq;
	int dp[maxn][30];
	
	void sol() {
		for(int i = 0; i <= q; i ++) {
			char c; int x;
			if(i) scanf("%d\n%c", &x, &c);
			s[x] = c;
			int ans = n * 26 + 1;
			memset(dp, 0x7f, sizeof dp);
			dp[0][0] = 0;
			for(int j = 1; j <= n; j ++) {
				qq.clear();
				qq.push_back(0);
				int u = (int)(s[j] - 'a' + 1);
				for(int k = 1; k <= 26; k ++) {//加个单调队列小优化,保证过
					while(!qq.empty() && dp[j - 1][qq.back()] >= dp[j - 1][k]) qq.pop_back();
					qq.push_back(k);
					dp[j][k] = dp[j - 1][qq.front()] + abs(k - u);
					if(j == n) ans = min(ans, dp[j][k]);
				}
			}
			printf("%d\n", ans);
		}
	}
}

void update(int p) {//转移
	for(int i = 1; i <= 5; i ++)
		for(int j = i; j <= 5; j ++)
			num(p, i, j) = INF;
	for(int i = 1; i <= 5; i ++) {//left
		for(int j = i; j <= 5; j ++) {
			for(int k = j; k <= 5; k ++) {//right
				for(int l = k; l <= 5; l ++) {
					num(p, i, l) = min(num(p, i, l), num(p << 1, i, j) + num(p << 1 | 1, k, l));
				}
			}
		}
	}
}

void build(int p, int l, int r) {
	l(p) = l, r(p) = r;
	for(int i = 1; i <= 5; i ++)
		for(int j = 1; j <= 5; j ++)
			num(p, i, j) = INF;
	if(l == r) {
		for(int i = 1; i <= 5; i ++) num(p, i, i) = abs((int)(s[l] - 'a' + 1) - i);
		return ;
	}
	int mid = l + r >> 1;
	build(p << 1, l, mid), build(p << 1 | 1, mid + 1, r);
	update(p);
}

void modify(int p, int x, int val) {
	if(l(p) == r(p)) {
		for(int i = 1; i <= 5; i ++) num(p, i, i) = abs(val - i);
		return ;
	}
	int mid = l(p) + r(p) >> 1;
	if(x <= mid) modify(p << 1, x, val);
	else modify(p << 1 | 1, x, val);
	update(p);
}

int query(int p) {
	int res = INF;
	for(int i = 1; i <= 5; i ++)
		for(int j = i; j <= 5; j ++)
			res = min(res, num(p, i, j));
	return res;
}

int main() {
	scanf("%s", s + 1), n = strlen(s + 1);
	scanf("%d", &q);
	if(q <= 10) return task1::sol(), 0;
	build(1, 1, n);
	for(int i = 0; i <= q; i ++) {
		int x; char c;
		if(i) scanf("%d\n%c", &x, &c), modify(1, x, (int)(c - 'a' + 1));
		printf("%d\n", query(1));
	}
	return 0;
}

\qquad 感觉正解也是非常的玄学……

\qquad 在上面的 55 p t s 55pts 55pts 的线段树中,若直接把 5 5 5 改成 26 26 26,毫无疑问会超时。我们的瓶颈在于转移的复杂度高达 2 6 4 26^4 264,不能接受。我们设 b ( S , i ) b(S,i) b(S,i) 为一个由 S S S 变形而来的 01 01 01 串,其中第 j j j 项表示 S j S_j Sj 是否大于等于 i i i,是为 1 1 1,否则为 0 0 0。我们若在线段树中储存将 b ( S , i ) b(S,i) b(S,i) 变为合法方案的最小值,转移的复杂度只有 2 4 2^4 24,这正是我们定义 b ( S , i ) b(S,i) b(S,i) 的目的。假设 f ( S ) f(S) f(S) 表示将 S S S 变为合法串的最优解。下面,我们考虑证明: ∑ i = ′ a ′ ′ z ′ f ( b ( S , i ) ) = f ( S ) \sum_{i='a'}^{'z'}f(b(S,i))=f(S) i=azf(b(S,i))=f(S)

\qquad 根据套路(?),我们将证明分为两步:1、证明 ∑ i = ′ a ′ ′ z ′ f ( b ( S , i ) ) ≤ f ( S ) \sum_{i='a'}^{'z'}f(b(S,i))\leq f(S) i=azf(b(S,i))f(S);2、证明 ∑ i = ′ a ′ ′ z ′ f ( b ( S , i ) ) ≥ f ( S ) \sum_{i='a'}^{'z'}f(b(S,i))\geq f(S) i=azf(b(S,i))f(S)

\qquad 首先,我们考虑证明第一条。

证明第一条
\qquad 对于 S S S 的第 j j j 位,我们将要把它变成 S j ′ S'_j Sj,而这需要的代价正是 ∣ S j − S j ′ ∣ |S_j - S'_j| SjSj。不妨设 S j < S j ′ S_j<S'_j Sj<Sj。那么这 S j ′ − S j S'_j-S_j SjSj 的代价正好可以使得 b ( S , S j ) ∼ b ( S , S j ′ − 1 ) b(S,S_j)\sim b(S,S'_j-1) b(S,Sj)b(S,Sj1) 的第 j j j 位的 1 1 1 变成 0 0 0。因为这个区间内的 i i i 在修改前后是有变动的,需要一定的代价来使它们合法。而对于不处于这个区间内的 i i i 呢?它们一定是不受影响的。原来小于 S j S_j Sj 的,改完还是小于 S j S_j Sj,原来大于 S j ′ S'_j Sj 的,改完还是大于 S j ′ S'_j Sj,它们不会产生多余的代价,这使得我们的分配是绝对有用的,不会产生浪费。这可以说明, f ( S ) f(S) f(S) 的次数都可以分配到某些 f ( b ( S , i ) ) f(b(S,i)) f(b(S,i)) 中使得它们合法。所以 f ( S ) ≥ ∑ i = ′ a ′ ′ z ′ f ( b ( S , i ) ) f(S)\geq \sum_{i='a'}^{'z'}f(b(S,i)) f(S)i=azf(b(S,i))

\qquad 然后,我们来证明第二条。

\qquad 我们假设 Z i Z_i Zi b ( S , i ) b(S,i) b(S,i) 最优解中 0 0 0 的个数。当 Z i Z_i Zi 单调时,我们考虑构造:让 [ Z i , Z i + 1 ) [Z_i,Z_{i+1}) [Zi,Zi+1) 的位置填上 i i i(下标从 0 0 0 开始)。
证明第二条

\qquad 在上面这个例子中,第 j j j 位从 c c c 变为了 a a a,给 f ( S ) f(S) f(S) 带来的代价为 2 2 2。同时,我们观察: f ( b ( S , b ) ) , f ( b ( S , c ) ) f(b(S,b)),f(b(S,c)) f(b(S,b)),f(b(S,c)) 同样也改变了,而且代价也是 2 2 2。这真的是巧合么?显然不是。在这个构造方案下,对于第 j j j 位,花费显然是:所有 b ( S , i ) b(S,i) b(S,i) 的第 j j j 位的前缀 1 1 1 的长度与所有 f ( b ( S , i ) ) f(b(S,i)) f(b(S,i)) 的第 j j j 位的前缀 1 1 1 的长度差。而这刚好是 ∑ i = ′ a ′ ′ z ′ f ( b ( S , i ) ) \sum_{i='a'}^{'z'}f(b(S,i)) i=azf(b(S,i)) 的一部分。这证明,在 Z i Z_i Zi 单调的情况下, ∑ i = ′ a ′ ′ z ′ f ( b ( S , i ) ) \sum_{i='a'}^{'z'}f(b(S,i)) i=azf(b(S,i)) 的次数都可以通过上述构造方案分配给 f ( S ) f(S) f(S) 使得它合法,所以在这种情况下, ∑ i = ′ a ′ ′ z ′ f ( b ( S , i ) ) ≥ f ( S ) \sum_{i='a'}^{'z'}f(b(S,i))\geq f(S) i=azf(b(S,i))f(S)

\qquad 但是如果 Z i Z_i Zi 不单调怎么办呢?感性理解发现它其实永远都是单调的…… 假设 Z i > Z i + 1 Z_i>Z_{i+1} Zi>Zi+1,那么我们交换 f ( b ( S , i ) ) f(b(S,i)) f(b(S,i)) f ( b ( S , i + 1 ) ) f(b(S,i+1)) f(b(S,i+1)) 的方案显然不会让答案变劣。因为对于每个位置, b ( S , i ) b(S,i) b(S,i) b ( S , i + 1 ) b(S,i+1) b(S,i+1) 的对应位情况只能有 ( 1 , 0 ) , ( 0 , 0 ) , ( 1 , 1 ) (1,0),(0,0),(1,1) (1,0),(0,0),(1,1) 三种。因此如果出现 Z i > Z i + 1 Z_i>Z_{i+1} Zi>Zi+1,那么对应位就出现了 ( 0 , 1 ) (0,1) (0,1),让 ( 1 , 0 ) (1,0) (1,0) 变为 ( 0 , 1 ) (0,1) (0,1) 显然是需要 2 2 2 代价的。但是交换之后,由 ( 1 , 0 ) (1,0) (1,0) 变到 ( 1 , 0 ) (1,0) (1,0) 是不需要花费代价的,所以一定是可以交换的。那么通过交换,可以得到一个 Z i Z_i Zi 单调递增的方案。

\qquad 所以,最终我们证明: ∑ i = ′ a ′ ′ z ′ f ( b ( S , i ) ) = f ( S ) \sum_{i='a'}^{'z'}f(b(S,i))=f(S) i=azf(b(S,i))=f(S)。代码就是将询问离线后 26 26 26 个字母分别处理即可。

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

const int INF = 0x3f3f3f3f;
const int maxn = 1e5 + 10;
int n, m;
char s[maxn];
struct node {
	int x, ans;
	char c;
}q[maxn];
struct Segment {
	int l, r, num[2][2];
	#define l(x) tree[x].l
	#define r(x) tree[x].r
	#define num(x, i, j) tree[x].num[i][j]
}tree[maxn << 2];

void update(int p) {
	for(int i = 0; i < 2; i ++)
		for(int j = i; j < 2; j ++)
			num(p, i, j) = INF;
	for(int i = 0; i < 2; i ++)//left
		for(int j = i; j < 2; j ++)
			for(int k = j; k < 2; k ++)//right
				for(int l = k; l < 2; l ++)
					num(p, i, l) = min(num(p, i, l), num(p << 1, i, j) + num(p << 1 | 1, k, l));
}

void build(int p, int l, int r) {
	l(p) = l, r(p) = r;
	for(int i = 0; i < 2; i ++)
		for(int j = i; j < 2; j ++)
			num(p, i, j) = INF;
	if(l == r) return ;
	int mid = l + r >> 1;
	build(p << 1, l, mid), build(p << 1 | 1, mid + 1, r);
}

void modify(int p, int x, int val) {
	if(l(p) == r(p)) {
		for(int i = 0; i < 2; i ++) num(p, i, i) = abs(val - i);
		return ;
	}
	int mid = l(p) + r(p) >> 1;
	if(x <= mid) modify(p << 1, x, val);
	else modify(p << 1 | 1, x, val);
	update(p);
}

int query(int p) {
	int res = INF;
	for(int i = 0; i < 2; i ++)
		for(int j = i; j < 2; j ++)
			res = min(res, num(p, i, j));
	return res;
}

int main() {
	scanf("%s", s + 1), n = strlen(s + 1);
	scanf("%d", &m);
	for(int i = 1; i <= m; i ++) scanf("%d\n%c", &q[i].x, &q[i].c);//离线询问
	//证明 ∑(b(S,i)) = f(S)
	for(int i = 1; i <= 26; i ++) {//26个字母分开考虑,大于等于i的赋成1,否则赋成0
		build(1, 1, n);
		for(int j = 1; j <= n; j ++) modify(1, j, ((int)(s[j] - 'a' + 1) >= i ? 1 : 0));
		for(int j = 0; j <= m; j ++) {
			if(j) modify(1, q[j].x, ((int)(q[j].c - 'a' + 1) >= i ? 1 : 0));
			q[j].ans += query(1);
		}
	}
	for(int i = 0; i <= m; i ++) printf("%d\n", q[i].ans);
	return 0;
}

T4

T4

\qquad 这位更是重量级,不会……

多校联考 Day 2

T1

T1
\qquad 场上假贪心水了 30 p t s 30pts 30pts……

\qquad 本题正解是二分答案。场上没写是因为感觉二分答案 c h e c k check check 的过程就是贪心,二分答案过了那贪心也能过,所以没往那上面想。二分答案的 c h e c k check check 过程其实就相当于模拟。假设在负无穷时刻有 m i d mid mid 个分身正在准备。每次碰到一波牛就让当前休息好的分身上,然后更新他们的休息时间即可。

\qquad 核心代码:

bool check(LL x) {
	while(!q.empty()) q.pop();
	q.push({-2000000000000, x});//在负无穷时刻有x个分身准备好了
	for(int i = 1; i <= n; i ++) {
		LL Now = 1LL * a[i].ai, T = 1LL * a[i].ti;
		while(Now && !q.empty() && q.top().t + A <= T) {
			Peo Top = q.top(); q.pop();
			if(Top.people > Now) {//分身比需要的人多
				q.push({Top.t, Top.people - Now});//把剩下的放回去
				q.push({max(Top.t + A + C, T - B + C), Now});//有用的更新休息时间
				Now = 0;
			}
			else {
				Now -= Top.people;
				q.push({max(Top.t + A + C, T - B + C), Top.people});//全都得用
			}
		}
		if(Now) return 0;//分身不够,不合法
	}
	return 1;
}

T2

T2
\qquad 对于 n , m ≤ 5000 n,m\leq 5000 n,m5000,不难想到让每条边作为一个物品,价值为 v i × s i z e u × ( n − s i z e u ) v_i\times size_u \times (n-size_u) vi×sizeu×(nsizeu),花费为 s i z e u size_u sizeu,然后正常做 0 / 1 0/1 0/1 背包即可。

\qquad 对于正解,我们需要抓住树是随机生成这一重要性质。因为树随机生成,所以 ∑ s i z e u = n l o g 2 n \sum size_u=nlog_2n sizeu=nlog2n,所以最多只有 n l o g 2 n \sqrt {nlog_2n} nlog2n 种不同的 s i z e u size_u sizeu。每一种物品只有 5 5 5 种权值,所以最多只有 5 × n l o g 2 n 5\times\sqrt{nlog_2n} 5×nlog2n 种物品,跑二进制优化的多重背包即可。

C o d e : \qquad Code: Code:

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

typedef long long LL;
const int maxn = 1e5 + 10;
int n, m;
struct pic {
	int to, lst, val;
}edge[maxn << 1];
int cnt[maxn][10];
int head[maxn], tot = 0, sze[maxn];
LL dp[maxn], Val;

inline void add(int x, int y, int z) {
	edge[++ tot] = {y, head[x], z};
	head[x] = tot;
}

void dfs(int x, int fa, int val) {
	sze[x] ++;
	for(int i = head[x]; i; i = edge[i].lst) {
		int v = edge[i].to, w = edge[i].val;
		if(v == fa) continue;
		dfs(v, x, w);
		sze[x] += sze[v];
	}
	Val += 1LL * val * sze[x] * (n - sze[x]);
	if(fa) cnt[sze[x]][val] ++;//物品数量++
}

int main() {
	scanf("%d%d", &n, &m);
	for(int i = 1, x, y, z; i < n; i ++) {
		scanf("%d%d%d", &x, &y, &z);
		add(x, y, z), add(y, x, z);
	}
	dfs(1, 0, 0);
	for(int i = 1; i <= n; i ++) {
		for(int j = 1; j <= 5; j ++) {
			if(!cnt[i][j]) continue;
			int k = 1;
			while(k <= cnt[i][j]) {
				int v = i * k; LL w = 1LL * j * i * (n - i) * k;
				for(int l = m; l >= v; l --) dp[l] = max(dp[l], dp[l - v] + w);
				cnt[i][j] -= k, k *= 2;
			}
			if(cnt[i][j]) {
				int v = i * cnt[i][j]; LL w = 1LL * j * i * (n - i) * cnt[i][j];
				for(int l = m; l >= v; l --) dp[l] = max(dp[l], dp[l - v] + w);
				cnt[i][j] = 0;
			}
		}
	}
	printf("%lld\n", Val - dp[m]);
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值