把
S
S
S 划分为若干串,其中每一串满足其中的字符相同,即
S
=
s
1
s
2
…
s
r
S=s_1s_2\dots s_r
S=s1s2…sr。最朴素的想法是,在
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。
进行修改后,则需要更新
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[p−1]。
时间复杂度为
O
(
n
+
q
)
\mathcal O(n + q)
O(n+q)。
代码
#include<bits/stdc++.h>usingnamespace std;constint maxn =3e6+100;char s[maxn], ch;int q, p, ans;intmain(){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 ++;elseif(s[0]!= s[1]&& ch == s[1]) curi --;}elseif(p == len -1){if(s[len -1]== s[len -2]) curi ++;elseif(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;elseif(l != mid && mid != r && l != r &&(ch == l || ch == r)) curi --;elseif(l == mid && mid != r && ch != r) curi ++;elseif(l != mid && mid == r && ch != l) curi ++;elseif(l == r && mid != l && ch == l) curi -=2;}}
s[p]= ch;printf("%d\n",(curi +1)/2);}return0;}
思考
f
(
T
0
)
f(T_0)
f(T0) 不唯一的原因。首先,直径可能不唯一,从上文的
1
→
0
1\to0
1→0 的长度一定不小于
1
→
5
1\to5
1→5 的长度可推得。我们可以用
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) 不唯一。其次,树的直径上可能存在到两个端点距离相等的点,判断一下即可。
思考
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 成为了新的直径端点,故他们分别从“左堆”和“右堆”出来。故我们需要用双端队列分别模拟存储存储两边的那一堆点,即左堆和右堆,并将其中的点按大到小的方式排序。但是更新的时候不能逐个加值,我们需要维护加法懒标记。
思考
f
2
(
T
0
)
f^2(T_0)
f2(T0) 不唯一的原因。直径不唯一仍有可能,但直径上不可能存在到两边的距离相等的点。于是我们只需要判断第一种情况,即左(或右)边的一堆点达到
l
m
lm
lm 或
r
m
rm
rm 的点不唯一,且两个直径端点都挂着点。
易得,
f
n
(
T
0
)
,
n
≥
1
f^n(T_0),n\geq1
fn(T0),n≥1 的结构与
f
(
T
0
)
f(T_0)
f(T0) 的构造类似,故重复上述操作
3
3
3 与
4
4
4 即可。
时间复杂度为
O
(
n
log
n
+
k
)
\mathcal O(n\log n + k)
O(nlogn+k)。
代码
#include<bits/stdc++.h>usingnamespace std;constint maxn =1e6+100;constunsignedlonglong MOD =4294967296;int n, k;int head[maxn], cnt;structedge{int v, w, next;} e[maxn <<1];voidadd(int u,int v,int w){
e[++ cnt]=(edge){v, w, head[u]};
head[u]= cnt;}unsignedlonglong tree_d, dis[maxn], maxl;int last_point, start_point, f[maxn];voiddfs(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 <unsignedlonglong> qu[2];bool vis[maxn];unsignedlonglong q[maxn], qi[maxn], lm, rm, fm, tag[2];int p, pi;boolcmp(unsignedlonglong a,unsignedlonglong b){return a > b;}voiddfsi(int u,int fa,unsignedlonglong 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);}}intmain(){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;unsignedlonglong 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;}unsignedlonglong 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);return0;}
收获
知道了如何
O
(
n
)
\mathcal O(n)
O(n) 找树中任意一点到其他点的最远距离,并确定了终点一定是直径端点。