网易 秋招 2023.10.15 编程题目与题解

1、小红的字符串变换(easy)

小红有一个字符串\(s\),她想把\(s\)变成\(t\)

小红可以进行操作:选择两种不同的字母,然后将在\(s\)中的这两种字母按任意顺序排列。

小红想知道她是否可以恰好操作一次将\(s\)变成\(t\)

输入

第一行输入一个字符串\(s\)

第二行输入一个字符串\(t\)

\(s\)\(t\)长度相等,且都不超过\(10^6\)

输出

若小红可以恰好操作一次将\(s\)变成\(t\),则输出"YES",再输出选择的两种字母,否则输出"NO"

如果有多种方案,请输出字典序最小的方案(第一种字母 ASCII 码值尽可能小,若第一种字母 ASCII 码值相同,则第二种字母 ASCII 码值尽可能小)。

样例

1
2
3
4
5
6
7
8
9
10
11
输入:
abab
abba

输出:
YES
a b

提示:
选择字母 'a' 、 'b' ,即可使 "abab" 变成 "abba"
选择字母 'b' 、 'a' 也是答案,但此方案的字典序较大。

解答

对于任意一个下标\(i\),如果\(s_i\neq t_i\),那么我们只能选择这一对字符\((s_i,t_i)\)进行操作,如果只使用这一对字符进行操作仍然无法完成\(s=t\)的目的,那么说明肯定还存在另一对和\((s_i,t_i)\)不相同的字符串需要操作,这时肯定是无解的。如果能够达到\(s=t\)的目的,那么\((s_i,t_i)\)就是唯一解。

如果有多个解,那么唯有\(s=t\)才可以做到。此时最小字典序的解为\((\texttt{a,b})\)

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
def solve(s, t):
n = len(s)
v = []
for i in range(n):
if s[i] != t[i]:
v = sorted([s[i], t[i]])
break
if not v:
return ['a', 'b']
s0 = set(i for i in range(n) if s[i] == v[0])
s1 = set(i for i in range(n) if s[i] == v[1])
t0 = set(i for i in range(n) if t[i] == v[0])
t1 = set(i for i in range(n) if t[i] == v[1])
if len(s0) == len(t0) and len(s1) == len(t1) and (s0 | s1) == (t0 | t1):
return v
else:
return []


ans = solve(input(), input())
if ans:
print("YES\n{} {}".format(ans[0], ans[1]))
else:
print("NO")

2、小红的树上染色

小红有一棵\(n\)个点的树,\(1\)号点是根结点。树上的一些边需要被染色,小红每次可以选择一个点,将这个点到根结点的所有边染成红色。小红想知道,最少需要多少次操作,才能使得树上所有的边都被染成红色。

必须把所有需要染红的边染红,可以把一条边染色多次,也可以把不需要染色的边染成红色。

输入

第一行一个整数\(n\),表示树上点的个数。

接下来\(n-1\)行,每行三个整数\(u,v,c\),表示\(u\)\(v\)之间有一条边,如果\(c=1\),表示这条边需要被染色,否则表示这条边可以不被染色。

  • \(2 \leq n \leq 10^5\)
  • \(1 \leq u, v \leq n\)

输出

输出一个整数,表示最少需要的操作次数。

样例

1
2
3
4
5
6
7
8
9
10
11
12
输入:
5
1 2 1
1 3 0
2 4 1
2 5 0

输出:
1

提示:
选择 4 号结点,把 (1, 2),(2, 4) 染红。

解答

基于贪心的思想,我们应该尽量选择深度足够深的节点进行染色。为了使得染色节点尽可能少,怎么做出这个选择呢?

如果一个节点\(u\)连向它的父节点这条边必须被染色,但是\(u\)的子树中,没有需要被染色的边,那么我们可以选择\(u\),这样子将使得\(u\)到根中的所有边都会被进行染色。此外,选择\(u\)的后代是没有必要的,因为选择它们的效果和选择\(u\)相同。

由此我们只需要统计选择必须被选择的节点\(u\)即可。

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# include <bits/stdc++.h>
# define pi pair<int,int>
using namespace std;
typedef long long ll;

const int N=100005;

vector<pi>g[N];
int n;
int ans=0;
int dfs(int u,int fa){
int cnt=0;
for(auto [v,w]:g[u]){
if(v==fa) continue;
int c=dfs(v,u);
if(w&&c==0) ++ans;
cnt+=w+c;
}
return cnt;
}
int main(){
int u,v,w;
scanf("%d",&n);
for(int i=1;i<n;i++){
scanf("%d %d %d",&u,&v,&w);
g[u].push_back(pi(v,w));
g[v].push_back(pi(u,w));
}
dfs(1,0);
printf("%d\n",ans);
}

3、小红的mex查询

小红拿到了一个空集。她准备进行以下操作:将\([l,r]\)区间的每个整数添加进集合。

请你在每次操作后,输出当前集合的\(\text{mex}\)。我们定义,集合的\(\text{mex}\)为:集合中最小的未出现的非负整数。

输入

第一行输入一个正整数\(q\),代表操作次数。 接下来的\(q\)行,每行输入两个正整数\(l,r\),代表一次操作。

  • \(1\leq q \leq 10^5\)
  • \(1\leq l \leq r \leq 10^9\)

输出

输出\(q\)行,每行输入一个整数,代表当前的集合\(\text{mex}\)

样例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
输入:
4
1 3
7 8
0 5
3 6

输出:
0
0
6
9

提示:
第一次操作后,集合为[1,2,3],mex 为 0。
第二次操作后,集合为[1,2,3,7,8],mex 仍然是 0。
第三次操作后,集合为[0,1,2,3,4,5,7,8],mex 为 6。
第四次操作后,集合为[0,1,2,3,4,5,6,7,8],mex 为 9。

解答

令第\(i\)次操作表示为区间\([l_i,r_i]\)。按照\(\text{mex}\)的覆盖性质可以发现,一个候选的\(\text{mex}\)值必定为集合\(U=\{r_i+1\mid i\in[1,n]\}\cup\{0\}\),因为对于一个数\(k\),如果不存在\(i\)使得\(r_i+1=k\),那么有两种情况:

  • \(k\)不被任何一个区间所覆盖,但是\(k-1\)也将不会被任何一个区间所覆盖。因此\(k\)必定不是一个\(\text{mex}\)值。
  • \(k\)被任何一个区间所覆盖,因此\(k\)必定不是一个\(\text{mex}\)值。

因此,每对一个区间\([l_i,r_i]\)进行操作后,将原本集合\(U\)中区间\([l_i,r_i]\)内的所有元素删除即可。最终输出\(S\)中的最小值即可。

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# include <bits/stdc++.h>
using namespace std;

const int N=100004;

int l[N],r[N],n;
int main(){
set<int>st{0};
scanf("%d",&n);
for(int i=1;i<=n;i++){
scanf("%d %d",&l[i],&r[i]);++r[i];
st.insert(r[i]);
}
for(int i=1;i<=n;i++){
while(true){
auto it=st.lower_bound(l[i]);
if(it==st.end()||*it>=r[i]) break;
st.erase(it);
}
printf("%d\n",*st.begin());
}
}

4、小红的字符串回文值

小红有一个字符串\(s\),她想知道这个字符串中字典序最大的后缀、字典序最小的后缀的回文值分别是多少。

字符串的回文值定义为:字符串的最长回文子串长度。

输入

第一行输入一个字符串,字符串长度不超过\(2 \times 10^5\)

输出

输出两个整数表示答案。

样例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
输入:
abc

输出:
1 1

提示:
字典序最大的后缀为"c",回文值为1
字典序最小的后缀为"abc",回文值为1

输入:
ababa

输出:
3 1

提示:
字典序最大的后缀为"baba",回文值为3(最大回文子串为 "bab" 或 "aba" )
字典序最小的后缀为"a",回文值为1

解答

本题将分为两个部分进行求解。

第一部分是找到两个字典序最大和最小的后缀。这题可以直接使用后缀数组进行解决,不过这不在考察范围内。对于一对后缀\((i,j)\),我们可以使用字符串哈希,通过二分找到最小的正整数\(l\)使得\(s_{i+l-1}\neq s_{j+l-1}\),并比较\(s_{i+l-1}\)\(s_{j+l-1}\)的顺序,从而比较出这两个后缀的字典序大小。如果不存在\(l\),那么说明\(i,j\)中其中一个是另一个的前缀,因此长度较小的那个后缀字典序比较小。

最终通过哈希算法求解字典序最大和最小的后缀。

第二部分是找到一个字符串的最长回文字符串长度,这个问题可以使用Manachar算法直接解决。也可以使用二分法和哈希法完成,具体做法是预处理出这个字符串的前缀哈希值和后缀哈希值,使用中心扩展法进行二分即可。

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
# include <bits/stdc++.h>
using namespace std;
typedef unsigned long long ull;

const int N=200004;
char s[N];
int n;

ull pw[N],hs[N],B=131;
ull geths(int l,int r){
return hs[r]-hs[l-1]*pw[r-l+1];
}
bool lt(int x,int y){
int m=n-y+1;
int l=1,r=m+1;
while(l<r){
int mid=(l+r)>>1;
if(geths(x,x+mid-1)!=geths(y,y+mid-1)) r=mid;
else l=mid+1;
}
if(l==m+1) return 0;
else return s[x+l-1]<s[y+l-1];
}
int solve(string t){
string s="#";
for(char ch:t){
s+=ch;s+='#';
}
int n=s.size();
vector<int>d(n);
for(int i=0,l=0,r=-1;i<n;i++){
int k=(i>r?1:min(d[l+r-i],r-i+1));
for(;0<=i-k&&i+k<n&&s[i-k]==s[i+k];k++);
d[i]=k--;
if(i+k>r){
l=i-k;
r=i+k;
}
}
int k=*max_element(d.begin(),d.end());
return k-1;
}
int main(){
scanf("%s",s+1);
n=strlen(s+1);
pw[0]=1;
for(int i=1;i<=n;i++){
pw[i]=pw[i-1]*B;
hs[i]=hs[i-1]*B+s[i];
}
int mx=1,mn=1;
for(int i=2;i<=n;i++){
if(!lt(mn,i)) mn=i;
if(lt(mx,i)) mx=i;
}
printf("%d %d\n",solve(string(s+mx,s+n+1)),solve(string(s+mn,s+n+1)));

}



如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
Ujimatsu Chiya 微信 微信
Ujimatsu Chiya 支付宝 支付宝