「解题报告」2023-10-17 模拟赛
Prufer 序列(prufer)
题目描述:
Pigbrain 不知道什么时候学习了 \(\texttt{prufer}\) 序列。
\(\texttt{prufer}\) 序列可以用来表示一棵树,其构造方法是这样的:
对于给定的树,假设节点编号为 \(1 \dots n\),那么进行这样的操作:
-
找到编号最小的度数为 \(1\) 的点。
-
删除该节点,并在序列末尾添加与该节点相邻的点的编号。
-
重复 \(1、2\) 操作,直到树中只剩下两个节点。
容易知道对于一棵 \(n\) 个点的树,这样得到的序列的长度为 \(n−2\)。
输入描述:
第 \(1\) 行一个整数 \(𝑛\),表示树中节点的个数。
第 \(2 \dots 𝑛\) 行,每行两个整数 \(u, v\),表示树上的一条边。保
证给定的是一棵树。
输出描述:
一行 \(n−2\) 个整数,代表树的 \(\texttt{prufer}\) 序列。
输入样例#1:
6
1 3
1 5
3 2
3 6
5 4
输出样例#1:
3 5 1 3
样例#1 解释:
参考题目描述。
输入/输出样例#2、#3:
见下发文件。
数据范围:
对于 \(30\%\) 的数据,树是一条链;
对于另外 \(10\%\) 的数据,满足输入的 \(u = 1\);
对于另外 \(50\%\) 的数据,\(n \le 3000\);
对于 \(100\%\) 的数据,\(n \le 2×10^5\)。
用小根堆来维护最小编号即可。
// The code was written by yifan, and yifan is neutral!!!
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define bug puts("NOIP rp ++!");
#define rep(i, a, b, c) for (int i = (a); i <= (b); i += (c))
#define per(i, a, b, c) for (int i = (a); i >= (b); i -= (c))
template<typename T>
inline T read() {
T x = 0;
bool fg = 0;
char ch = getchar();
while (ch < '0' || ch > '9') {
fg |= (ch == '-');
ch = getchar();
}
while (ch >= '0' && ch <= '9') {
x = (x << 3) + (x << 1) + (ch ^ 48);
ch = getchar();
}
return fg ? ~x + 1 : x;
}
template<typename T>
void write(T x) {
if (x < 0) {
putchar('-');
x = -x;
}
if (x > 9) {
write(x / 10);
}
putchar(x % 10 + '0');
}
template<typename T>
void print(T x, char c) {
write(x);
putchar(c);
}
const int N = 2e5 + 5;
int n, cnt;
int dug[N];
bool ch[N];
vector<int> e[N], ans;
priority_queue<int, vector<int>, greater<int> > q;
int main() {
// freopen("prufer.in", "r", stdin);
// freopen("prufer.out", "w", stdout);
n = read<int>();
int x, y;
rep (i, 1, n - 1, 1) {
x = read<int>(), y = read<int>();
e[x].emplace_back(y);
e[y].emplace_back(x);
++ dug[x];
++ dug[y];
}
rep (i, 1, n, 1) {
ch[i] = 1;
if (dug[i] == 1) {
q.emplace(i);
}
}
int u;
while (cnt < n - 2) {
u = q.top();
q.pop();
for (int v : e[u]) {
-- dug[v];
if (dug[v] == 1 && ch[v]) {
q.emplace(v);
}
if (ch[v]) {
ans.emplace_back(v);
}
}
ch[u] = 0;
++ cnt;
}
for (int v : ans) {
print(v, ' ');
}
return 0;
}
美丽括号(beauty)
题目描述:
Pigbrain 有着独特的审美方式。
现在 Pigbrain 手上有一个长度为 \(n\) 的括号序列。根据 Pigbrain 的审美方式,他认为一个括号序列是美丽的,当且仅当:
-
括号序列的长度为偶数
-
括号序列的前半部分是左括号,后半部分是右括号。
现在,Pigbrain 想要删去原括号序列中的一部分括号(可以一个都不删除),使得剩下的括号组成一个美丽的括号序列。Pigbrain 当然会做啦,但是他想知道有多少种方法。你能帮他解决这个问题吗?
两种方法被认为是不同的,当且仅当存在一个位置,使得在其中一种方法中该位置的元素被删除,而另一个方
法中没有删除。
注意,尽管你的审美可能与 Pigbrain 不同,但你依然要帮他解决这个问题。
输入描述:
一行一个字符串,表示 Pigbrain 的括号序列。
输出描述:
一行,一个整数,代表答案。因为答案可能很大,因
此请输出答案对 \(10^9 +7\) 取模的结果。
输入样例#1:
()()()
输出样例#1:
7
样例#1 解释:
删除后得到 ()
的方法有 \(6\) 种,得到 (())
的有 \(1\) 种,因此答案为 \(6+1=7\)。
输入样例#2:
((()))
输出样例#2:
19
样例#2 解释:
删除后得到 ()
的方法有 \(3×3=9\) 种,删除后得到 (())
的方法有 \(\dfrac{3×2}{2} \times \dfrac{3×2}{2} = 9\) 种,删除后得到 ((()))
的方法有 \(1\) 种。
输入样例#3:
)(
输出样例#3:
0
样例#3 解释:
并没有合法的方案。
20 分做法
直接 dfs 搜索即可
50 分做法
枚举每一个左括号,枚举美丽括号的长度,设这个左括号为最右边的左括号,\(n\) 为该左括号左边的左括号的数量,\(m\) 为该左括号右边的右括号的数量,则答案为 \(\sum_{i = 1}^{\min \left (n, m \right )} C_{n - 1}^{i - 1} \times C_{m}^{i}\)。
70 分做法
特判特殊性质即可。
// The code was written by yifan, and yifan is neutral!!!
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define bug puts("NOIP rp ++!");
#define rep(i, a, b, c) for (int i = (a); i <= (b); i += (c))
#define per(i, a, b, c) for (int i = (a); i >= (b); i -= (c))
template<typename T>
inline T read() {
T x = 0;
bool fg = 0;
char ch = getchar();
while (ch < '0' || ch > '9') {
fg |= (ch == '-');
ch = getchar();
}
while (ch >= '0' && ch <= '9') {
x = (x << 3) + (x << 1) + (ch ^ 48);
ch = getchar();
}
return fg ? ~x + 1 : x;
}
template<typename T>
void write(T x) {
if (x < 0) {
putchar('-');
x = -x;
}
if (x > 9) {
write(x / 10);
}
putchar(x % 10 + '0');
}
template<typename T>
void print(T x, char c) {
write(x);
putchar(c);
}
const int N = 2e5 + 5;
const int mod = 1e9 + 7;
int n, cnt, cntleft, cntright;
ll ans;
char s[N];
bool yes[N];
ll cntl[N], cntr[N];
ll fac[N], inv[N];
void work() {
int cot = 0;
rep (i, 0, n - 1, 1) {
if (yes[i]) {
++ cot;
if (cot <= cnt / 2 && s[i] != '(') {
return ;
}
if (cot > cnt / 2 && s[i] != ')') {
return ;
}
}
}
++ ans;
ans %= mod;
}
void dfs(int u) {
if (u == n) {
if (cnt & 1 || cnt == 0) {
return ;
}
work();
return ;
}
yes[u] = 1;
++ cnt;
dfs(u + 1);
yes[u] = 0;
-- cnt;
dfs(u + 1);
}
ll qpow(ll x, ll y) {
ll res = 1;
while (y) {
if (y & 1) {
res = res * x % mod;
}
y >>= 1;
x = x * x % mod;
}
return res;
}
ll C(ll n, ll m) {
if (n < m) return 0;
return fac[n] * inv[m] % mod * inv[n - m] % mod;
}
int main() {
// freopen("beauty.in", "r", stdin);
// freopen("beauty.out", "w", stdout);
scanf("%s", s);
n = strlen(s);
if (n <= 20) {
dfs(0);
print(ans, '\n');
return 0;
}
fac[0] = inv[0] = 1;
rep (i, 1, n + 2, 1) {
fac[i] = fac[i - 1] * i % mod;
}
inv[n + 2] = qpow(fac[n + 2], mod - 2);
per (i, n + 1, 1, 1) {
inv[i] = inv[i + 1] * (i + 1) % mod;
}
int x = 0, fg = 1;
rep (i, 0, n - 2, 1) {
if (s[i] != s[i + 1]) {
if (x) {
fg = 0;
break ;
} else {
x = i + 1;
}
}
}
if (fg) {
int lim = min(x, n - x);
rep (i, 1, lim, 1) {
ans = (ans + C(x, i) * C(n - x, i) % mod) % mod;
}
print(ans, '\n');
return 0;
}
rep (i, 1, n, 1) {
if (s[i] == '(') {
++ cntleft;
} else {
++ cntright;
}
}
rep (i, 1, n - 1, 1) {
if (s[i - 1] == '(') {
cntl[i] = (cntl[i - 1] + 1) % mod;
} else {
cntl[i] = cntl[i - 1];
}
}
per (i, n - 2, 0, 1) {
if (s[i + 1] == ')') {
cntr[i] = (cntr[i + 1] + 1) % mod;
} else {
cntr[i] = cntr[i + 1];
}
}
ll lim = min(cntleft, cntright);
ll res = 0;
rep (i, 1, lim, 1) {
res = 0;
rep (j, 0, n - 1, 1) {
if (s[j] == '(') {
res = (res + 1ll * C(cntl[j], i - 1) % mod * C(cntr[j], i) % mod) % mod;
}
}
ans = (ans + res) % mod;
}
print(ans, '\n');
return 0;
}
100 分做法
上面的式子 \(\sum_{i = 1}^{\min \left (n, m \right )} C_{n - 1}^{i - 1} \times C_{m}^{i}\),设 \(n \le m\),那么这个式子可以写成 \(\sum_{i = 1}^{n} C_{n - 1}^{n - i} \times C_{m}^{i}\),由于 \(C_{n - 1}^{n} = 0\),所以这个式子可以再扩展为 \(\sum_{i = 0}^{n} C_{n - 1}^{n - i} \times C_{m}^{i}\),这个式子等于 \(\sum_{i = 1}^{n}C_{n + m - 1}^{n}\),复杂度 \(O(n)\)。
// The code was written by yifan, and yifan is neutral!!!
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define bug puts("NOIP rp ++!");
#define rep(i, a, b, c) for (int i = (a); i <= (b); i += (c))
#define per(i, a, b, c) for (int i = (a); i >= (b); i -= (c))
template<typename T>
inline T read() {
T x = 0;
bool fg = 0;
char ch = getchar();
while (ch < '0' || ch > '9') {
fg |= (ch == '-');
ch = getchar();
}
while (ch >= '0' && ch <= '9') {
x = (x << 3) + (x << 1) + (ch ^ 48);
ch = getchar();
}
return fg ? ~x + 1 : x;
}
template<typename T>
void write(T x) {
if (x < 0) {
putchar('-');
x = -x;
}
if (x > 9) {
write(x / 10);
}
putchar(x % 10 + '0');
}
template<typename T>
void print(T x, char c) {
write(x);
putchar(c);
}
const int N = 2e5 + 5;
const int mod = 1e9 + 7;
int n;
ll ans;
char s[N];
ll cntr[N];
ll fac[N << 1], inv[N << 1];
ll qpow(ll x, ll y) {
ll res = 1;
while (y) {
if (y & 1) {
res = res * x % mod;
}
y >>= 1;
x = x * x % mod;
}
return res;
}
ll C(ll n, ll m) {
if (n < m) return 0;
return fac[n] * inv[m] % mod * inv[n - m] % mod;
}
int main() {
scanf("%s", s);
n = strlen(s);
fac[0] = inv[0] = 1;
rep (i, 1, N - 1, 1) {
fac[i] = fac[i - 1] * i % mod;
}
inv[N - 1] = qpow(fac[N - 1], mod - 2);
per (i, N - 2, 1, 1) {
inv[i] = inv[i + 1] * (i + 1) % mod;
}
per (i, n - 1, 0, 1) {
if (s[i] == ')') {
cntr[i] = (cntr[i + 1] + 1) % mod;
} else {
cntr[i] = cntr[i + 1];
}
}
int cnt = 0;
rep (i, 0, n - 1, 1) {
if(s[i] == '(') {
++ cnt;
ans = (ans + C(cnt - 1 + cntr[i], cnt)) % mod;
}
}
print(ans, '\n');
return 0;
}
字串谜题(string)
题目描述:
Pigbrain 将美丽的括号序列种了下去,希望能够得到
更多的括号序列。
然而,地里长出了一棵树。
这棵树很奇怪。除了树根以外,它有 \(n\) 个节点。其中每个节点要么是从根部长出来,要么是接在其他节点上。
Pigbrain 仔细观察了这棵树,发现这棵树的每条树枝,包括从根部连出来的树枝上,都写着一个小写字母。
不难发现,这里的每个节点都可以表示一个字符串:将从根部到该节点的路径上所经过的树枝上的字母依次写下,就能得到一个字符串。
Pigbrain 发现这件事之后,想要进行 \(m\) 次询问。每次 Pigbrain 会给定一个字符串 \(S\) 和一个数字 \(x\),他想要知道 \(S\) 在第 \(x\) 个节点表示的字符串中出现了多少次。你能帮帮他吗?
输入描述:
第 \(1\) 行输入一个整数:\(n\),代表除根以外的节点个数。
接下来的 \(n\) 行,第 \(i\) 行的输入会是以下两种形式中的一种:
\(1 \ c\) —— 代表第 \(i\) 个节点是从根部直接长出来的,对应的树枝上写着字母 \(c\);
\(2 \ j \ c\) —— 代表第𝑖个节点是从节点 \(j(j<i)\) 长出来的,对应的树枝上写着字母 \(c\);
下一行输入一个整数:\(m\)。
接下来的 \(m\) 行,每行输入格式为:
\(x \ S\)——代表询问字符串 \(S\) 在第 \(x\) 个节点表示的字符串中出现了多少次。
输出描述:
对于每个询问,输出一行,代表答案。
输入样例#1:
20
1 d
2 1 a
2 2 d
2 3 a
2 4 d
2 5 a
2 6 d
2 7 a
1 d
2 9 o
2 10 k
2 11 i
2 12 d
2 13 o
2 14 k
2 15 i
2 1 o
2 17 k
2 18 i
2 15 i
12
8 da
8 dada
8 ada
6 dada
3 dada
19 doki
19 ok
16 doki
15 doki
9 d
1 a
20 doki
输出样例#1:
4
3
3
2
0
1
1
2
1
1
0
2
数据范围:
对于 \(20\%\) 的数据,\(n \le 500,|S| \le 500,\sum|S| \le 1000\);
对于 \(38\%\) 的数据,\(n \le 3000,m \le 3000,|S| \le 3000\);
对于另外 \(14\%\) 的数据,第 \(i\) 个节点从第 \(i-1\) 个节点长
出。特别地,第 \(1\) 个节点直接接在树根。
对于另外 \(14\%\) 的数据,所有询问中的 \(S\) 都相等。
对于另外 \(14\%\) 的数据,所有询问中的 \(x\) 都相等。
对于 \(100\%\) 的数据,满足 \( 1 \le n \le 4 \times 10^5,1 \le m \le 4 \times 10^5 1 \times x \times 𝑛,|S| \le 4 \times 10^5,\sum|S| \le 4 \times 10^5\)
数据范围中的 \(S\) 均指询问中给出的字符串。
保证给出的树中只含小写字母。
提示:
下发样例#2 满足\(𝑛 \le 500,|S| \le 500,\sum |S| \le 1000\);
下发样例#3 满足\(n \le 3000,m \le 3000,|S| \le 3000\);
下发样例#4 满足第 \(i\) 个节点从第 \(i−1\) 个节点长出;
下发样例#5 满足所有询问中的 \(S\) 都相等;
下发样例#6 满足所有询问中的 \(x\) 都相等。
38 分做法
算是暴力了。
// The code was written by yifan, and yifan is neutral!!!
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define bug puts("NOIP rp ++!");
#define rep(i, a, b, c) for (int i = (a); i <= (b); i += (c))
#define per(i, a, b, c) for (int i = (a); i >= (b); i -= (c))
template<typename T>
inline T read() {
T x = 0;
bool fg = 0;
char ch = getchar();
while (ch < '0' || ch > '9') {
fg |= (ch == '-');
ch = getchar();
}
while (ch >= '0' && ch <= '9') {
x = (x << 3) + (x << 1) + (ch ^ 48);
ch = getchar();
}
return fg ? ~x + 1 : x;
}
template<typename T>
void write(T x) {
if (x < 0) {
putchar('-');
x = -x;
}
if (x > 9) {
write(x / 10);
}
putchar(x % 10 + '0');
}
template<typename T>
void print(T x, char c) {
write(x);
putchar(c);
}
const int N = 4e5 + 5;
int n, m;
vector<int> t[N][26];
string s[N];
int idn(char c) {
return (c - 'a');
}
void dfs(int u, int fa, string st) {
s[u] = st;
rep (i, 0, 25, 1) {
if (t[u][i].size()) {
for (int v : t[u][i]) {
dfs(v, u, st + char('a' + i));
}
}
}
}
int main() {
freopen("string.in", "r", stdin);
freopen("string.out", "w", stdout);
n = read<int>();
int op, j;
char c;
rep (i, 1, n, 1) {
cin >> op;
if (op == 1) {
cin >> c;
t[0][idn(c)].emplace_back(i);
} else {
cin >> j >> c;
t[j][idn(c)].emplace_back(i);
}
}
dfs(0, -1, "");
m = read<int>();
string str, tmp;
int x;
rep (i, 1, m, 1) {
x = read<int>();
cin >> str;
tmp = s[x];
int len = tmp.size(), l = str.size();
int ans = 0;
rep (j, 0, len - l + 1, 1) {
int fg = 1;
rep (k, 0, l - 1, 1) {
if (tmp[j + k] != str[k]) {
fg = 0;
break ;
}
}
ans += fg;
}
print(ans, '\n');
}
return 0;
}
正解:一堆优质题解
没错,这是 CF 上的原题!
随便找一份题解粘一下……
毕竟 AC 自动机又不在考纲里面
#include<bits/stdc++.h>
#define rep(i,x,y) for(int i=x;i<=y;++i)
#define mar(o) for(int E=fst[o];E;E=e[E].nxt)
#define v e[E].to
using namespace std;
const int n7=401234,m7=801234,z7=1601234;
struct dino{int to,nxt;}e[z7];
struct lyca{int z,id;};
int n,T,cnt=1,tre[m7][26],fail[m7],poi[n7],head,tail,que[m7];
int ecnt,fst[m7],t,atre[n7],L[n7],R[n7],ans[n7];
char cr[n7];bool tru[m7][26];
vector <lyca> vec[m7];
int rd(){
int shu=0;char ch=getchar();
while(!isdigit(ch))ch=getchar();
while(isdigit(ch))shu=(shu<<1)+(shu<<3)+(ch^48),ch=getchar();
return shu;
}
void edge(int sta,int edn){
ecnt++;
e[ecnt]=(dino){edn,fst[sta]};
fst[sta]=ecnt;
}
void insert1(){
rep(i,1,n){
int sys=rd(),las=1;
if(sys==2)las=poi[ rd() ];
char ch=getchar()-'a';
if(!tre[las][ch]){
cnt++,tre[las][ch]=cnt;
}
poi[i]=tre[las][ch];
}
}
void insert2(int z){
int len=strlen(cr+1),now=1;
rep(i,1,len){
int ch=cr[i]-'a';
if(!tre[now][ch]){
cnt++,tre[now][ch]=cnt;
}
now=tre[now][ch];
}
poi[z]=now;
}
void Gfail(){
head=1,tail=1,que[1]=1;
rep(i,0,25)tre[0][i]=1;
while(head<=tail){
int now=que[head];
rep(i,0,25){
int edn=tre[ fail[now] ][i];
if(tre[now][i]){
fail[ tre[now][i] ]=edn;
edge(edn,tre[now][i]);
tail++,que[tail]=tre[now][i];
tru[now][i]=1;
}
else tre[now][i]=edn;
}
head++;
}
}
#define lb(z) (z&-z)
void updat(int z,int id){
while(id<=cnt)atre[id]+=z,id+=lb(id);
}
int Dquery(int id){
int tot=0;
while(id)tot+=atre[id],id-=lb(id);
return tot;
}
int query(int l,int r){
return Dquery(r)-Dquery(l-1);
}
void dfs1(int o){
t++,L[o]=t;
mar(o)dfs1(v);
R[o]=t;
}
int fimd(){
int len=strlen(cr+1),now=1;
rep(i,1,len)now=tre[now][ cr[i]-'a' ];
return now;
}
void dfs2(int o){
updat(1,L[o]);
int wal=vec[o].size()-1;
rep(i,0,wal){
int ll=L[ vec[o][i].z ];
int rr=R[ vec[o][i].z ];
ans[ vec[o][i].id ]=query(ll,rr);
}
rep(i,0,25){
if(tru[o][i])dfs2(tre[o][i]);
}
updat(-1,L[o]);
}
int main(){
n=rd(),insert1(),T=rd();
rep(i,1,T){
int z=rd();scanf("%s",cr+1);
insert2(i+n);
vec[ poi[z] ].push_back( (lyca){poi[i+n],i} );
}
Gfail(),dfs1(1),dfs2(1);
rep(i,1,T)printf("%d\n",ans[i]);
return 0;
}