JRKSJ 的比赛 Round 7 Div.2

P8932 [JRKSJ R7] Clock Paradox

思路

  1. S S S 划分为若干串,其中每一串满足其中的字符相同,即 S = s 1 s 2 … s r S=s_1s_2\dots s_r S=s1s2sr。最朴素的想法是,在 s i s_i si 后面插入 s i s_i si,显然不优。但是发现可以一次性在 s i s_i si s i + 1 s_{i+1} si+1 之间一次性插入 s i s i + 1 s_is_{i+1} sisi+1。容易证明,一次性插入两个以上是不可行的(可以手写一下)。故最后的答案为 f ( S ) = r + 1 2 f(S)=\frac{r+1}{2} f(S)=2r+1
  2. 进行修改后,则需要更新 r r r。先不考虑边界的特殊情况,考虑一般情形。易得,只用考虑以修改位置为中心以及向左向右各一个单位这三个字符。他们的排列情况只有 5 5 5 种,分别是 ccc,cca,acc,cac,abc,其中 a,b,c 是不同的字符。再考虑边界。若修改位置在字符串首或未,则只用考虑 ca,cc 两种情况。分类讨论修改字符 c h ch ch 是什么就可以了,具体的讨论方法是: c h ch ch 与 a (或 b 或c)相等,或全不相等时, r r r 的改变情况。故最后的答案 f ( S ) = r 1 + 1 2 f(S)=\frac{r_1 +1}{2} f(S)=2r1+1。不要忘记更新 s [ p − 1 ] s[p-1] s[p1]
  3. 时间复杂度为 O ( n + q ) \mathcal O(n + q) O(n+q)

代码

#include <bits/stdc++.h>
using namespace std;
const int maxn = 3e6 + 100;
char s[maxn], ch;
int q, p, ans;
int main ()
{
	scanf ("%d", &q);
	cin >> s;
	int len = strlen (s);
	char cur = s[0]; ans = 1;
	for (int i = 1; i < len; i++) 
	{
		if (cur == s[i]) continue;
		else ans ++, cur = s[i];
	}
	printf ("%d\n", (ans + 1) / 2);
	int curi = ans;
	while (q --) 
	{
		scanf ("%d %c", &p, &ch), p--;
		if (ch != s[p]) 
		{
			if (p == 0) 
			{
				if (s[0] == s[1]) curi ++;
				else if (s[0] != s[1] && ch == s[1]) curi --;
			} 
			else if (p == len - 1) 
			{
				if (s[len - 1] == s[len - 2]) curi ++;
				else if (s[len - 1] != s[len - 2] && ch == s[len - 2]) curi --;
			}
			else 
			{
				char l = s[p - 1], r = s[p + 1], mid = s[p];
				if (l == r && r == mid) curi += 2;
				else if (l != mid && mid != r && l != r && (ch == l || ch == r)) curi --;
				else if (l == mid && mid != r && ch != r) curi ++;
				else if (l != mid && mid == r && ch != l) curi ++;
				else if (l == r && mid != l && ch == l) curi -= 2; 
			}
		}
		s[p] = ch;
		printf ("%d\n", (curi + 1) / 2);
	}
	return 0;
}

收获

  1. 仔细思考,读清题意 /doge。

P8932 [JRKSJ R7] TSM eerT

思路

  1. 树的直径一定是 f ( T 0 ) f(T_0) f(T0) 上的权值最大边,思考 f ( T 0 ) f(T_0) f(T0) 上的其他边的权值与这条直径的关系。构建一个简单的树(其中 0 → 1 → 2 → 3 → 4 0\to 1\to2\to3\to4 01234 是直径)。
    在这里插入图片描述
    我们求点 7 7 7 到其他点的最远距离。首先,类似于 7 → 6 → 1 → 5 7\to6\to1\to5 7615 的路径的长度一定不大于 7 → 6 → 1 → 0 7\to6\to1\to0 7610 这条路径的长度,因为 1 → 0 1\to0 10 的长度一定不小于 1 → 5 1\to5 15 的长度。同理可得,类似于 7 → 6 → 1 → 2 → 8 → 9 7\to6\to1\to2\to8\to9 761289 的路径的长度一定不大于 7 → 6 → 1 → 2 → 3 → 4 7\to6\to1\to2\to3\to4 761234 这条路径的长度。由于点 7 7 7 的任意性,可知以任意一点(除直径端点外)为起点,最远路径的终点一定是两个直径端点中的某一个,路径长度等于其到直径上的距离加上,直径上的那点到两端点距离的较大值。于是我们可以得到 f ( T 0 ) f(T_0) f(T0) 的结构了:直径的端点分别挂着一堆非端点,类似于下图的结构( 1 1 1 2 2 2 为原直径端点,其他点为非端点)。
    在这里插入图片描述
  2. 思考 f ( T 0 ) f(T_0) f(T0) 不唯一的原因。首先,直径可能不唯一,从上文的 1 → 0 1\to0 10 的长度一定不小于 1 → 5 1\to5 15 的长度可推得。我们可以用 m a x l maxl maxl 记录直径上的某一点向外延伸的最大距离,若 m a x l maxl maxl 等于这一点到两端点距离中的任意一个,则令 f l a g flag flag 1 1 1。但是直径不唯一不一定使得 f ( T 0 ) f(T_0) f(T0) 不唯一:当存在一个直径端点下没有挂点时,直径不唯一并不会导致 f ( T 0 ) f(T_0) f(T0) 不唯一。也就是说, f l a g = 1 flag=1 flag=1 且两个端点下面都挂着点的时候, f ( T 0 ) f(T_0) f(T0) 不唯一。其次,树的直径上可能存在到两个端点距离相等的点,判断一下即可。
  3. 思考 f 2 ( T 0 ) f^{2}(T_0) f2(T0) 的构造,以如下图作为 f ( T 0 ) f(T_0) f(T0)
    在这里插入图片描述
    1 1 1 与点 2 2 2 之间的距离即原直径记为 f m fm fm。假设点 3 3 3 是左边挂着的三个点中到点 2 2 2 的边权最大(记此边权为 l m lm lm)的点,点 7 7 7 是右边挂着的两个点中到点 1 1 1 的边权最大(记此边权为 r m rm rm)的点。于是左边挂着的点(除点 3 3 3 外),其最远距离更新为边权加 f m fm fm r m rm rm,其中的最大值是 f 2 ( T 0 ) f^2(T_0) f2(T0) 中的 l m lm lm;右边挂着的点(除点 7 7 7 外),其最远距离更新为边权加 f m fm fm l m lm lm,其中的最大值是 f 2 ( T 0 ) f^2(T_0) f2(T0) 中的 r m rm rm。点 3 3 3 到点 7 7 7 的距离更新为 f m + r m + l m fm+rm+lm fm+rm+lm,是 f 2 ( T 0 ) f^2(T_0) f2(T0) 中的 f m fm fm。点 2 2 2 到其他点的最远距离更新为 f m + r m fm+rm fm+rm,点 1 1 1 到其他点的最远距离更新为 f m + l m fm+lm fm+lm,分别进入“左堆”和“右堆”,各自作为其中的最小值。这时,点 3 3 3 与点 7 7 7 成为了新的直径端点,故他们分别从“左堆”和“右堆”出来。故我们需要用双端队列分别模拟存储存储两边的那一堆点,即左堆和右堆,并将其中的点按大到小的方式排序。但是更新的时候不能逐个加值,我们需要维护加法懒标记
  4. 思考 f 2 ( T 0 ) f^2(T_0) f2(T0) 不唯一的原因。直径不唯一仍有可能,但直径上不可能存在到两边的距离相等的点。于是我们只需要判断第一种情况,即左(或右)边的一堆点达到 l m lm lm r m rm rm 的点不唯一,且两个直径端点都挂着点。
  5. 易得, f n ( T 0 ) , n ≥ 1 f^n(T_0),n\geq1 fn(T0),n1 的结构与 f ( T 0 ) f(T_0) f(T0) 的构造类似,故重复上述操作 3 3 3 4 4 4 即可。
  6. 时间复杂度为 O ( n log ⁡ n + k ) \mathcal O(n\log n + k) O(nlogn+k)

代码

#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e6 + 100;
const unsigned long long MOD = 4294967296;
int n, k;
int head[maxn], cnt;
struct edge 
{
	int v, w, next;
} e[maxn << 1];
void add (int u, int v, int w) 
{
	e[++ cnt] = (edge) {v, w, head[u]};
	head[u] = cnt;
}
unsigned long long tree_d, dis[maxn], maxl;
int last_point, start_point, f[maxn]; 
void dfs (int u, int fa, int tp) 
{
	if (tp == 1) f[u] = fa;
	if (dis[u] > tree_d) tree_d = dis[u], last_point = u;
	for (int i = head[u]; i; i = e[i].next) 
	{
		int v = e[i].v, w = e[i].w;
		if (v == fa) continue;
		dis[v] = (dis[u] + 1ll * w); 
		dfs (v, u, tp);
	}
}
deque <unsigned long long> qu[2];
bool vis[maxn];
unsigned long long q[maxn], qi[maxn], lm, rm, fm, tag[2];
int p, pi;
bool cmp (unsigned long long a, unsigned long long b) {return a > b;} 
void dfsi (int u, int fa, unsigned long long d, int tp) 
{
	if (tp == 1) q[++ p] = d;
	else qi[++ pi] = d;
	if (maxl < d) maxl = d;
	for (int i = head[u]; i; i = e[i].next) 
	{
		int v = e[i].v, w = e[i].w;
		if (vis[v] || v == fa) continue;
		dfsi (v, u, (d + 1ll * w), tp);
	}
} 
int main ()
{
	scanf ("%d %d", &n, &k);
	for (int i = 2; i <= n; i++) 
	{
		int v, w;
		scanf ("%d %d", &v, &w); v = i - v;
		add (i, v, w), add (v, i, w);
	}
	dfs (1, 0, 0);
	start_point = last_point;
	memset (dis, 0, sizeof (dis));
	tree_d = last_point = 0;
	dfs (start_point, 0, 1);
	int cur_point = last_point, flagi = 0;
	while (cur_point != 0) 
		vis[cur_point] = 1, cur_point = f[cur_point]; 
	fm = tree_d;
	cur_point = f[last_point];
	while (cur_point != start_point) 
	{
		int flag = dis[cur_point] > tree_d - dis[cur_point] ? 1 : 0;
		unsigned long long cur_d = max (dis[cur_point], tree_d - dis[cur_point]);
		if (dis[cur_point] == tree_d - dis[cur_point]) printf ("-1"), exit (0);
		maxl = 0, dfsi (cur_point, f[cur_point], cur_d, flag);
		if (maxl == dis[cur_point] + cur_d || maxl == tree_d - dis[cur_point] + cur_d)
			flagi = 1;
		cur_point = f[cur_point];
	}
	if (flagi && p != 0 && pi != 0) printf ("-1"), exit (0);
	sort (q + 1, q + p + 1, cmp), sort (qi + 1, qi + pi + 1, cmp);
	for (int i = 1; i <= p; i++) qu[0].push_back (q[i]);
	for (int i = 1; i <= pi; i++) qu[1].push_back (qi[i]);
	bool pd = qu[0].empty (), pdi = qu[1].empty ();
	for (int i = 1; i < k; i++) 
	{
		lm = pd ? 0 : tag[0] + qu[0].front (), 
		rm = pdi ? 0 : tag[1] + qu[1].front ();
		if (!pd) qu[0].pop_front (), qu[0].push_back (-tag[0]);
		if (!pdi) qu[1].pop_front (), qu[1].push_back (-tag[1]);
		if (p != 0 && pi != 0 && (lm == qu[0].front () + tag[0] || rm == qu[1].front () + tag[1]))
			printf ("-1"), exit (0);
		tag[0] += rm + fm, tag[1] += lm + fm, fm += lm + rm;
	}
	unsigned long long ans = 0ll;
	for (int i = 0; i <= 1; i++) 
		while (!qu[i].empty ()) 
			ans += qu[i].front () + tag[i], qu[i].pop_front ();
	printf ("%u", (ans + fm) % MOD); 
	return 0;
}


收获

  1. 知道了如何 O ( n ) \mathcal O(n) O(n) 找树中任意一点到其他点的最远距离,并确定了终点一定是直径端点
  2. 知道了 f ( T 0 ) f(T_0) f(T0) 直径不唯一中的特殊情况。
  3. 见识到了加法懒标记 t a g tag tag双端队列 deque 的使用。
  4. 知道了啥叫无向完全图
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值