codeforces 1168(div.1)题解

vp的第一场div.1,好好写个题解。

A Increasing by Modulo

题意

n n n 个数 { a i } \{a_i\} {ai} , 每次可以选择一些数将他们变成 ( a i + 1 ) m o d    m (a_i+1)\mod m (ai+1)modm , 要求将数列变成单调不减,求最小的变化次数。

key

  • 二分答案

题解

二分次数,然后贪心,在满足不减的条件下使每个数尽量小。

代码

#include<bits/stdc++.h>
using namespace std;
const int N = 3e5 + 10;
int n, m, a[N], b[N];
 
bool check(int add)
{
	b[0] = 0;
	for (int i = 1; i <= n; ++ i){
		int low = a[i];
		int up = (a[i] + add) % m;
		if (up >= low){
			if (b[i-1] > up) return false;
			else if (b[i-1] >= low) b[i] = b[i-1];
			else b[i] = low;
		}
		else{
			if (b[i-1] <= up || b[i-1] >= low) b[i] = b[i-1];
			else b[i] = low;
		}
	}
	return true;
}
 
int solve()
{
	int l = 0, r = m-1, mid, ret;
	while (l <= r){
		mid = l + r >> 1;
		if (check(mid))
			ret = mid, r = mid-1;
		else
			l = mid+1;
	}
	return ret;
}
 
int main()
{
	ios::sync_with_stdio(false);
	cin >> n >> m;
	for (int i = 1; i <= n; ++ i)
		cin >> a[i];
	cout << solve() << endl;
	return 0;
}

B Good Triple

题意

一个01串 s s s ,求有多少对 ( l , r ) (l,r) (l,r) ,满足至少有一组 s x = s y = s z ( l ≤ x < y < z ≤ r ) s_x=s_y=s_z(l\le x<y<z\le r) sx=sy=sz(lx<y<zr) ,且 y − x = z − y y-x=z-y yx=zy

key

  • 结论
  • brute force

题解

可以发现一个长度大于等于 9 的01串必定有一组 ( x , y , z ) (x,y,z) (x,y,z) 满足要求,所以暴力就好了。

下面来发现一下:

随便挑3个连续的位置,不妨假设中间位置的数为1。假如这三个位置不能满足要求,那么可以分成两种情况:

  1. 010 010 010
  2. 110 110 110

对于第一种情况,假设这个长度为 3 的串在 3 到 5 的位置。假如要没有满足要求的三元组,发现 1 和 7 位置必须为0,那 ( 1 , 4 , 7 ) (1,4,7) (1,4,7) 就满足要求了。

1234567
01010

第二种情况同理,如下表, ( 1 , 5 , 9 ) (1,5,9) (1,5,9) 满足要求。

12345678910
1100110011

然后暴力找就好了。

不知道这种东西比赛的时候不知道结论该怎么做qwq

代码

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 3e5 + 100;
int n;
char s[N];
LL ans;
 
int main()
{
	scanf("%s", s + 1);
	n = strlen(s + 1);
	ans = 0;
	int minr = n+1;
	for (int i = n; i >= 1; -- i){
		for (int j = 1; i+j*2 <= n; ++ j)
			if (s[i] == s[i+j] && s[i+j] == s[i+j*2]){
				minr = min(minr, i+j*2);
				break;
			}
		if (minr != -1) ans += n-minr+1;
	}
	printf("%I64d\n", ans);
	return 0;
}

C And Reachability

题意

有一个长度为 n n n 的数组 { a i } \{a_i\} {ai} , 假如两个数按位和起来大于 0,那么前面那个数就可以“到达”后面那个数。给出多组询问,求 x x x 是否可以直接或者间接“到达” y y y

key

  • DP

题解

考虑对于每个数 a i a_i ai 找他后面第一个可达的且在 j j j 位上是 1 的数记为 d p i , j dp_{i,j} dpi,j

倒着扫一遍 O ( n log ⁡ 2 n ) O(n\log^2n) O(nlog2n) 得到 d p dp dp 数组,单次询问 O ( log ⁡ n ) O(\log n) O(logn) ,对于 y y y 的每一个1位 j j j 判断 x x x d p x , j dp_{x,j} dpx,j 是否小于等于 y y y

p.s. 300iq 貌似挺喜欢中文的。。。

代码

#include<bits/stdc++.h>
using namespace std;
const int N = 3e5 + 10, E = 20;
int n, m, a[N], nxt[E], dp[N][E];

int main()
{
	ios::sync_with_stdio(false);
	cin >> n >> m;
	for (int i = 1; i <= n; ++ i)
		cin >> a[i];
	memset(dp, 0x3f, sizeof(dp));
	for (int i = 0; i < E; ++ i)
		nxt[i] = n+1, dp[n+1][i] = n+1;
	for (int i = n; i >= 1; -- i)
		for (int j = 0; j < E; ++ j)
			if ((a[i]>>j)&1){
				for (int k = 0; k < E; ++ k)
					dp[i][k] = min(dp[i][k], dp[nxt[j]][k]);
				dp[i][j] = i;
				nxt[j] = i;
			}
	for (int i = 1; i <= m; ++ i){
		int x, y, z = n+1;
		cin >> x >> y;
		for (int j = 0; j < E; ++ j)
			if ((a[y]>>j)&1)
				z = min(z, dp[x][j]);
		puts(z <= y ? "Shi" : "Fou");
	}
	return 0;
}

D Anagram Paths

题意

有一棵二叉树,每条边上有一个小写字母或者??里可以填上任意字母。

每条根到一个叶子的路径组成一个字符串。假如有一种方法,将所有的?填上字母,使得根到每个叶子组成的所有字符串,每个串中每个字母的个数都相同,那么这就是一棵 anagrammable 的树。

对于一棵 anagrammable 的树,他的权值定义为 ∑ i n d ( c ) ∗ f ( c ) \sum ind(c)*f(c) ind(c)f(c), 其中 i n d ( ′ a ′ ) = 1 , i n d ( ′ b ′ ) = 2 , . . . , i n d ( ′ z ′ ) = 26 ind('a')=1,ind('b')=2,...,ind('z')=26 ind(a)=1,ind(b)=2,...,ind(z)=26 f ( c ) f(c) f(c) 表示在所有可行的填字母方案中,在一条路径组成的字符串中 c c c 字符的最大出现次数。

现在有多个询问,每次修改一条边的字母。对于每次修改,求树是否 anagrammable ,若是,求出他的权值。

key

  • 英语阅读
  • 结论
  • DP

题解

假如这棵树要 anagrammable ,那么他的所有叶子深度必须相等。

假如这棵树要 anagrammable ,那么他的每一棵子树都必须满足 ∑ m a x ( c ) ≤ d e p \sum max(c) \le dep max(c)dep,其中 m a x ( c ) max(c) max(c) 表示 c c c 字符在一条到叶子的路径上出现的最大次数(还没填字母,?不算), d e p dep dep 是子树深度。

注意是每棵子树都必须满足,而不仅仅是整棵树。

DDP?并不。

发现一些结论。假如在某个节点只有一个儿子,那么这个点的儿子满足要求,他必然满足要求,他的儿子不满足要求,那他也一定不满足要求。所以可以将他和他的儿子合并为同一个点。合并之后树高是 O ( n ) O(\sqrt{n}) O(n ) 的。为什么不是 O ( log ⁡ n ) O(\log n) O(logn) ,因为显然合并之后所有叶子深度不一定相等。至于为什么是 O ( n ) O(\sqrt{n}) O(n ) ,可以这样证明。我们假设只有在同一深度的所有点都只有一个儿子时才将这一层的点和他们的儿子合并。因为原树的每一层的节点数都大于等于上一层,两层的节点个数相同时,他们合并掉了,所以最后留下的每层的节点数单调递增。那么大概的 1 + 2 + . . . + n = n 1+2+...+\sqrt{n}=n 1+2+...+n =n,深度就是 O ( n ) O(\sqrt{n}) O(n )

然后每次直接暴力修改到根所有点的DP值,因为是二叉树,所以更新一个点的 DP 值是常数级别的,最终复杂度 O ( n ⋅ 26 + q n ) O(n\cdot26+q\sqrt{n}) O(n26+qn ) ,分别是建树和修改。

注意一下实现。可以先建出原树,将边的信息存在深度较深的节点上。然后一遍dfs建出新树,顺便把该合并的节点的信息合并到儿子上。最后修改只要跑新树就好了。

我重构了一遍代码才过,只能说代码能力太差了。

p.s. 我的线段树垃圾做法成功被第 4 个点 hack 掉了。不过讨论里居然有大佬也是这么做的,然后被出题人爆D"impossible to extend"

代码

#include<bits/stdc++.h>
using namespace std;
const int N = 2e5 + 10, S = 27;
int n, m, w[N][S], dgr[N], faw[N], dep[N], mdep, rel[N], fa[N], dp[N][S], tot[N], failcnt;
int h1[N], ecnt1, nxt1[N], v1[N];
int h[N], ecnt, nxt[N], v[N];

void add1(int _u, int _v)
{
	nxt1[++ecnt1] = h1[_u];
	v1[ecnt1] = _v;
	h1[_u] = ecnt1;
}

void add(int _u, int _v)
{
	nxt[++ecnt] = h[_u];
	v[ecnt] = _v;
	h[_u] = ecnt;
}

int dfs(int u)
{
	if (dgr[u] == 0){
		if (mdep == 0) mdep = dep[u];
		else if (mdep != dep[u]){
			for (int i = 1; i <= m; ++ i) puts("Fou");
			exit(0);
		}
		rel[u] = u;
		return u;
	}
	int rt;
	for (int i = h1[u]; i; i = nxt1[i]){
		dep[v1[i]] = dep[u]+1;
		rt = dfs(v1[i]);
		if (v1[i] != rt)
			for (int j = 0; j < S; ++ j)
				w[rt][j] += w[v1[i]][j];
		for (int j = 1; j < S; ++ j)
			dp[u][j] = max(dp[u][j], dp[rt][j]+w[rt][j]);
		if (dgr[u] == 1 && u != 1) rel[u] = rt;
		if (dgr[u] == 2 || u == 1){add(u, rt); fa[rt] = u;}
	}
	for (int i = 1; i < S; ++ i)
		tot[u] += dp[u][i];
	failcnt += tot[u] > mdep-dep[u];
	if (dgr[u] == 2 || u == 1) rel[u] = u;
	return (dgr[u] == 1 ? rt : u);
}

void modify(int u, int s)
{
	while (u){
		failcnt -= tot[u] > mdep-dep[u];
		tot[u] -= dp[u][s];
		dp[u][s] = 0;
		for (int i = h[u]; i; i = nxt[i])
			dp[u][s] = max(dp[u][s], dp[v[i]][s]+w[v[i]][s]);
		tot[u] += dp[u][s];
		failcnt += tot[u] > mdep-dep[u];
		u = fa[u];
	}
}

void get_ans()
{
	if (failcnt){puts("Fou"); return;}
	int cnt = 0, ans = 0;
	for (int i = 1; i < S; ++ i)
		cnt += dp[1][i];
	for (int i = 1; i < S; ++ i)
		ans += i*(mdep-cnt+dp[1][i]);
	printf("Shi %d\n", ans);
}

int main()
{
	scanf("%d%d", &n, &m);
	for (int i = 2; i <= n; ++ i){
		int x; char c;
		scanf("%d", &x);
		c = getchar();
		while (c != '?' && (c < 'a' || c > 'z')) c = getchar();
		add1(x, i);
		faw[i] = c == '?' ? 0 : c-'a'+1;
		w[i][faw[i]]++;
		dgr[x]++;
	}
	dfs(1);
	for (int i = 1; i <= m; ++ i){
		int x; char c;
		scanf("%d", &x);
		c = getchar();
		while (c != '?' && (c < 'a' || c > 'z')) c = getchar();
		w[rel[x]][faw[x]]--;
		if (faw[x]) modify(fa[rel[x]], faw[x]);
		faw[x] = c == '?' ? 0 : c-'a'+1;
		w[rel[x]][faw[x]]++;
		if (faw[x]) modify(fa[rel[x]], faw[x]);
		get_ans();
	}
	return 0;
}

E XorPermutations

结论题qwq

看官方题解吧qwq

这谁顶得住呀qwq

总结

看似题题暴力,但是实际上思维含量很高的一套题。暴力也是要结论撑腰的qwq。况且暴力也不是谁都能写的出来的,比如我就写不出来。

div.1 只会 A 题, B 题就卡住了,实在是实力不足。

补题基本补完了。E题算了,等我能力够了再做不迟。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值