CCF 非专业级软件能力认证入门级(第二轮)
CCF CSP 2019
时间:2019 年 mm 月 dd 日 hh : mm ~ hh : mm
题目名称 | Sheep | Tree | Sequence | Invitation |
---|---|---|---|---|
题目类型 | 传统型 | 传统型 | 传统型 | 传统型 |
目录 | sheep | tree | sequence | invitation |
可执行文件名 | sheep | tree | sequence | invitation |
输入文件名 | sheep.in | tree.in | sequence.in | invitation.in |
输出文件名 | sheep.out | tree.out | sequence.out | invitation.out |
每个测试点时限 | 1.0 s | 2.0 s | 2.0 s | 3.0 s |
内存限制 | 512 MB | 512 MB | 512 MB | 512 MB |
子任务数目 | 10 | 10 | 10 | 10 |
测试点是否等分 | 是 | 是 | 是 | 是 |
提交源程序文件名
对于 C++ 语言 | sheep.cpp | tree.cpp | sequence.cpp | invitation.cpp |
---|---|---|---|---|
对于 C 语言 | sheep.c | tree.c | sequence.c | invitation.c |
对于 pascal 语言 | sheep.pas | tree.pas | sequence.pas | invitation.pas |
编译选项
对于 C++ 语言 | -O2 -lm |
---|---|
对于 C 语言 | -O2 -lm |
对于 pascal 语言 | -O2 |
文章目录
-
CCF 非专业级软件能力认证入门级(第二轮) - A . Sheep
- B . Tree
- C . Sequence
- D . Invitation
- 题解 & 标程
- A . Sheep
- B . Tree
- C . Sequence
- D . Invitation
- 心路历程
A . Sheep
题目描述
今天是甜咩咩咚咚子的主场!
甜咩咩咚咚子是一个可爱的男孩子。作为一个整天咩咩叫的男孩子,他的本命当然是羊啦!
因为甜咩咩咚咚子整天咩咩叫,大家都听不清他在说什么,所以请你来帮忙告诉大家甜咩咩咚咚子今天说了什么有意义的话。
我们假设甜咩咩咚咚子今天说的话是一个只包含小写英文字母和数字的字符串,他咩咩叫的声音用 “mie” 表示。你需要做的就是把这个字符串中所有的 “mie” 去掉,并把剩下的字符输出。
输入格式
一行一个字符串,仅包含小写英文字母和数字。
输出格式
一行一个字符串,表示去掉所有 “mie” 之后的字符串。
样例输入
mietimieandmieongmie1miemiemie23mieamiekmieimieoimie
样例输出
tiandong123akioi
样例解释
咩咩咩!!!
数据规模
令 ∣ S ∣ |S| ∣S∣ 表示甜咩咩咚咚子说的话的长度。
对于 100 % 100 \% 100% 的数据, ∣ S ∣ ≤ 1 0 5 |S|\le 10^5 ∣S∣≤105 。
对于 50 % 50 \% 50% 的数据, ∣ S ∣ ≤ 100 |S|\le 100 ∣S∣≤100 。
B . Tree
题目描述
甜咩咩咚咚子是一个喜欢游戏的男孩子。
今天他想找香喵喵蕉蕉子玩,但是香喵喵蕉蕉子不在家,于是甜咩咩咚咚子只好在自家后院的树上玩。
甜咩咩咚咚子家的树与一般的树有所不同,这是一棵 n n n 个节点 n − 1 n-1 n−1 条无向边组成的无环连通图,点从 1 1 1 到 n n n 编号,每条边的边长为 1 1 1 。但是正当甜咩咩咚咚子想要爬上这棵他家引以为傲的大树的时候,他惊奇地发现他家的树只剩 m ( m < n − 1 ) m(m<n-1) m(m<n−1) 条边,树不再连通了。
现在甜咩咩咚咚子想要给这张不连通的图连上 n − m − 1 n-m-1 n−m−1 条边,使它重新成为一棵树。因为甜咩咩咚咚子站得离 s s s 号点最近,所以为了方便起见,他还希望连边之后树上所有点离 s s s 号点的距离之和最小。树上两个点间的距离 d i s ( x , y ) dis(x,y) dis(x,y) 定义为两个点之间最短路径包含的边数。
请你连上 n − m − 1 n-m-1 n−m−1 条边,然后告诉他这个最小的距离之和。
输入格式
第一行一个正整数 n , m , s n,m,s n,m,s 分别表示原树的点数和剩下的边数和甜咩咩咚咚子离得最近的点的编号 。
接下来 m m m 行每行两个正整数 x i , y i x_i,y_i xi,yi 表示 x i , y i x_i,y_i xi,yi 两个点之间有一条长度为 1 1 1 的无向边相连。保证给出的边不组成环。
输出格式
一行一个正整数表示答案。
样例输入
9 7 9
1 2
1 3
2 4
2 5
2 6
7 8
7 9
样例输出
15
样例解释
如图,连 1 1 1 条边 ( 2 , 9 ) (2, 9) (2,9) ,树上所有点到 s s s 的距离之和为 15 15 15 。可以证明没有比这更优的方案。
数据范围
对于 100 % 100\% 100% 的数据, n ≤ 1 0 5 , m < n − 1 , 1 ≤ s , x i , y i ≤ n n\le 10^5,m<n-1,1\le s,x_i,y_i \le n n≤105,m<n−1,1≤s,xi,yi≤n
对于 30 % 30 \% 30% 的数据, n ≤ 1000 n\le 1000 n≤1000 。
对于另外 20 % 20 \% 20% 的数据, m = n − 2 m=n-2 m=n−2 。
对于再另外 20 % 20 \% 20% 的数据, m = 0 m=0 m=0 。
C . Sequence
题目描述
甜咩咩咚咚子是一个有强迫症的男孩子。
他有一些奇怪的癖好,比如他从来不喜欢把饭和菜混在一起吃。每当有人问起他,“难道饭和菜不会在嘴里混合吗?”,他便看着问他的人,露出一副不屑置辩的眼神。等其他人要再问,他嘴里就全是些之乎者也之类的东西,令人不解了。
这天,甜咩咩咚咚子的强迫症又犯了。他面前有一个长度为 n n n 的序列,他想要从中取出长度恰好为 m m m 的一个子序列,满足子序列严格单调递增。换句话说,假如他的序列是 a 1 , a 2 , . . . , a n a_1,a_2,...,a_n a1,a2,...,an ,那他想要的子序列就是一组 1 ≤ i 1 < i 2 < . . . < i m ≤ n 1\le i_1<i_2<...<i_m\le n 1≤i1<i2<...<im≤n ,满足 a i 1 < a i 2 < . . . < a i m a_{i_1}<a_{i_2}<...<a_{i_m} ai1<ai2<...<aim 。
因为这样的子序列太多了,甜咩咩咚咚子还在纠结改选哪个好。请你先告诉他有多少个不同的子序列满足要求,对 1 0 9 + 7 10^9+7 109+7 取模。
输入格式
第一行两个正整数 n , m n,m n,m 表示序列长度和甜咩咩咚咚子想要的子序列长度。
接下来一行 n n n 个数,第 i i i 个为序列中的 a i a_i ai 。
输出格式
一行一个整数表示满足要求的子序列个数。
样例输入1
3 2
1 1 2
样例输出1
2
样例解释1
长度为 2 2 2 的子序列有 3 3 3 个,分别为 { 1 , 1 } , { 1 , 2 } , { 1 , 2 } \{1,1\},\{1,2\},\{1,2\} {1,1},{1,2},{1,2} 。其中有两个满足严格单调递增。
样例输入2
7 3
1 7 3 5 9 4 8
样例输出2
12
样例解释2
出题人想出了一个精妙的解释,但是太长了,这里写不下。
数据规模
对于 100 % 100\% 100% 的数据, n ≤ 10000 , m ≤ 100 , − 1 0 9 ≤ a i ≤ 1 0 9 n\le 10000,m\le 100,-10^9\le a_i\le 10^9 n≤10000,m≤100,−109≤ai≤109
对于 20 % 20 \% 20% 的数据, n ≤ 100 n\le 100 n≤100 。
对于另外 20 % 20 \% 20% 的数据, 序列 { a i } \{a_i\} {ai} 满足严格单调递增。
对于再另外 20 % 20 \% 20% 的数据, { a i } \{a_i\} {ai} 中存在一个最小值,并且最小值的左边严格单调递减,右边严格单调递增。
D . Invitation
题目描述
香喵喵蕉蕉子是甜咩咩咚咚子的好朋友。
香喵喵蕉蕉子是一个喜欢男孩子的男孩子,暑假里,他每天都请男孩子去他家吃饭,还给他们康一些好康的东西。
那天,他邀请了甜咩咩咚咚子去他家吃晚饭。甜咩咩咚咚子是一个好学的男孩子,他翻开了香喵喵蕉蕉子之前请客的记录,发现他总共请过 n n n 个不同的男孩子,假设每个人 i i i 来过 a i a_i ai 次,他还知道来过的总人次 ∑ i = 1 n a i = m \sum_{i=1}^n a_i=m ∑i=1nai=m ,但是这本记录有些杂乱,并没有记录每个人来的顺序。
而善于发现的甜咩咩咚咚子又从这本记录中看出了一些奥秘,他发现所有 a i a_i ai 的最小公倍数居然是 1 1 1, 即 gcd i = 1 n a i = 1 \gcd_{i=1}^{n} a_i=1 gcdi=1nai=1 。
时间飞逝,暑假过完了,男孩子们在香喵喵蕉蕉子的家里留下了美好的回忆。但是这本载满回忆的请客记录却不知道去了哪里。所以香喵喵蕉蕉子有 q q q 个询问,每次问你:假如这本记录上记载有 n n n 个不同的男孩子,每个男孩子至少来过一次,而他们总共来过 m m m 次,且满足 gcd i = 1 n a i = 1 \gcd_{i=1}^{n} a_i=1 gcdi=1nai=1 ,有多少种可行的请客方案,答案对 1 0 9 + 7 10^9+7 109+7 取模。
一种请客方案即一个长度为 n n n 的序列 { a i } ( a i ≥ 1 ) \{a_i\}(a_i\ge 1) {ai}(ai≥1),表示每个男孩子来过几次。两种方案 { a i } \{a_i\} {ai} 和 { b i } \{b_i\} {bi} 不同当且仅当 ∃ i , a i ≠ b i \exist i, a_i\neq b_i ∃i,ai=bi。注意,每个男孩子互不相同,所以 { 1 , 2 } \{1,2\} {1,2} 和 { 2 , 1 } \{2,1\} {2,1} 是两种不同的方案。
输入格式
第一行一个正整数 q q q , 表示询问个数。
接下来 q q q 行,每行两个数 m , n m,n m,n,分别表示请客的总人次和男孩子的个数。
输出格式
q q q 行,每行一个非负整数表示请客方案数对 1 0 9 + 7 10^9+7 109+7 取模。
样例输入
6
6 2
7 2
6 3
6 4
7 4
83160 65251
样例输出
2
6
9
10
20
29602786
样例解释
m = 6 , n = 2 m=6,n=2 m=6,n=2 时,可行的请客方案有 { 1 , 5 } \{1,5\} {1,5} 和 { 5 , 1 } \{5,1\} {5,1} 。
数据规模
对于 100 % 100\% 100% 的数据, q , n , m ≤ 1 0 5 , n ≤ m q,n,m\le 10^5,n\le m q,n,m≤105,n≤m。
对于 20 % 20 \% 20% 的数据, q , n , m ≤ 5 q,n,m\le 5 q,n,m≤5 。
对于 40 % 40 \% 40% 的数据, q ≤ 5 , n , m ≤ 1000 q \le 5,n,m\le 1000 q≤5,n,m≤1000 。
对于 60 % 60 \% 60% 的数据, q ≤ 1 0 5 , n , m ≤ 1000 q \le 10^5,n,m\le 1000 q≤105,n,m≤1000 。
题解 & 标程
A . Sheep
暴力。注意细节,小心结尾的 mie
,小心不要漏了 mi
和 m
。
O ( n ) O(n) O(n)
#include<bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
char s[N], t[4] = "mie";
int n;
int main()
{
scanf("%s", s);
n = strlen(s);
int j = 0;
for (int i = 0; i < n; ++ i){
if (s[i] == t[j]) j = (j+1)%3;
else{
for (int k = 0; k < j; ++ k) putchar(t[k]);
j = 0;
if (s[i] == 'm') j = 1;
else putchar(s[i]);
}
}
for (int k = 0; k < j; ++ k) putchar(t[k]);
puts("");
return 0;
}
B . Tree
给出的数据相当于一片森林,也就是很多棵树。
对于每一棵树,假如 s s s 就在这棵树里,那么只需要求这棵树中所有点到 s s s 的距离就好了,这个一遍 dfs ,过程中记录一下子树大小和子树中所有点到子树的根的距离,就可以方便地转移。
对于 s s s 不在这棵树里的情况,我们需要找一个点 u u u ,把 u u u 和 s s s 连边,想要最小化这棵子树中所有点到 s s s 的距离,就是要最小化所有点到 u u u 的距离,因为边 ( u , s ) (u,s) (u,s) 的贡献是固定的,就是这棵树的大小。
所以我们继续用 dfs 解决。先随便找一个点做 u u u,求出所有点到他的距离和,然后把 u u u 往他的子树中移动。假如将点 u u u 从当前位置移动到一个子树大小为 s i z [ v ] siz[v] siz[v] 的儿子 v v v 上,距离和的变化量是 Δ = s i z _ t o t − 2 ∗ s i z [ v ] \Delta = siz\_tot-2*siz[v] Δ=siz_tot−2∗siz[v] (其中 s i z _ t o t siz\_tot siz_tot 表示整棵树的大小)。所以只要对所有点找出最大的儿子,假如往这个儿子跳能够使答案变小,那么就跳,直到无法使答案变小为止。
也可以证明最后选取的 u u u 就是每棵树的重心,要是你会求重心的话,这题也可以很快的做出来。
O ( n ) O(n) O(n)
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N = 1e5 + 10;
int n, m, s;
vector<int> to[N];
int siz[N], sum[N], ans;
bool vis[N];
void dfs(int u, int fa)
{
vis[u] = 1;
siz[u] = 1; sum[u] = 0;
for (int i = 0, qw = to[u].size(); i < qw; ++ i)
if (to[u][i] != fa){
dfs(to[u][i], u);
siz[u] += siz[to[u][i]];
sum[u] += sum[to[u][i]] + siz[to[u][i]];
}
}
int dfs2(int u, int fa, int rt, int now)
{
int son = -1;
for (int i = 0, qw = to[u].size(); i < qw; ++ i)
if (to[u][i] != fa && siz[rt]-2*siz[to[u][i]] < 0){
if (son == -1 || siz[rt]-2*siz[to[u][i]] < siz[rt]-2*siz[son]) son = to[u][i];
}
if (son == -1) return now;
return dfs2(son, u, rt, now+siz[rt]-2*siz[son]);
}
signed main()
{
ios::sync_with_stdio(false);
cin >> n >> m >> s;
for (int i = 1; i <= m; ++ i){
int x, y;
cin >> x >> y;
to[x].push_back(y);
to[y].push_back(x);
}
dfs(s, 0); ans = sum[s];
for (int i = 1; i <= n; ++ i)
if (!vis[i]){
dfs(i, 0);
ans += siz[i];
ans += dfs2(i, 0, i, sum[i]);
}
cout << ans << endl;
return 0;
}
C . Sequence
可以先写出 DP 方程式: 设 d p [ i ] [ j ] dp[i][j] dp[i][j] 表示前 i i i 个数,长度恰好为 j j j 的严格上升子序列的长度。那么 d p [ i ] [ j ] = ∑ d p [ k ] [ j − 1 ] ( k < i , a [ k ] < a [ i ] ) dp[i][j]=\sum dp[k][j-1](k<i,a[k]<a[i]) dp[i][j]=∑dp[k][j−1](k<i,a[k]<a[i]) 。
再结合部分分,可以得到不错的好成绩。
然后考虑树状数组优化。用 m m m 个树状数组, t [ i ] [ j ] t[i][j] t[i][j] 表示到当前位置之前的 ∑ d p [ k ] [ i ] ( a [ k ] ≤ j ) \sum dp[k][i](a[k] \leq j) ∑dp[k][i](a[k]≤j) 。那么转移的时候在树状数组上询问,然后再把当前的 d p dp dp 值加入树状数组就好了。
O ( n m log n ) O(nm\log n) O(nmlogn)
#include<bits/stdc++.h>
using namespace std;
const int N = 1e4 + 10, M = 1e2 + 10, mod = 123456789;
int n, m, a[N], t[M][N], ans;
vector<int> v;
void add(int &x, int y){x += y; if (x >= mod) x -= mod;}
int lowbit(int x){return x&-x;}
int query(int s, int x){
int ret = 0;
for (int i = x; i; i -= lowbit(i))
add(ret, t[s][i]);
return ret;
}
void modify(int s, int x, int y){
for (int i = x; i < N; i += lowbit(i))
add(t[s][i], y);
}
int main()
{
while (scanf("%d%d", &n, &m) == 2){
v.clear();
for (int i = 1; i <= n; ++ i){
scanf("%d", &a[i]);
v.push_back(a[i]);
}
sort(v.begin(), v.end());
v.resize(unique(v.begin(), v.end())-v.begin());
for (int i = 1; i <= n; ++ i)
a[i] = lower_bound(v.begin(), v.end(), a[i])-v.begin()+2;
memset(t, 0, sizeof(t));
modify(0, 1, 1);
ans = 0;
for (int i = 1; i <= n; ++ i)
for (int j = 1; j <= m; ++ j){
int f = query(j-1, a[i]-1);
if (j < m) modify(j, a[i], f);
else add(ans, f);
}
printf("%d\n", ans);
}
return 0;
}
D . Invitation
首先考虑没有 gcd \gcd gcd 限制的时候,问题转化为: m m m 个球放到 n n n 个盒子里,盒子不能空的方案数。这个问题可以用隔板法解决,答案是 C m − 1 n − 1 C_{m-1}^{n-1} Cm−1n−1 。
然后考虑解决 gcd \gcd gcd 的限制。我们考虑容斥,找出 n n n 的所有质因数,假设是 p 1 , p 2 , . . . p k p_1,p_2,...p_k p1,p2,...pk ,那么对于 gcd \gcd gcd 是 p i p_i pi 的倍数的情况,我们需要把他减掉,对于这个问题,可以把 p i p_i pi 个球看成一个球,做上面的放球问题。然后对于 gcd \gcd gcd 是 p i ∗ p j p_i*p_j pi∗pj 的倍数的情况,前面重复减了,所以要加上。以此类推,这个过程可以用状压解决。
O ( n + q ( n + 2 6 ) ) O(n+q(\sqrt{n}+2^6)) O(n+q(n+26))
#include<bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10, mod = 1e9 + 7, S = 1<<7;
int fc[N], ifc[N], p[N], pn, cnt[S];
bool b[N];
int m, a[S], t[S];
template<class T> inline T fpow(T x, T y, T p){
T r = 1;
while (y){
if (y&1) r = 1LL*r*x%p;
x = 1LL*x*x%p;
y >>= 1;
}
return r;
}
void init()
{
fc[0] = 1;
for (int i = 1; i < N; ++ i)
fc[i] = 1LL*fc[i-1]*i%mod;
ifc[N-1] = fpow(fc[N-1], mod-2, mod);
for (int i = N-2; i >= 0; -- i)
ifc[i] = 1LL*ifc[i+1]*(i+1)%mod;
cnt[0] = 0;
for (int i = 1; i < S; ++ i)
cnt[i] = cnt[i^(i&-i)]+1;
}
int C(int n, int m)
{
if (m < 0 || m > n) return 0;
return 1LL*fc[n]*ifc[m]%mod*ifc[n-m]%mod;
}
int main()
{
ios::sync_with_stdio(false);
init();
int q; cin >> q;
for (int i = 1; i <= q; ++ i){
int n, f, ans, tmp;
cin >> n >> f;
tmp = n; m = 1;
for (int j = 2, qw = sqrt(n); j <= qw; ++ j)
if (tmp % j == 0){
a[m] = j; m <<= 1;
while (tmp%j == 0) tmp /= j;
}
if (tmp > 1) a[m] = tmp, m <<= 1;
t[0] = 1; ans = C(n-1, f-1);
for (int j = 1; j < m; ++ j){
t[j] = t[j^(j&-j)] * a[j&-j];
ans = (ans + ((cnt[j]&1) ? -1 : 1)*C(n/t[j]-1, f-1)) % mod;
}
cout << (ans+mod)%mod << endl;
}
return 0;
}
心路历程
挣扎了一个星期,这套比赛终于是出搬完了。前两题是原创题,后两题分别来自 HDU 和 codeforces ,具体来源我好想有点忘了 qwq 。
出一整套比赛真的挺累的,特别是出数据。但是后来我写了 get_in
和 get_ans
的脚本,用了 c++
的 sprintf
,然后对于每道题只需要写好标算和生成数据,就可以快速地生成多组数据并相应的给出答案。
注意在生成多组随机数据的时候,在两次生成之间最好有一段时间间隔。在难以保证数据强度的时候使用多组数据是一个不错的选择。
对我来说,这次出题积累了宝贵的经验。