寒假补题


H-牛牛看云_2022牛客寒假算法基础集训营1 (nowcoder.com)

  思路:

赛时和队友都想到了二分的做法,但是我觉得这个数据量1000 * 1000完全可以过啊,于是赛后就看到了下面这个代码,这个代码的思维量比二分大不少

既然要求所有任意数对与1000差值的绝对值的和,每个数的范围又是 0 - 1000,统计个数就行

b.collections 相当于是把 a 里的出现的每个 x 放到 map 里使得:map[x]++,得到的 map 就是 b

第一个双重循环:对于每个数值相异数对,拿一个 i 有 b[i] 种取法,拿一个 j 有 b[j] 种取法,公式考虑顺序,因此除以排列数A22,也就是2

第二个单重循环:对于数值不相异的数对,如果位置相异,当作数值相异计算,因此包含在二重循环中,位置相同则是自己和自己的贡献

此时不需要用 b 单独算组合数,直接扫一遍 a 数组就行

  代码:

 1 import collections
 2 n = int(input())
 3 a = list(map(int, input().split()))
 4 b = collections.Counter(a)
 5 ans = 0
 6 for i in range(1001):
 7     for j in range(1001):
 8         ans += b[i] * b[j] * abs(i + j - 1000) / 2
 9 for i in range(n):
10     ans += abs(a[i] - 500)
11 print(int(ans))

F-小沙的算数_2022牛客寒假算法基础集训营2 (nowcoder.com)

  思路:

乘法块分出来之后wa了,问题出在题目数据太大而进行了取模运算,取模和除法的顺序不能换

因此用逆元,但是我队当时三个人都wa了,逆元好像用的不对。。。

 1 #include
 2 using namespace std;
 3 typedef long long ll;
 4 const int mod = 1e9 + 7;
 5 const int N = 1e6 + 8;
 6 char s[N];
 7 ll num[N], pos[N], n, q, a[N];
 8 ll ksm(ll a, ll b) {
 9     ll ans = 1;
10     while (b) {
11         if (b & 1) (ans *= a) %= mod;
12         (a *= a) %= mod;
13         b >>= 1;
14     }
15     return ans;
16 }
17 int main() {
18     cin >> n >> q;
19     scanf("%s", s + 1);
20     for (int i = 1; i <= n; i++) cin >> a[i];
21     int cnt = 1;
22     num[1] = a[1];
23     pos[1] = 1;
24     //放到块里
25     for (int i = 2; i <= n; i++) {
26         if (s[i - 1] == '*') {
27             pos[i] = cnt;
28             num[cnt] = num[cnt] * a[i] % mod;
29         }
30         else {
31             num[++cnt] = a[i];
32             pos[i] = cnt;
33         }
34     }
35     ll ans = 0, x = 0, y = 0;
36     for (int i = 1; i <= cnt; i++)  (ans += num[i]) %= mod;
37 
38     while (q--) {
39         cin >> x >> y;
40         //先把答案里去掉这块
41         ans = (ans - num[pos[x]] + mod) % mod;
42         //更新这块,然后加回去就完事了
43         //原块的值 * 修改值 % mod / 原值 % mod 错到西伯利亚去了
44         //原块的值 * 原值的逆元 % mod * 修改值 % mod
45         //就记住,乘上 k 的逆元相当于除 k 就完事了
46         num[pos[x]] = num[pos[x]] * ksm(a[x], mod - 2) % mod * y % mod;
47         ans = (ans + num[pos[x]]) % mod;
48         a[x] = y;
49         cout << ans << endl;
50     }
51     return 0;
52 }

A-小沙的炉石_2022牛客寒假算法基础集训营2 (nowcoder.com)

思路:

想到了伤害可以通过调整牌的顺序改变,应该是一个区间,但是没想到是二分攻击次数

不过复杂度最低的解法其实也不是二分,直接判断就得了

使用所有攻击牌最低伤害是:+sp,atk,+sp,atk,...

这样的伤害是1 + 3 + 5 + ... + min(n, m)  = min(n, m) ** 2是个奇数等差数列

最大伤害是:+sp,+sp,... ,atk,atk,atk...

这样伤害是 (m + 1) + (m + 2) + ... + (m + n) = (2 * m + n + 1) * n / 2

代码:

 1 n,m=map(int,input().split())
 2 a=(min(n,m)+1)**2-1;
 3 b=min(n,m+1)
 4 c=(2*m+1+b)*b//2
 5 t=int(input())
 6 while t:
 7     t-=1
 8     x=int(input())
 9     if x==a or x>c:
10         print("NO")
11     else:
12         print("YES")

I-小沙的构造_2022牛客寒假算法基础集训营2 (nowcoder.com)

思路:看似字符串,其实模拟题

把字符分为两类:第一类自身对称,见代码 s1,第二类相互对称,见 s2 和 s3

然后贪心的模拟就ok了

代码:

 1 #include
 2 using namespace std;
 3 int n, m;
 4 char ans[1000005];
 5 string s1 = "\"!'*+-.08:=^_WTYUIOAHXVM|";
 6 string s2 = "<\\[{(";
 7 string s3 = ">/]})";
 8 int main() {
 9     cin >> n >> m;
10     if (m > n || m > 35) {
11         cout << -1 << endl;
12         return 0;
13     }
14     int l = 1, r = n;
15     int p = 0, q = 0;
16     while (m > 2 || (m == 2 && l + 1 == r)) {
17         cout << "p: " << p << " ans: " << ans + 1 << endl;
18         if (p >= 5) {
19             break;
20         }
21         ans[l++] = s2[p];
22         ans[r--] = s3[p];
23         m -= 2;
24         p++;
25     }
26     while (l <= r) {
27         if (m == 0) {
28             ans[l++] = ans[r--] = s1[q - 1];
29         }
30         else {
31             ans[l++] = ans[r--] = s1[q++];
32             m--;
33         }
34     }
35     if (m) {
36         cout << "-1" << endl;
37         return 0;
38     }
39     else {
40         cout << ans + 1;
41     }
42     return 0;
43 }

E-智乃的数字积木(easy version)_2022牛客寒假算法基础集训营3 (nowcoder.com)

思路:

先按照题意同色相邻的为一段进行分段

贪心使每段都能做大得到的结果就是最大

对每一段进行sort还是 log2(1e5) * 1e5

注意到 k 的范围只有10,算一下复杂度发现是10 * log2(1e5) * 1e5,原来又是个模拟题

写个 modify 函数调用 k 次就可以了

代码:

 1 #include 
 2 using namespace std;
 3 const int p = 1e9 + 7, N = 1e6 + 10;;
 4 int n, m, k, c[N];
 5 char s[N];
 6 int modify(){
 7     for (int i = 1; i <= n;) {
 8         int j = i;
 9         while (j <= n && c[j] == c[i]) j++;
10         sort(s + i, s + j);
11         reverse(s + i, s + j);
12         i = j;
13     }
14     int res = 0;
15     for (int i = 1; i <= n; i++) {
16         res = (1ll * res * 10 + s[i] - '0') % p;
17     }
18     return res;
19 }
20 int main(){
21     ios::sync_with_stdio(false); cin.tie(0), cout.tie(0);
22     cin >> n >> m >> k >> s + 1;
23     for (int i = 1; i <= n; i++) cin >> c[i];
24     cout << modify() << '\n';
25     //cout<
26     while (k--) {
27         int x, y; cin >> x >> y;
28         for (int i = 1; i <= n; i++) if (c[i] == x) c[i] = y;
29         cout << modify() << '\n';
30     }
31 }

I-智乃的密码_2022牛客寒假算法基础集训营3 (nowcoder.com)

思路:

尺取还是比较好想的,只是我的实现能力有点狗屎吧

下面复制一段大佬的代码,偶尔复习一下能学到东西

代码:(是个压行大佬的,我这辈子都写不出这种)

 1 #include
 2 #define int long long
 3 #define endl '\n'
 4 using namespace std;
 5 int judge(char c) {
 6     if (isupper(c)) return 1;
 7     if (islower(c)) return 2;
 8     if (isdigit(c)) return 3;
 9     return 4;
10 }
11 int a[5], ans;
12 void answer() { cout << ans; exit(0); }
13 int nm() { int res = 0; for (int i = 1; i <= 4; ++i) if (a[i]) res++; return res; }
14 signed main() {
15     int n, l = 1, L, R, r = 0, len = 0; string s;
16     cin >> n >> L >> R >> s; s = " " + s;
17     while (1) {
18         while (r - l + 1 < L || nm() < 3) {
19             r++; if (r > n) answer(); a[judge(s[r])]++;
20         }
21         while (r - l + 1 >= L && nm() >= 3 && l <= r) {
22             ans += max(min(n - r + 1, l - r + R), 0ll);
23             a[judge(s[l])]--; l++;
24         }
25     }
26     return 0;
27 }

L-智乃的数据库_2022牛客寒假算法基础集训营3 (nowcoder.com)

思路:

模拟题,待补

代码:

待补

I-爆炸的符卡洋洋洒洒_2022牛客寒假算法基础集训营4 (nowcoder.com)

思路:

稍作改动的01背包(给涛涛写)

注意取模的操作就行

代码:

 1 #include
 2 #define int long long
 3 using namespace std;
 4 const int N = 1100;
 5 int a[N], b[N], dp[N][N];
 6 
 7 signed main() {
 8     ios::sync_with_stdio(false); cin.tie(0), cout.tie(0);
 9     int n, k; cin >> n >> k;
10     for (int i = 1; i <= n; i++)  cin >> a[i] >> b[i];
11     memset(dp, -0x3f, sizeof dp);
12     dp[0][0] = 0;
13     for (int i=1;i <= n; i++)
14         for (int j = 0; j < k; j++)
15         dp[i][j] = max(dp[i - 1][j], dp[i - 1][(j - a[i] % k + k) % k] + b[i]);
16     if (dp[n][0] > 0) cout << dp[n][0];
17     else cout << -1;
18     return 0;
19 }

J-区间合数的最小公倍数_2022牛客寒假算法基础集训营4 (nowcoder.com)

思路:

假设把区间内每个合数都分解出来,比如说 {4, 6, 8, 9} 中,那么因子再某个数中出现次数最高有 {2: 3, 3:2}

则上述合数的最小公倍数则为 2**3 * 3**2

数据量只有30000,那我肯定用py啊

代码:

 1 cnt = 0
 2 mod = int(1e9 + 7)
 3 n = 30001
 4 #质数是0,合数是1
 5 st = [0 for i in range(30005)]
 6 prime = [0 for i in range(30005)]
 7 #指数筛
 8 for i in range(2, n+1):
 9     if not st[i]:
10         prime[cnt] = i
11         cnt += 1
12     for j in range(n):
13         if prime[j] > n / i: break
14         st[prime[j] * i] = 1
15         if i%prime[j] == 0: break
16 #模拟
17 l, r = map(int, input().split())
18 dic = {}
19 for i in range(l, r + 1):
20     if st[i]:
21         tmp = i
22         for j in prime:
23             if tmp == 1: break
24             tmp_cnt = 0
25             while tmp % j == 0 and tmp != 1:
26                 tmp /= j
27                 tmp_cnt += 1
28             if j not in dic or dic[j] < tmp_cnt:
29                 dic[j] = tmp_cnt
30 #累计答案
31 ans = 1
32 for i in dic:
33     ans = ans * (i**dic[i] % mod) % mod
34 if ans == 1:
35     print(-1)
36 else:
37     print(ans)

D-雪色光晕_2022牛客寒假算法基础集训营4 (nowcoder.com)

思路:

计算几何,点到射线的最短距离即是答案

下面是py题解(人生苦短,我用py),返校去实验室机子找找lmgg的板子再用c++补(挖坑)

题解:

 1 n=int(input())
 2 s = input()
 3 s=s.split(' ')
 4 x0=int(s[0])
 5 y_0=int(s[1])
 6 X=int(s[2])
 7 Y=int(s[3])
 8 ans=1e18
 9 for i in range(n):
10     s=input().split(' ')
11     x=int(s[0])
12     y=int(s[1])
13     A=x*x+y*y
14     B=2*(x*(x0-X)+y*(y_0-Y))
15     C=(x0-X)*(x0-X)+(y_0-Y)*(y_0-Y)
16     
17     if -B>=0and-B<=2*A:
18         ans=min(ans,(4*A*C-B*B)/(4*A))
19     else:
20         ans=min(ans,min(A+B+C,C))
21     x0+=x;
22     y_0+=y;
23 print(pow(ans,0.5))

D-数位小孩_2022牛客寒假算法基础集训营5 (nowcoder.com)

思路:

众所周知DP是一种暴力算法,既然是暴力,那DFS说不定也可以

相邻两数相加为质数,也就18以内的质数,数出来

记录 last 和当前位的值,如果不是质数就break,大剪枝

然后算一下复杂度,看似10**10,其实两个数的和为0 - 18, 一共19种,而只有7种是符合情况的质数

那么不正确且粗略的估算下,复杂度不会过分偏离(10 * 7 / 19)** 10  ≈ 4 ** 10 = 2 ** 20 ≈ 1e6

因此可以DFS。

但是我写不出优美的DFS,贴上某佬的代码以供学习

代码:

 1 #include 
 2 using namespace std;
 3 int a[20];
 4 long long l, r;
 5 int ans = 0;
 6 void dfs(long long x, int last, int flag) {
 7     if (x > r) return;
 8     if (x >= l && flag == 1) ans++;
 9     for (int i = 0; i <= 9; i++) {
10         if (a[i + last]) dfs(x * 10 + i, i, flag || i == 1);
11     }
12 }
13 
14 int main() {
15     cin >> l >> r;
16     a[2] = a[3] = a[5] = a[7] = a[11] = a[13] = a[17] = 1;
17     for (long long i = 1; i <= 9; i++) {
18         dfs(i, i, i == 1);
19     }
20     cout << ans << endl;
21 }

2022牛客寒假算法基础集训营6_ACM/NOI/CSP/CCPC/ICPC算法编程高难度练习赛_牛客竞赛OJ (nowcoder.com)

思路:

好题,数学题,一开始思路被题目描述带偏了,搁那推公式

后面看题解发现都是暴力,算一下复杂度能过,暴力能过为什么要推公式呢?

1、首先注意到 a 的值仅有0,1,2

如果累乘中出现了0,那么该项为0;

如果累乘中出现了1,那么该项不变;

如果累乘中出现了2,那么该项 * 2;

显然,答案应该是累加了一堆 2 的幂,先把 p2[maxn]算出来备用;

并且,统计0,1,2的个数,存入a[3]中

2、当某项可以产生贡献时,该项是个n次多项式,其中k项为1,则有n-k项为2

从a[1]中选择k项,从a[2]中选择n - k项,组合数经典问题,因此提前求出逆元inv[maxn]备用

代码:

 1 #include
 2 #define int long long
 3 using namespace std;
 4 const int N = 1e7 + 10, mod = 998244353;
 5 
 6 int ksm(int a, int b){
 7     int ans = 1;
 8     while (b)    {
 9         if (b & 1)ans = ans * a % mod;
10         b >>= 1;
11         a = a * a % mod;
12     }
13     return ans;
14 }
15 
16 int f[N], inv[N], p2[N], a[5];
17 
18 signed main(){
19     int n, k; cin >> n >> k;
20     for (int i = 1; i <= n; i++){
21         int x; cin >> x;
22         a[x]++;
23     }
24     f[0] = inv[0] = p2[0] = 1;
25     for (int i = 1; i <= max(a[1], a[2]); i++){
26         p2[i] = p2[i - 1] * 2 % mod;
27         f[i] = f[i - 1] * i % mod;
28         inv[i] = inv[i - 1] * ksm(i, mod - 2) % mod;
29     }
30     int ans = 0;
31     for (int i = 0; i <= min({ k,a[2] }); i++){
32         int y = i, x = a[2];
33         int z = ((f[x] * inv[y] % mod) * inv[x - y] % mod) * p2[i] % mod;
34         x = a[1], y = k - i;
35         ans = ans + ((z * (f[x] * inv[y] % mod) % mod) * inv[x - y] % mod) % mod;
36         ans %= mod;
37     }
38     cout << ans << endl;
39 }

B-价值序列_2022牛客寒假算法基础集训营6 (nowcoder.com)

思路:

单调子串可以删掉中间的数(只保留波峰和波谷)不改变贡献

其余数可删可不删,也就是01的情况

找出所有可以删掉的数的数量 k,然后求 pow(2, k)即可

代码:

 1 #include 
 2 #define int long long
 3 using namespace std;
 4 const int maxn = 2e5 + 5;
 5 const int mod = 998244353;
 6 int arr[maxn], p2[maxn];
 7 
 8 signed main() {
 9     ios::sync_with_stdio(false); cin.tie(0), cout.tie(0);
10     p2[0] = 1;
11     for (int i = 1; i <= 100000; i++) p2[i] = p2[i - 1] * 2 % mod;
12     int t; cin >> t; while (t--) {
13         int n, res = 1; cin >> n;
14         for (int i = 1; i <= n; i++) cin >> arr[i];
15         for (int i = 1; i <= n; i++) {
16             int j = i;
17             while (j < n && arr[j + 1] == arr[i]) j++; //值不改变
18             if (i - 1 >= 1 && j + 1 <= n && //范围内
19                 (arr[i - 1] < arr[i] && arr[j + 1] > arr[i] || //单调上升 
20                 arr[i - 1] > arr[i] && arr[j + 1] < arr[i])) { //单调下降
21                 res = res * p2[2, j - i + 1] % mod;
22             }
23             else {
24                 res = res * (p2[2, j - i + 1] + mod - 1) % mod;
25             }
26             i = j;
27         }
28         cout << res << endl;
29     }
30     return 0;
31 }

D-删除子序列_2022牛客寒假算法基础集训营6 (nowcoder.com)

思路:

从左往右枚举当前下标的状态

贪心的想,删字符肯定是尽量靠左,给右边删除其他的留空间

动态规划的想,如果当前位置有机会为答案产生贡献,其左边一定可以组成前缀串

用一个一维数组记录被贡献串某下标可以组成的前缀串数量

由于没有重复字母,降低了难度,只需要26 * 1e6就能过

代码:

 1 #include 
 2 //#define int long long
 3 using namespace std;
 4 const int maxn = 2e5 + 5;
 5 int p[30];
 6 
 7 signed main() {
 8     ios::sync_with_stdio(false); cin.tie(0), cout.tie(0);
 9     int t; cin >> t; while (t--) {
10         memset(p, 0, sizeof p);
11         int n, m; cin >> n >> m;
12         string s1, s2; cin >> s1 >> s2;
13         for (int i = 0; s1[i]; i++) {
14             for (int j = 0; s2[j]; j++) {
15                 if (s1[i] == s2[j]) { // 有机会产生贡献
16                     if (j == 0) { // 作为开头或是延长左边字串的贡献
17                         p[j]++;
18                     }
19                     else if (p[j - 1]) { // 道路不断向前延伸(我在干什么啊
20                         p[j]++, p[j - 1]--;
21                     }
22                 }
23             }
24         }
25         cout << p[m - 1] << '\n';
26     }
27     return 0;
28 }

Problem - D - Codeforces

题意:

在给定的数组中,选择某相邻两数间的位置进行操作

操作:任意选择一个数,在任意位置插入两次,使串最终变成多组对称串

输出:

第一行:操作次数 x;

接下来 x 行:操作位置 p 和插入数 n

思路:

我们想象一个隔板,左边是当前拥有的,右边是还未进行操作的

用桶把左边的数装起来,对于某个数字 x,如果他的数量是1,那么无论如何进行操作,由于每次增加偶数个,因此不可能消掉 x

现在隔板向右移动一格,将一个数字加入了左边

如果加入数字后,这个桶里存在两个相同的数,那么肯定可以消除:凑回文串

那么我们可以通过一直进行凑回文的操作,保证左边的桶里都只有0或1个数(出现2时消除变成0)来构造

赛时想到了,但是我的代码水平,那就是一坨*

代码:

抄某个大佬的,有空照着模一模

 1 #include
 2 #define int long long
 3 #define PII pair
 4 using namespace std;
 5 
 6 const int N = 505;
 7 int a[N],tmp[N],n;
 8 map<int,int> mp;
 9 vector<int> v;
10 vector ans;
11 void solve(){
12     cin>>n;
13     for(int i=1;i<=n;i++)
14         cin>>a[i];
15     mp.clear();
16     for(int i=1;i<=n;i++) 
17         mp[a[i]]++;
18     bool flag = 1;
19     for(PII p:mp){
20         if(p.second%2==1){
21             flag=0;
22             break;
23         }
24     }
25     if(!flag){
26         cout<<"-1\n";
27         return ;
28     }
29     
30     v.clear();
31     ans.clear();
32     int now=1,last=0;
33     while(now<=n){
34         int index=-1;
35         for(int i=now+1;i<=n;i++){
36             if(a[i]==a[now]){
37                 index=i;
38                 break;
39             }
40         }
41         v.push_back((index-now)*2);//该子段配置完毕后的长度贡献
42         for(int i=now+1;i)
43             ans.push_back({last+index-now+i-now,a[i]});
44         
45         for(int i=now+1;i)
46             tmp[i]=a[index-(i-now)];
47         for(int i=now+2;i<=index;i++)
48             a[i]=tmp[i-1];
49         //以上操作为进行数组内的子段倒置
50             
51         last+=(index-now)*2;//最后一组配对完后的下标 
52         now+=2;//下一个待构造
53     }
54     cout<endl;
55     for(auto p:ans)
56         cout<" "<endl;
57     cout<endl;
58     for(auto val:v)
59         cout<" ";
60     cout<<endl;
61 }
62 
63 signed main(){
64     int t;
65     cin>>t;
66     while(t--){
67         solve();
68     }
69     return 0;
70 }

相关