2021年广东工业大学第十五届文远知行杯程序设计竞赛(同步赛)详解
A M形字符串
https://ac.nowcoder.com/acm/contest/13504/A
传送门
A M形字符
题意:
M形字符串指的是由两个相同的回文串拼接而成
给你一个串S,问有多少个前缀是M形字符串
思路:
M形是有两个相同的回文串构成的,所以这个M形串本身就是回文串,我们只需要判断一个串是回文串的同时,他的一半也是回文串即可
那如何判是不是回文串呢,这里我们使用哈希进行判断
如果一个串的正序哈希值等于其逆序哈希,则说明这个串是回文串
哈希的计算方法:
pre[i] = pre[i - 1] * seed + s[i] - 'a' + 1;
对于中间取的一段的哈希值,我们可以利用类似于差分的方法
pre[r] - pre[l - 1] * base[r - l + 1];
乘base[r - l + 1]是为了将其化为“同阶”
AC码:
#include <cstdio> #include <cstring> #include <string> #include <cmath> #include <iostream> #include <algorithm> #include <vector> #include <stack> #include <queue> #include <stdlib.h> #include <sstream> #include <map> #include <set> using namespace std; #define inf 0x3f3f3f3f #define MAX 1500000 + 7 #define endl '\n' #define seed 1331 const int mod = 1e9 + 7; #define io ios::sync_with_stdio(false); cin.tie(0); cout.tie(0) #define mem(a,b) memset((a),(b),sizeof(a)) //#define mod 571373; typedef long long ll ; //不开longlong见祖宗! inline int IntRead(){char ch = getchar();int s = 0, w = 1;while(ch < '0' || ch > '9'){if(ch == '-') w = -1;ch = getchar();}while(ch >= '0' && ch <= '9'){s = s * 10 + ch - '0';ch = getchar();}return s * w;} int pre[MAX], back[MAX], base[MAX]; string s; ll len; int idx(char x){ return (int)(x - 'a' + 1); } int getpre(int l, int r){//获得区间[l, r]的顺序哈希值 return pre[r] - pre[l - 1] * base[r - l + 1]; } int getback(int l, int r){//获得区间[l, r]的逆序哈希值 return back[r] - back[l - 1] * base[r - l + 1]; } bool judge(int i, int j){//判断正逆序的哈希是否相同 return getpre(i, j) == getback(len - j + 1, len - i + 1); } int half(int x){ return (x + 1) / 2; } int main(){ cin>>s; s = " " + s; len = s.size(); base[0] = 1; for(int i = 1; i <= len; ++i){ base[i] = base[i - 1] * seed; pre[i] = (pre[i - 1] * seed + idx(s[i])); back[i] = (back[i - 1] * seed + idx(s[len - i + 1])); } int ans = 0; for(int i = 1; i <= len; ++i){ //如果这个串是回文串,且他的一半也是回文串,就更新答案 if(judge(1, i) && judge(1, half(i)))ans++; } cout<<ans<<endl; return 0; }
在求正逆序哈希的时候,我将base[r - l + 1]换成pow(seed, r - l + 1)再理论方法来说是没有问题的,调了好长时间这个数还是对不上(╥﹏╥)
B找山坡
题意:
母牛哥在电脑面前坐久了,想站起来看看窗外的小山坡,于是就想出了这个问题:给定一个大小为n的数组a,序号从1开始, 计算:
max{ R - L | 1 <= L <= R <= n, a[L] == a[R], 对于所有i (L <= i <= R), 满足a[i] >= a[L] }. 也就是找到两个坐标,这两个坐标的值相等,并且他们之间的值都大于等于这两个坐标上的值. 这两个坐标相减最大能是多少.
思路:
可以用线段数来做,也可以用单调栈来做
先说结论:
栈存下标
如果tr[i] 小于 栈顶对应的元素,就将栈顶元素扔出去(这个时候我们可以知道,被扔出去的元素肯定是在tr[i]之前的,他的值大于tr[i],就不符合我们要求的两边的值大于内部的值,也就不会对结果产生贡献)
如果tr[i] 等于栈顶对应的元素,就更新答案
其他情况就把tr[i]对应的下标塞进去
注意如果要取栈顶一定要判栈是否为空
#include <cstdio> #include <cstring> #include <string> #include <cmath> #include <iostream> #include <algorithm> #include <vector> #include <stack> #include <queue> #include <stdlib.h> #include <sstream> #include <map> #include <set> using namespace std; #define inf 0x3f3f3f3f #define MAX 1000000 + 50 #define endl '\n' #define seed 1331 #define io ios::sync_with_stdio(false); cin.tie(0); cout.tie(0) #define mem(a,b) memset((a),(b),sizeof(a)) //const ll mod = 1e9 + 7; //#define mod 571373; //#define mod 1000000007 typedef long long ll ; //不开longlong见祖宗! inline int IntRead(){char ch = getchar();int s = 0, w = 1;while(ch < '0' || ch > '9'){if(ch == '-') w = -1;ch = getchar();}while(ch >= '0' && ch <= '9'){s = s * 10 + ch - '0';ch = getchar();}return s * w;} int n, x, ans; int tr[MAX]; stack<int>st; int main(){ cin>>n; for(int i = 1; i <= n; ++i)cin>>tr[i]; for(int i = 1; i <= n; ++i){ while (!st.empty() && tr[i] < tr[st.top()]) { st.pop(); } if(!st.empty() && tr[i] == tr[st.top()])ans = max(ans, i - st.top()); else st.push(i); } cout<<ans<<endl; return 0; }
C 涂墙
题意:
母牛哥有一桶油漆,把它用完可以给n平方米的墙涂上颜色. 母牛哥想要在墙上涂5个正方形(这些正方形的边长都是整数,单位是米),并且刚好把油漆用完. 母牛哥能做到吗?
思路1:找规律
0 1 2 3 4 6 7 9 10 12 15 18 33 这些数无法由五个平方数构成,其他都可以滴
#include <cstdio> #include <cstring> #include <string> #include <cmath> #include <iostream> #include <algorithm> #include <vector> #include <stack> #include <queue> #include <stdlib.h> #include <sstream> #include <map> #include <set> using namespace std; #define inf 0x3f3f3f3f #define MAX 1000 + 7 #define endl '\n' #define io ios::sync_with_stdio(false); cin.tie(0); cout.tie(0) #define mem(a,b) memset((a),(b),sizeof(a)) //#define mod 571373; typedef long long ll ; //不开longlong见祖宗! inline int IntRead(){char ch = getchar();int s = 0, w = 1;while(ch < '0' || ch > '9'){if(ch == '-') w = -1;ch = getchar();}while(ch >= '0' && ch <= '9'){s = s * 10 + ch - '0';ch = getchar();}return s * w;} int t, n, m, ans; int tr[1005]; int main(){ cin>>t; while (t--) { cin>>n; if(n == 1|| n == 0 || n == 2 || n == 3 || n == 4 || n == 6 || n == 7 || n == 9 || n == 10 || n == 12 || n == 15 || n == 18 || n == 33)cout<<"NO\n"; else cout<<"YES\n"; } return 0; } //dfs暴力 //void dfs(int x, int num){ // if(x == n && num == 5){ // ans = 1; // return; // } // else if(num == 5 && x != n)return; // for(int i = 1; i <= 30; ++i){ // dfs(x + tr[i], num + 1); // } // //} // //int main(){ // t = IntRead(); // int len = 0; // for(int i = 1;i <= 1000; ++i)tr[++len] = i * i; //// n = 55; //// dfs(0, 0); //// cout<<ans<<endl; // for(int i = 1; i <= t; ++i){ // n = i; // ans = 0; // dfs(0, 0); // if(ans == 1)cout<<i<<' '<<"YES\n"; // else cout<<i<<' '<<"NO\n"; // } // return 0; // while (t--) { // n = IntRead(); // dfs(0, 0); // if(ans == 1)cout<<"YES\n"; // else cout<<"NO\n"; // } //}
思路2:优美的暴力
看了大佬们的代码才发现,大佬口中的暴力和我的暴力并不相同
就像人与人的悲欢并不相通,呜呜呜(╥﹏╥)
dfs的时候尽量从大的开始找,不要从小的一直找,因为你小的可能找了五个才发现不行,才开始回退,对于类似于1这种的,就会搜的特别多,而你找大的,可能一下子就超了,就开始换下一个,就不会太暴力,这就是这个题的暴力美学orz
#include <cstdio> #include <cstring> #include <string> #include <cmath> #include <iostream> #include <algorithm> #include <vector> #include <stack> #include <queue> #include <stdlib.h> #include <sstream> #include <map> #include <set> using namespace std; #define inf 0x3f3f3f3f #define MAX 200000 + 50 #define endl '\n' #define seed 1331 #define io ios::sync_with_stdio(false); cin.tie(0); cout.tie(0) #define mem(a,b) memset((a),(b),sizeof(a)) //const ll mod = 1e9 + 7; //#define mod 571373; //#define mod 1000000007 typedef long long ll ; //不开longlong见祖宗! inline int IntRead(){char ch = getchar();int s = 0, w = 1;while(ch < '0' || ch > '9'){if(ch == '-') w = -1;ch = getchar();}while(ch >= '0' && ch <= '9'){s = s * 10 + ch - '0';ch = getchar();}return s * w;} int t, n; bool dfs(int n, int k){//n代表数,k代表已经凑几个数了 if(n == 0 && k == 0)return true; if(n <= 0 || k == 0)return false; else{ for(int i = sqrt(n); i; --i){//从大数开始找 if(dfs(n - i * i, k - 1)) return true; } } return false; } int main(){ cin>>t; while (t--) { cin>>n; if(dfs(n, 5))cout<<"YES\n"; else cout<<"NO\n"; } return 0; }
D动态序列
题目描述:
给出n(1 <= n <= 100000)个整数 ai(1 <= ai <= 1000000000)的序列,有q(1 <= q <= 100000)个询问,设序列长度为len,序号从1开始,每个询问有如下操作:
1 b:序列中所有数乘以整数b(1 <= b <= 1000000000)
2 b:序列中所有数增加整数b(1 <= b <= 1000000000)
3 b:在序列头部添加一个整数b(1 <= b <= 1000000000)
4 b:在序列尾部添加一个整数b(1 <= b <= 1000000000)
5 p:输出序列的第p(1 <= p <= len)个数,并将结果对1000000007取模
题目保证所有操作都是合法的!
思路:
对于每一个需要输出的数,都是经过多次乘法和加法的,我们就可以写作 的形式,所以我们可以只需要更新记录mul 和add即可(mul为乘的数,初始为1,add为加的数,初始为0)
对于操作1 : 我们取其中一个数tr[i]来看
还是 一个数乘以 a 加上 一个数的形式我们需要更新mul 和 add 的值
- 对于操作2 同理,我们只需要跟新add的值即可
要注意,我们输出的时候是输出,所以对于新加入的元素p,他想要进入这个数组,就必须转换成的形式,也即是将 x 加入数组,此时
因为这题是需要取模的,所以就不能使用除法,得使用逆元来求x
对于本题,模是1e7是质数,所以我们可以使出秘技:费马小定理来求逆元,也就是 x = (p - add) * quick_pow(mul, mod - 2)
因为3是向头部加元素,数据最多1e5,所以就把1e5 + 1 当作是数组的头,1e5 + n 当作是数组的尾,注意要开大点数组,取模的时候你就遇到计算就取一步模
记得初始化mul 和 add
#include <cstdio> #include <cstring> #include <string> #include <cmath> #include <iostream> #include <algorithm> #include <vector> #include <stack> #include <queue> #include <stdlib.h> #include <sstream> #include <map> #include <set> using namespace std; #define inf 0x3f3f3f3f #define MAX 300000 + 50 #define endl '\n' #define seed 1331 #define io ios::sync_with_stdio(false); cin.tie(0); cout.tie(0) #define mem(a,b) memset((a),(b),sizeof(a)) //#define mod 571373; //#define mod 1000000007 typedef long long ll ; const ll mod = 1e9 + 7; //不开longlong见祖宗! inline int IntRead(){char ch = getchar();int s = 0, w = 1;while(ch < '0' || ch > '9'){if(ch == '-') w = -1;ch = getchar();}while(ch >= '0' && ch <= '9'){s = s * 10 + ch - '0';ch = getchar();}return s * w;} ll n, m, a, b, sta, en, mul, add, x; ll tr[MAX]; ll quick_pow(ll a, ll b){//快速幂 ll ans = 1; a %= mod; while (b) { if(b & 1){ ans = (ans * a) % mod; } a = (a * a) % mod; b >>= 1; } return ans; } void solve(ll a, ll b){ switch (a) { case 1: mul = (mul * b) % mod; add = (add * b) % mod; break; case 2: add = (add + b) % mod; break; case 3: x = (((b - add + mod) % mod) * (quick_pow(mul, mod - 2) % mod)) % mod; tr[--sta] = x; break; case 4: x = (((b - add + mod) % mod) * (quick_pow(mul, mod - 2) % mod)) % mod; tr[++en] = x; break; case 5: cout<<((mul * tr[sta + b - 1]) % mod + add % mod) % mod<<endl; break; } } int main(){ cin>>n>>m; sta = 1e5 + 1;en = 1e5 + n; for(ll i = sta; i <= en; ++i)cin>>tr[i]; mul = 1;add = 0; while (m--) { cin>>a>>b; solve(a, b); } return 0; }
E 捡贝壳
题意:
给你n个贝壳,每个贝壳有不同的质量,进行q次询问,询问的是区间[l, r]中的贝壳质量是x的倍数的有多少个
思路1:
一开始最暴力的方法是用个二维数组存因子的前缀和,然后就可以做差直接查询,但是空间不允许,就得放弃
所以,我们就可以采取分块的方法,将n个贝壳进行分块,每一块的大小不超过,然后像线段树求区间和一样一段段的求即可
分块操作:我这里选择的是输入的时候就直接分块,你也可以像其他人那样写个函数,等输入完了再分
分块的时候最好是从0这个数开始,比如0 1 2 3 4 5 6 7 8,块是3,前三个数除以3刚好是0,但如果你从1开始计数,那就不方便了
for(int i = 1; i <= n; ++i){ ar[i] = IntRead();//快读 for(int j = 1; j * j <= ar[i]; ++j){//质因数分解 if(ar[i] % j == 0){//判断能否整除j tr[(i - 1) / cnt + 1][j]++;//能整出就对该块的j进行++操作,这里从1输入,所以对i-1进行判块,且我这里cnt从1开始是为了后面的操作做准备,一会讲 if(ar[i] / j != j)tr[(i - 1) / cnt + 1][ar[i] / j]++;//对于j这个因子,肯定有ar[i] / j这另一个因子,但需要判断这两个因子是不是相同的,也就是ar[i]找个数可能是平方数,那他开平方的数就只需要计算一遍 } } }
- 求解:
分两种情况:
- l 和 r 在一个块里面,这时候就直接打个暴力,从 l 循环到 r,最多最多也就跑300多,因为分的块最大也就是
- l 和 r 不在一个块里面,这个时候就分三部分进行运算
在这里插入图片描述
见图:由l 到 a块的结束 + a块与b块中间的块 + b块的开始到 r
int getnum(int l, int r, int x){ //判断l和r在哪个块里面,注意这里要让块的值是大于等于1的,所以就得加一,因为如果第一个块等于0,那0乘以块的大小还是0,我们就无法得到一个块的尾部 int a = l / cnt + 1, b = r / cnt + 1; int sum = 0; if(a == b){//判断在同一个块内 for(int i = l; i <= r; ++i){ if(ar[i] % x == 0)sum++;//直接莽暴力 } return sum; } //第一部分: 从l 开始,到 a块的结束的位置打暴力 for(int i = l; i <= a * cnt; ++i){ if(ar[i] % x == 0)sum++; } //第三部分 从 b块的开始到 r 打暴力 for(int i = (b - 1) * cnt + 1; i <= r; ++i){ if(ar[i] % x == 0)sum++; } //第三部分,计算中间到块的值 for(int i = a + 1; i < b; ++i)sum += tr[i][x]; return sum; }
献上AC码:
#include <cstdio> #include <cstring> #include <string> #include <cmath> #include <iostream> #include <algorithm> #include <vector> #include <stack> #include <queue> #include <stdlib.h> #include <sstream> #include <map> #include <set> using namespace std; #define inf 0x3f3f3f3f #define MAX 100050 + 7 #define endl '\n' #define io ios::sync_with_stdio(false); cin.tie(0); cout.tie(0) #define mem(a,b) memset((a),(b),sizeof(a)) //#define mod 571373; typedef long long ll ; //不开longlong见祖宗! // inline int IntRead(){char ch = getchar();int s = 0, w = 1;while(ch < '0' || ch > '9'){if(ch == '-') w = -1;ch = getchar();}while(ch >= '0' && ch <= '9'){s = s * 10 + ch - '0';ch = getchar();}return s * w;} int n, m, x, y, cnt, z; int tr[320][MAX], ar[MAX]; int getnum(int l, int r, int x){ int a = l / cnt + 1, b = r / cnt + 1; int sum = 0; if(a == b){ for(int i = l; i <= r; ++i){ if(ar[i] % x == 0)sum++; } return sum; } for(int i = l; i <= a * cnt; ++i){ if(ar[i] % x == 0)sum++; } for(int i = (b - 1) * cnt + 1; i <= r; ++i){ if(ar[i] % x == 0)sum++; } for(int i = a + 1; i < b; ++i)sum += tr[i][x]; return sum; } int main(){ n = IntRead();m = IntRead(); cnt = sqrt(n); for(int i = 1; i <= n; ++i){ ar[i] = IntRead(); for(int j = 1; j * j <= ar[i]; ++j){ if(ar[i] % j == 0){ tr[(i - 1) / cnt + 1][j]++; if(ar[i] / j != j)tr[(i - 1) / cnt + 1][ar[i] / j]++; } } } while (m--) { x = IntRead(); y = IntRead();z = IntRead(); cout<<getnum(x, y, z)<<endl; } return 0; }
思路2: vector + 二分
对于这个题,数比较小,每个数都是小于1e5的,我们就可以采用枚举每个x的倍数,在给定区间去找
这里我们使用vector数组来存每个数的下标,便于二分查找,防T
#include <cstdio> #include <cstring> #include <string> #include <cmath> #include <iostream> #include <algorithm> #include <vector> #include <stack> #include <queue> #include <stdlib.h> #include <sstream> #include <map> #include <set> using namespace std; #define inf 0x3f3f3f3f #define MAX 100050 + 7 #define endl '\n' #define io ios::sync_with_stdio(false); cin.tie(0); cout.tie(0) #define mem(a,b) memset((a),(b),sizeof(a)) //#define mod 571373; typedef long long ll ; //不开longlong见祖宗! // inline int IntRead(){char ch = getchar();int s = 0, w = 1;while(ch < '0' || ch > '9'){if(ch == '-') w = -1;ch = getchar();}while(ch >= '0' && ch <= '9'){s = s * 10 + ch - '0';ch = getchar();}return s * w;} int n, m, l, r, x, a, ans; vector<int>tr[MAX]; int main(){ n = IntRead();m = IntRead(); for(int i = 1; i <= n; ++i){ x = IntRead(); tr[x].push_back(i);//把数为x的下标塞进去 } while (m--) { l = IntRead(); r = IntRead();x = IntRead(); ans = 0; for(int i = x; i <= MAX; i += x){//这里就相当于枚举每个x的倍数 ans += upper_bound(tr[i].begin(), tr[i].end(), r) - lower_bound(tr[i].begin(), tr[i].end(), l); //因为是找的给定区间的,我们就去二分,前一个二分是找大于r的第一个位置,后一个二分找的是大于等于 l 的第一个位置 } cout<<ans<<endl; } return 0; }
G分割
题意:
在一个二维平面,
有n条平行于y轴的直线, 他们的x坐标是x[i],
m条平行于x轴的直线y[i],他们的y坐标是y[i].
求出这些直线所有可能形成矩形的总面积对1000000007取模的值。
保证所有直线不完全重叠。
思路:
面积的总和 = 能构成的本质不同的矩形的面积和
我们就可以找出所有本质不同的长和宽进行相乘并求和即可
我们举个数找下规律:
n = 5 (2 - 1) + (3 - 1) + (4 -1) + (5 - 1) (3 - 2) + (4 - 2) + (5 - 2) (4 - 3) + (5 - 3) (5 - 4) = 1 * (-4) + 2 * (-2) + 3 *(0) + 4 * (2) + 5 * (4) 1 : 0 -4 2 : 1 -3 3 : 2 -2 4 : 3 -1 5 : 4 0
对于第i个元素他的贡献值就为:
对于y也是一样的
所以我们只需要先对x 和 y数组分别升序排序,然后对x循环一遍,记录不同的x的贡献值的和ans再对y循环一遍,记录不同的y的贡献值的和cnt最后输出cnt * ans 再取模即可!
#include <cstdio> #include <cstring> #include <string> #include <cmath> #include <iostream> #include <algorithm> #include <vector> #include <stack> #include <queue> #include <stdlib.h> #include <sstream> #include <map> #include <set> using namespace std; #define inf 0x3f3f3f3f #define MAX 100000 + 50 #define endl '\n' #define seed 1331 const int mod = 1000000007; #define io ios::sync_with_stdio(false); cin.tie(0); cout.tie(0) #define mem(a,b) memset((a),(b),sizeof(a)) //#define mod 571373; typedef long long ll ; //不开longlong见祖宗! inline int IntRead(){char ch = getchar();int s = 0, w = 1;while(ch < '0' || ch > '9'){if(ch == '-') w = -1;ch = getchar();}while(ch >= '0' && ch <= '9'){s = s * 10 + ch - '0';ch = getchar();}return s * w;} ll n, m; ll xx[MAX], yy[MAX]; ll ans, cnt; int main(){ io; cin>>n>>m; for(int i = 1; i <= n; ++i)cin>>xx[i]; for(int i = 1; i <= m; ++i)cin>>yy[i]; ans = 0;cnt = 0; sort(xx + 1, xx + 1 + n); sort(yy + 1, yy + 1 + m); for(int i = 1; i <= n; ++i){ ans += xx[i] * (i * 2 - n - 1); ans %= mod; } for(int j = 1; j <= m; ++j){ cnt += yy[j] * (j * 2 - m - 1); cnt %= mod; } cout<<(ans * cnt) % mod<<endl; return 0; }
I 母牛哥与子序列
题意:
众所周知,一个序列拥有许多非空子序列。
所谓子序列就是在原序列中任意删除 0 个或多个元素,然后保持剩下元素的顺序不变所形成的序列。非空子序列集意味着剩下的子序列不能为空。
比如对于序列[1, 2, 3],它的所有非空子序列为:[1, 2, 3],[1, 2],[1, 3],[2, 3],[1],[2],[3]。再比如序列 [1, 1],它的非空子序列有:[1, 1],[1] (删除了第一个 1),[1] (删除了第二个1) 。
现在母牛哥手里有一个长度为 n 的正整数序列,他现在要为这个序列的所有非空子序列打分。对于一个序列而言,它的评分标准为序列里所有数的乘积(只有一个数的序列,分数就是这个数)。
母牛哥想要知道所有分数的和,但由于结果太大了,所以你只要告诉母牛哥结果对 1000000007 取模即可
思路:
打表可以发现规律:
记得取模!
#include <cstdio> #include <cstring> #include <string> #include <cmath> #include <iostream> #include <algorithm> #include <vector> #include <stack> #include <queue> #include <stdlib.h> #include <sstream> #include <map> #include <set> using namespace std; #define inf 0x3f3f3f3f #define MAX 100050 + 7 #define endl '\n' #define io ios::sync_with_stdio(false); cin.tie(0); cout.tie(0) #define mem(a,b) memset((a),(b),sizeof(a)) //#define mod 571373; typedef long long ll ; //不开longlong见祖宗! // inline int IntRead(){char ch = getchar();int s = 0, w = 1;while(ch < '0' || ch > '9'){if(ch == '-') w = -1;ch = getchar();}while(ch >= '0' && ch <= '9'){s = s * 10 + ch - '0';ch = getchar();}return s * w;} ll n, x; ll ans = 1; int main(){ cin>>n; for(int i = 1; i <= n; ++i){ cin>>x; ans *= (x + 1); ans %= 1000000007; } cout<<(ans - 1 + 1000000007) % 1000000007 <<endl; return 0; }