第十二届蓝桥杯软件赛省赛C++ A组 题解。
 
    
    
以下代码均通过测试,可放心食用。
本期知识点:最短路,状压 dp,博弈,DFS,多维 dp。
填空题 2. 直线 数据不大,直接暴力所有选点,将可能的直线加入 set 中,最后返回 set 的大小即可。注意这里处理斜率不存在的直线,不加入 set,最后计数时再直接加上。输出 40257 。
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 #include <bits/stdc++.h>  #define  int long long using  namespace  std;typedef  pair<int , int > PII;typedef  pair<double , double > PDD;void  solve ()     int  n, m; cin >> n >> m;              vector<PII>p (n * m);     set<PDD>st;     for (int  i = 0 ; i < n; i++){         for (int  j = 0 ; j < m; j++){             p[i * m + j] = {i, j};         }     }     for (int  i = 0 ; i < p.size (); i++){         for (int  j = i + 1 ; j < p.size (); j++){             auto  [x1, y1] = p[i];             auto  [x2, y2] = p[j];             if (x1 == x2) continue ;             double  k = 1.0  * (y2 - y1) / (x2 - x1);             double  b = 1.0  * (x2 * y1 - x1 * y2) / (x2 - x1);             st.emplace (k, b);         }     }     cout << st.size () + n; } signed  main ()     ios::sync_with_stdio (0 );     cin.tie (0 );     solve ();     return  0 ; } 
3. 货物摆放 题意:将 n 拆成 3 个数的乘积,求有多少种拆法。
做质因数分解:$n=p_1^{q_1}p_2^{q_2}\cdots p_k^{q_k}$,分别处理不同质因子,乘法原理即可。
对于一个 $p_i^{q_i}$,希望将 $q_i$ 拆分为 3 个自然数(0, 1, …) 的和,这就是高中常处理的隔板问题,方案数 $C_{q_i+2}^2$。
因此最终结果应为 $\prod\limits_{i=1}^k C_{q_i+2}^2$,输出结果 2430 。
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  int long long using  namespace  std;void  solve ()     int  n; cin >> n;          vector<int >p;     for (int  i = 2 ; i * i <= n; i++){         if (n % i == 0 ){             int  u = 0 ;             while (n % i == 0 ){                 u++; n /= i;             }             p.emplace_back (u);         }     }     if (n != 1 ) p.emplace_back (1 );          int  res = 1 ;     for (auto  &i : p){         res *= (i + 2 ) * (i + 1 ) / 2 ;     }     cout << res; } signed  main ()     ios::sync_with_stdio (0 );     cin.tie (0 );     solve ();     return  0 ; } 
4. 路径 直接按题意建图,跑一遍最短路即可,输出 10266837 。
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 #include <bits/stdc++.h>  #define  int long long using  namespace  std;typedef  pair<int , int > PII;void  solve ()     int  n; cin >> n;     vector<vector<PII>>adj (n + 1 );     for (int  i = 1 ; i <= n; i++){         for (int  j = i + 1 ; j <= i + 21  && j <= n; j++){             int  w = lcm (i, j);             adj[i].emplace_back (w, j);             adj[j].emplace_back (w, i);         }     }     vector<int >dist (n + 1 , 1e9 ); dist[1 ] = 0 ;     vector<bool >vis (n + 1 , 0 );     priority_queue<PII, vector<PII>, greater<PII>>Q;     Q.emplace (0 , 1 );     while (!Q.empty ()){         int  u = Q.top ().second; Q.pop ();         if (vis[u]) continue ;         vis[u] = 1 ;         for (auto  &[d, v] : adj[u]){             if (dist[v] > dist[u] + d){                 dist[v] = dist[u] + d;                 Q.emplace (dist[v], v);             }         }     }     cout << dist[n]; } signed  main ()     ios::sync_with_stdio (0 );     cin.tie (0 );     solve ();     return  0 ; } 
5. 回路计数 题意:求图中经过所有点的哈密尔顿回路个数。
第一想法是,数据 $n=21$ 不大,试试 dfs 能不能过,写完跑不出结果,一算 $n!$ 近似 $5e19$,显然跑不下来。
正解是状压 dp,在(已经经过哪些点)的集合之间转移。
dp[i][j] 表示,节点集合 i 中, 以 j 为结尾的路径的数量,直接在不同集合间转移即可,最后汇总满集合的路径总数量。时间复杂度 $O(n^22^n)$ ,最大近似 $9e8$,能跑下来,最终输出 881012367360 。
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 #include <bits/stdc++.h>  #define  int long long using  namespace  std;void  solve ()     int  n; cin >> n;          vector<vector<bool >>g (n, vector <bool >(n));     vector<vector<int >>dp (1  << n, vector <int >(n));          for (int  i = 1 ; i <= n; i++){         for (int  j = i + 1 ; j <= n; j++){             if (gcd (i, j) == 1 ){                 g[i - 1 ][j - 1 ] = g[j - 1 ][i - 1 ] = 1 ;             }         }     }     dp[1 ][0 ] = 1 ;     for (int  i = 0 ; i <= 1  << n; i++){         for (int  j = 0 ; j < n; j++){             if (i >> j & 1 ){                 for (int  k = 0 ; k < n; k++){                     if (i - (1  << j) >> k & 1  && g[k][j]){                         dp[i][j] += dp[i - (1  << j)][k];                     }                 }             }         }     }     int  res = 0 ;     for (int  i = 1 ; i < n; i++) res += dp[(1  << n) - 1 ][i];     cout << res; } signed  main ()     ios::sync_with_stdio (0 );     cin.tie (0 );     solve ();     return  0 ; } 
程序题 1. 卡片 不难发现,$\dfrac{k(k+1)}{2}\ge n$,已知 $n$ 倒推最小的 $k$ 即可。
$n$ 范围 $1e9$,很多方法都可以,遍历 $O(\sqrt n)$,二分 $O(\log n)$,求根式解 $O(1)$,挑一个写就好了。
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>  #define  int long long using  namespace  std;void  solve ()     int  n; cin >> n;          int  l = 0 , r = 1e5 ;     while (l + 1  != r){         int  mid = l + r >> 1 ;         if (mid * (mid + 1 ) >= 2  * n) r = mid;         else  l = mid;     }     cout << r; } signed  main ()     ios::sync_with_stdio (0 );     cin.tie (0 );     solve ();     return  0 ; } 
6. 砝码称重 没什么好说的,直接模拟就可以了,每多一个砝码,在原有可称重量上转移。
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 #include <bits/stdc++.h>  #define  int long long using  namespace  std;void  solve ()     int  n; cin >> n;     vector<int >a (n);     for (int  i = 0 ; i < n; i++) cin >> a[i];     set<int >st;     st.emplace (0 );     for (auto  &i : a){         auto  tmp = st;         for (auto  &u : tmp){             st.emplace (u + i);             if (u - i > 0 ) st.emplace (u - i);             if (i - u > 0 ) st.emplace (i - u);         }     }     cout << st.size () - 1 ; } signed  main ()     ios::sync_with_stdio (0 );     cin.tie (0 );     solve ();     return  0 ; } 
7. 异或数列 比较两数大小即比较谁有更高位的 1,从高位往低位比较:
如果该位的 1 个数为偶数,那么无论两人怎么取,在该位上两人都会相同,因此此位无影响,继续比较下一位。 
如果该位的 1 个数为奇数,不难发现,掌控着最后一个 1 的人取得胜利。
如果只有一个 1,那么 Alice 拿去,Alice 胜。 
如果此位 0 的个数为偶数,那么 Alice 先拿 1,场上剩下的 0 和 1 都为偶数,接下来只需要 Bob 拿什么,Alice 拿什么,就一定能拿到最后一个 1,Alice 胜。 
如果此位 0 的个数为奇数,那么第一次取数时,无论 Alice 拿什么,Bob 拿相反的,场上剩下的 0 和 1 都为偶数,接下来只需要 Alice 拿什么,Bob 拿什么,就一定能拿到最后一个 1,Bob 胜。 
 
 
 
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 #include <bits/stdc++.h>  #define  int long long using  namespace  std;void  solve ()     int  n; cin >> n;     vector<int >a (n);     for (int  i = 0 ; i < n; i++) cin >> a[i];     vector<int >p (21 );     for (int  k = 0 ; k < 21 ; k++){         for (int  i = 0 ; i < n; i++){			             p[k] += (a[i] >> k) & 1 ;         }     }     for (int  i = 20 ; i >= 0 ; i--){         if (p[i] % 2  == 0 ) continue ;         if (p[i] == 1 ){             cout << 1  << '\n' ;             return ;         }         if (n % 2  == 1 ){             cout << 1  << '\n' ;             return ;         }         cout << -1  << '\n' ;         return ;     }     cout << 0  << '\n' ; } signed  main ()     ios::sync_with_stdio (0 );     cin.tie (0 );     int  t; cin >> t;     while (t--) solve ();     return  0 ; } 
8. 左孩子右兄弟 难点大概在从题目简短的描述中读懂左孩子右兄弟的转换方法……
不难发现,当前子树的最大深度,为自身的孩子个数,加上某个孩子为根的子树的最大深度,如此 dfs 即可。
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 #include <bits/stdc++.h>  #define  int long long using  namespace  std;void  solve ()     int  n; cin >> n;     vector<vector<int >>adj (n + 1 );     for (int  i = 2 ; i <= n; i++){         int  p; cin >> p;         adj[p].emplace_back (i);     }     function<int (int )>dfs = [&](int  u){         int  res = 0 ;         for (auto  &v : adj[u]){             res = max (res, dfs (v));         }         return  res + adj[u].size ();     };     cout << dfs (1 ); } signed  main ()     ios::sync_with_stdio (0 );     cin.tie (0 );     solve ();     return  0 ; } 
9. 括号序列 
不难发现,存在一个分割位置,在分割位置前,只加入 (,在分割位置后,只加入 )。计算总方案时,只需利用乘法原理将两种添加括号的方案数相乘即可,下面只考虑加入左括号。
实际上,由于以 ) 做分割,每一个 ) 前的位置都可以添加 (。因此能够添加 ( 的位置个数即为 ) 的个数。
未匹配的 ) 有多少个,就加多少个。
当前位必须满足紧跟其后 ) 已完成匹配。
解:用 min_p 记录当前 ) 所需的最小 ( 数,dp[i][j] 表示考虑第 i 个 ),已经使用 j 个 ( 的方案数。
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 #include <bits/stdc++.h>  #define  int long long using  namespace  std;constexpr  int  mod = 1e9  + 7 ;void  solve ()     string s; cin >> s;     auto  cal = [](string s) -> int {         int  t = 0 , all = 0 , p = 0 ;                  vector<int >min_p (1 );         for (auto  &u : s){             if (u == '(' ) t++;             else {                 all++;                 if (--t < 0 ){                     t = 0 ;                     p++;                 }                 min_p.emplace_back (p);             }         }         vector<vector<int >>dp (all + 1 , vector <int >(p + 1 ));         vector<vector<int >>pre (all + 1 , vector <int >(p + 1 ));         dp[0 ][0 ] = 1 ;         for (int  j = 0 ; j <= p; j++) pre[0 ][j] = 1 ;         for (int  i = 1 ; i <= all; i++){             for (int  j = min_p[i]; j <= p; j++){                 dp[i][j] = pre[i - 1 ][j];             }             pre[i][min_p[i]] = dp[i][min_p[i]];             for (int  j = min_p[i] + 1 ; j <= p; j++){                 pre[i][j] = pre[i][j - 1 ] + dp[i][j];                 pre[i][j] %= mod;             }         }         return  dp[all][p];     };          int  resl = cal (s);     reverse (s.begin (), s.end ());     for (auto  &u : s) u = '('  + ')'  - u;     int  resr = cal (s);     cout << resl * resr % mod; } signed  main ()     ios::sync_with_stdio (0 );     cin.tie (0 );     solve ();     return  0 ; } 
10. 分果果 动态规划,解析见代码。时间复杂度 $O(wn^3)$。
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 #include <bits/stdc++.h>  #define  int long long using  namespace  std;constexpr  int  inf = 1e9 ;void  solve ()     int  n, m; cin >> n >> m;     vector<int >w (n + 1 );          for (int  i = 1 ; i <= n; i++) {         cin >> w[i];         w[i] += w[i - 1 ];     }     int  res = inf;          for (int  minn = 1 ; minn <= w[n] * 2  / m; minn++){         vector<vector<vector<int >>>dp (m + 1 , vector<vector<int >>(n + 1 , vector <int >(n + 1 , inf)));                           dp[0 ][0 ][0 ] = minn;                  for (int  i = 1 ; i <= m; i++){                          for (int  k = 0 ; k <= n; k++){                                  int  p = 0 ;                                  for (int  j = k; j <= n; j++){                                          if (k > 0 ){                         dp[i][j][k] = min (dp[i][j][k], dp[i][j][k - 1 ]);                     }                                          while (p < j && w[j] - w[p] >= minn){                         p++;                     }                                          if (p > 0 ){                         int  pp = min (p - 1 , k);                         dp[i][j][k] = min (dp[i][j][k], max (dp[i - 1 ][k][pp], w[j] - w[pp]));                     }                 }             }             res = min (res, dp[m][n][n] - minn);         }     }     cout << res; } signed  main ()     ios::sync_with_stdio (0 );     cin.tie (0 );     solve ();     return  0 ; }