高二&高一&初三模拟赛17 总结
2017-09-21 11:31
507 查看
蛋糕
题目描述
输入格式
一行两个整数 n 和 P, 意义如题面所示。输出格式
一行一个整数, 表示有多少种切法。输入样例
【样例一输入】6 1000000007
【样例二输入】
20 572541752
输出样例
【样例一输出】14
【样例二输出】
266161148
题解(卡特兰数+线性筛+快速幂)
题目看上去像个dp,设f[n]为n边形的答案,选一个三角形分割,将问题划分成两边,则f[n]=∑n−1k=2f[k]∗f[n−k+1]。其中f[2]=f[3]=1。这是一个n2的dp,好像无法优化,但是学过OI的我们都知道这就是一个卡特兰数。题目问的就是F[n−2] mod P的值。我们知道卡特兰数的计算公式为F[n]=Cn2n−Cn+12n=Cn2nn+1。这样如果算出结果是O(n)的,但是P不是质数,所以求不了逆元。如果强行分解质因数就是O(nn√)的了。线性筛也考虑过但害怕空间炸就没有向下想。其实怀疑数据范围是不是错了,为毛n那么大啊。考试时想到这里我就想不出来了(不可能考什么CRT吧,rt。。)。
其实线性筛是可以的,我们预先筛出1−2n内所有素数,并记住每个数的最小质因子,分解时不断除,这样将每个数分解质因数时的时间就不是n√的了,而变成了那个数的质因数数量,然而这样做,再加上快速幂都还是过不了,最后kekxy告诉了我一个更快的方法。
我们枚举每个质数,拿去筛掉所有数,求到所有数中有多少个这样的质因子,并记录在数组里。假设当前的素数是2,那么2的倍数的就有⌊n2⌋个,每个对2所在位置的贡献是1,是4的倍数的有⌊n4⌋个,对2所在的位置的贡献是2…这样我们每次枚举筛出的质数,然后将质数像2∗2∗2..这样的不断乘,每次O(1)求出贡献,假设当前的质数是i,那么多出的贡献就是⌊xi⌋∗v,v为1或−1代表相应的位置是加还是减。
这样的时间复杂度降下去了,由每个数质因子个数*总数变成了总质数个数*log级别的倍增计算。于是时间大概是O(cnt∗logN+N),cnt是质数数量,大约有N/ln(N)个。
这种方法其实很常见,快速求出一段区间的质因数个数,用线性筛。这种方法其实就是原来是先枚举i,然后一个个算,现在直接枚举质因数,然后算区间内有几个。很像求约数个数和,切换枚举次序,将相同的累积一起算,而不是离散地去求和。无奈我记忆力不好,这些套路总是记不住多半是废了。这个计算方法有些类似黎曼积分和勒贝格积分的差别。
代码
#include <cstdio> #include <cstdlib> #include <algorithm> #include <iostream> #include <cstring> #include <cmath> #define MAXN 20000010 using namespace std; typedef long long LL; LL P, ans = 1LL; int n, cnt, num[MAXN], prime[MAXN]; bool vis[MAXN]; void Da(){ for(int i = 2; i <= (n<<1); i++){ if(!vis[i]) prime[++cnt] = i; for(int j = 1; j <= cnt && i*prime[j] <= (n<<1); j++){ vis[i*prime[j]] = true; if(i % prime[j] == 0) break; } } } void add(int x, int v){ for(int i = 1; i <= cnt; i++) for(LL j = prime[i]; j <= x; j *= prime[i]) num[i] += x / j * v; } LL Pow(LL x, int y){ LL res = 1LL; while(y){ if(y & 1) res = res * x % P; x = x * x % P; y >>= 1; } return res; } int main(){ scanf("%d%lld", &n, &P); n -= 2; Da(); add(n<<1, 1); add(n+1, -1); add(n, -1); for(int i = 1; i <= cnt; i++) ans = ans * Pow(prime[i], num[i]) % P; printf("%lld", ans); return 0; }
解密
题目描述
输入格式
两行, 第一行为字符串 s1, 第二行为字符串 s2。输出格式
一行两个整数, 表示所有敌方可能传递的信息的最短长度和最长长度; 如果没有, 那么输出两个-1输入样例
【样例一输入】apple
pepperoni
【样例二输入】
testsetses
teeptes
【样例三输入】
bidhan
roy
输出样例
【样例一输出】2 2
【样例二输出】
3 3
【样例三输出】
1 -1
题解(kmp/后缀数组/SAM)
话说这题真是6,kmp正解不难想到,同时也是后缀数组的果题,大神们用SAM也是眨眨眼就切掉。还有,如果你什么都不会,直接上O(n3)暴力,都能拿到绝大部分分!然而考试时我TM还就真没去做这题,想都没去想。我一直在码第三题,想第一题和钓鱼,甚至好像忘了还有第二题的存在,连个暴力都没去动。不瞎BB了,直接讲两种方法(SAM老子不会)。kmp的话,就直接枚举s1的位置,以其为开头求一次Next,然后拿去和s1和s2匹配。一开始我想这样是不是就能知道枚举位置后的每个长度的前缀在s1和s2中的出现次数了呢?如果是的话不直接看看出现次数是不是都等于1就好了吗?答案是否定的。假如说有一个后缀aa,匹配aaa,长度为1的加1次,长度为2的加1次,长度为2的再加1次,没了。总共1加了1次,2加了2次。很明显,其实这样是算少了的。
但是题目有一个性质:只有出现1次的才能成为答案,而如果出现了,kmp一定会算到。于是我们只需看看是否出现了两次及以上就行了。那这样怎么做呢?如果一个串在另一个串中出现了至少两次的话呢,那它除了本身加了外,一定作为Next被加了,当然可能被当作Next的Next加了。举个栗子,如果aaa匹配成功了,而且是跳Next跳出来的,我们就可能要将a加1次,aa也加一次,Next那些往回跳啊跳都要加。但是我们只关心出现次数是否大于1,只需要往回加1个,因为前面的已经被加过了,如果出现次数大于1就至少是2了,大概是这个样子。我在BB些啥。所以每次就将k和Next[k]那里+1。这样就能保证时间n2了。如果每次往回跳到底,暴力也有挺多分吧。
后缀数组的话,就将s1,s2拼在一起,用特殊字符隔开,然后求sa和height。这题都不用用到二分+分块(也用不了),直接枚举某个后缀和匹配长度l,然后找与其的lcp>=l的后缀有几个是s1的,有几个是s2的就行了。我忘了height数组的性质,一开始不会实现,以为是n3,后来后缀数组大神KsCla告诉我这样是n2的,因为枚举后缀后,lcp向两边肯定不增,因为lcp是height的最小值,而height的最小值不增(废话),所以每次向上下扫,然后统计满足要求的s1,s2数量,一旦超过1就退出,所以最多向上和下走3步,走的步数是个常数。时间就是O(n2+nlogn)。
代码(kmp)
#include <cstdio> #include <cstdlib> #include <algorithm> #include <cstring> #include <iostream> #include <cmath> #define MAXN 5005 using namespace std; int times1[MAXN], times2[MAXN]; int Next[MAXN], len1, len2, ans1 = -1, ans2 = -1; char s1[MAXN], s2[MAXN]; void Kmp(int l){ Next[l] = Next[l+1] = l; int k = l; for(int i = l+2; i <= len1; i++){ while(k != l && s1[i-1] != s1[k]) k = Next[k]; if(s1[i-1] == s1[k]) k ++; Next[i] = k; } k = l; for(int i = 1; i <= len1; i++){ while(k != l && s1[i-1] != s1[k]) k = Next[k]; if(s1[i-1] == s1[k]) k ++; if(k){ times1[k-l] ++; times1[Next[k]-l] ++; } if(k+l == len1) k = Next[k]; } k = l; for(int i = 1; i <= len2; i++){ while(k != l && s2[i-1] != s1[k]) k = Next[k]; if(s2[i-1] == s1[k]) k ++; if(k){ times2[k-l] ++; times2[Next[k]-l] ++; } if(k+l == len1) k = Next[k]; } } int main(){ scanf("%s%s", &s1, &s2); len1 = strlen(s1); len2 = strlen(s2); for(int i = 0; i < len1; i++){ memset(times1, 0, sizeof(times1)); memset(times2, 0, sizeof(times2)); Kmp(i); for(int j = 1; j <= len1; j++) if(times1[j] == 1 && times2[j] == 1){ ans1 = max(ans1, j); ans2 = (ans2 == -1) ? j : min(ans2, j); } } printf("%d %d\n", ans2, ans1); return 0; }
代码(SA)
#include <cstdio> #include <cstdlib> #include <cstring> #include <iostream> #include <algorithm> #include <cmath> #define MAXN 10010 using namespace std; char s[MAXN], s1[MAXN], s2[MAXN]; int cnt[MAXN], tmp1[MAXN], tmp2[MAXN], sa[MAXN], height[MAXN]; int *x = tmp1, *y = tmp2, ans1 = -1, ans2 = -1; int n, m = 1000; bool Cmp(int *r, int a, int b, int l){ return r[a] == r && r[a + l] == r[b + l]; } void Da(){ int i, j, p; for(i = 0; i < m; i++) cnt[i] = 0; for(i = 0; i < n; i++) cnt[x[i] = s[i]] ++; for(i = 1; i < m; i++) cnt[i] += cnt[i-1]; for(i = n-1; i >= 0; i--) sa[--cnt[x[i]]] = i; for(j = 1, p = 1; p < n; j <<= 1, m = p){ for(p = 0, i = n-j; i < n; i++) y[p++] = i; for(i = 0; i < n; i++) if(sa[i] >= j) y[p++] = sa[i] - j; for(i = 0; i < m; i++) cnt[i] = 0; for(i = 0; i < n; i++) cnt[x[y[i]]] ++; for(i = 1; i < m; i++) cnt[i] += cnt[i-1]; for(i = n-1; i >= 0; i--) sa[--cnt[x[y[i]]]] = y[i]; for(i = 1, p = 1, swap(x, y), x[sa[0]] = 0; i < n; i++) x[sa[i]] = Cmp(y, sa[i], sa[i-1], j) ? p-1 : p++; } } void Calc_height(){ int i, j, k = 0; for(i = 0; i < n; i++){ if(x[i]){ for(k?k--:0, j = sa[x[i]-1]; s[i+k] == s[j+k]; k++); height[x[i]] = k; } else height[x[i]] = 0; } } int main(){ scanf("%s%s", &s1, &s2); int len1 = strlen(s1), len2 = strlen(s2); for(int i = 0; i < len1; i++) s[n++] = s1[i]; s[n++] = '$'; for(int i = 0; i < len2; i++) s[n++] = s2[i]; s[n++] = char(0); Da(); Calc_height(); for(int i = 0; i < n; i++){ for(int s = 1; s <= n-sa[i]; s++){ int Min = 1e6, cnt1 = 0, cnt2 = 0; if(sa[i] < len1) cnt1 ++; else cnt2 ++; for(int l = i-1; l+1 < n && l >= 0; l--){ Min = min(Min, height[l+1]); if(Min < s || cnt1 > 1 || cnt2 > 1) break; if(sa[l] < len1) cnt1 ++; else cnt2 ++; } Min = 1e6; for(int r = i+1; r < n; r++){ Min = min(Min, height[r]); if(Min < s || cnt1 > 1 || cnt2 > 1) break; if(sa[r] < len1) cnt1 ++; else cnt2 ++; } if(cnt1 == 1 && cnt2 == 1){ ans1 = max(ans1, s); ans2 = ans2 == -1 ? s : min(ans2, s); } } } printf("%d %d\n", ans2, ans1); return 0; }
漏洞
题目描述
输入格式
输出格式
输入样例
【样例一输入】5 5
38 43 4 12 70
1 1 3 4 8
2 2 4
1 4 5 0 8
1 2 5 8 7
2 1 5
【样例二输入】
5 5
25 36 39 40 899
1 1 3 2 7
2 1 2
1 3 5 9 1
1 4 4 0 9
2 1 5
输出样例
【样例一输出】103
207
【样例二输出】
111
1002
题解(线段树)
本题是[b]线段树的果题,我一看到题目就以为自己会了,结果爆零了(代码一堆漏洞)。线段树上每个节点开一个数组num[]记此区间内的每个数的权值和。假设某个区间有两个数124和42,那么对应的num[1]=100,num[2]=11,num[4]=11。然后我们build()出这个信息,然后顺便求和sum。
然后我们先看简单的query(),明显是询问一段区间的和。
然和看看update(),要求将一段区间的x改成y,一开始我想每一个点记一个二维bool数组change[x][y],代表将x改成y。然而这样空间很吃紧,时间也有100倍常数,那时naive的我以为已经没什么其他好害怕的了,然而这样合并标记,就是down()的时候,无法知道先后顺序,假如是将x改成y,y改成z,我先改那个呢?于是我就光荣爆0。我们可以开一个链表链一下,然而这样时间还是100倍,非常蠢。其实我们多维护的懒惰标记是可以使常数变为10的。
我们考虑记mark[]代表这个区间内的所有的i被改成了mark[i],这样标记是可以合并的,而且下面可以说明常数为10。不考虑down(),先处理update(),现在区间[L, R]又来了一个标记是将u改成v,我们要更新答案sum,num[],以及懒惰标记mark[]。一开始的所有的mark[i]=i,现在mark[i]=j。如果u=j的话,代表现在我们要合并mark[i]=j和mark[j]=v。于是我们直接将mark[i]=v,更新好了mark[]。由于之前已经将mark[i]=i和mark[i]=j合并,答案已经算过。于是此时的贡献与更新就是sum+=num[u]*(v-u),num[v]+=num[u],num[u]=0。
于是update()我们也搞定了,就是说我们处理好了一个点的先后标记的合并与答案的更新,而且常数是10。
换到down(),我们需要处理父亲的标记与儿子标记的合并与儿子答案的更新。注意这时我们已经处理好父亲标记的先后顺序了,就是说能合并的已经在update()的时候或上一次down()搞定了。举个栗子,如果父亲有标记1->3,3->5,明显是先3->5再1->3的,所以我们不能直接看儿子的3然后改成5,因为儿子之前可能因为父亲的标记1->3而使3多了,这样标记就会冲突。所以我们开一个临时数组now[]保存儿子的num[],将儿子的num[]清0。然后将要改的部分累记到儿子的num[]里去,个数转移时还是用now[],这样最后剩下的那些now[]就再累加回儿子的num[]里去,常数也是10,sum也是用now[]去更新。mark[]的话向update()时一样改,最后记得清掉父亲的标记。
至此我们处理好了所有部分,时间复杂度为O(10qlogN)。
总结:线段数的题目就是在合并标记与更新答案,还要知道线段树里记些啥,这些信息要满足区间加法,要能够直接维护,如果不能就要借助其他的信息来维护。
不行,太严肃了,还是讲讲其他的吧。我发现线段树码错了后,最后一分钟将暴力交上去,结果暴力也爆零了,变量打错了QAQ。真是菜出强大。
代码
#include <cstdio> #include <cstring> #include <cstdlib> #include <algorithm> #include <cmath> #include <iostream> #define MAXN 100010 using namespace std; typedef long long LL; int n, q; LL a[MAXN]; struct Tnode{ LL sum, num[10]; int mark[10]; }tree[MAXN<<2]; void build(int root, int L, int R){ if(L == R){ LL temp = a[L]; int t = 1; while(temp){ tree[root].num[temp%10] += t; t *= 10; temp /= 10; } tree[root].sum = a[L]; for(int i = 0; i <= 9; i++) tree[root].mark[i] = i; return; } int mid = (L + R) >> 1, Lson = root << 1, Rson = root << 1 | 1; build(Lson, L, mid); build(Rson, mid+1, R); tree[root].sum = tree[Lson].sum + tree[Rson].sum; for(int i = 0; i <= 9; i++) tree[root].num[i] = tree[Lson].num[i] + tree[Rson].num[i]; for(int i = 0; i <= 9; i++) tree[root].mark[i] = i; } void Down(int root, int Lson, int Rson){ LL now[10]; memcpy(now, tree[Lson].num, sizeof(now)); memset(tree[Lson].num, 0, sizeof(now)); for(int i = 0; i <= 9; i++){ int &temp = tree[Lson].mark[i]; if(tree[root].mark[temp] == temp) continue; tree[Lson].sum += (tree[root].mark[temp] - temp) * now[temp]; tree[Lson].num[tree[root].mark[temp]] += now[temp]; now[temp] = 0LL; temp = tree[root].mark[temp]; } for(int i = 0; i <= 9; i++) tree[Lson].num[i] += now[i]; memcpy(now, tree[Rson].num, sizeof(now)); memset(tree[Rson].num, 0, sizeof(now)); for(int i = 0; i <= 9; i++){ int &temp = tree[Rson].mark[i]; if(tree[root].mark[temp] == temp) continue; tree[Rson].sum += (tree[root].mark[temp] - temp) * now[temp]; tree[Rson].num[tree[root].mark[temp]] += now[temp]; now[temp] = 0LL; temp = tree[root].mark[temp]; } for(int i = 0; i <= 9; i++) tree[Rson].num[i] += now[i]; for(int i = 0; i <= 9; i++) tree[root].mark[i] = i; } void update(int root, int L, int R, int x, int y, int u, int v){ if(x > R || y < L) return; if(x <= L && y >= R){ tree[root].sum += (v - u) * tree[root].num[u]; tree[root].num[v] += tree[root].num[u]; tree[root].num[u] = 0LL; for(int i = 0; i <= 9; i++) if(tree[root].mark[i] == u) tree[root].mark[i] = v; return; } int mid = (L + R) >> 1, Lson = root << 1, Rson = root << 1 | 1; Down(root, Lson, Rson); update(Lson, L, mid, x, y, u, v); update(Rson, mid+1, R, x, y, u, v); tree[root].sum = tree[Lson].sum + tree[Rson].sum; for(int i = 0; i <= 9; i++) tree[root].num[i] = tree[Lson].num[i] + tree[Rson].num[i]; } LL query(int root, int L, int R, int x, int y){ if(x > R || y < L) return 0LL; if(x <= L && y >= R) return tree[root].sum; int mid = (L + R) >> 1, Lson = root << 1, Rson = root << 1 | 1; Down(root, Lson, Rson); LL temp1 = query(Lson, L, mid, x, y); LL temp2 = query(Rson, mid+1, R, x, y); return temp1 + temp2; } int main(){ scanf("%d%d", &n, &q); for(int i = 1; i <= n; i++) scanf("%lld", &a[i]); build(1, 1, n); int op, l, r, x, y; for(int i = 1; i <= q; i++){ scanf("%d%d%d", &op, &l, &r); if(op == 1){ scanf("%d%d", &x, &y); if(x == y) continue; update(1, 1, n, l, r, x, y); } else printf("%lld\n", query(1, 1, n, l, r)); } return 0; }
总结
这次考试的时间过得好快,时间分配不对,起码第二题还是能拿很多分的。第三题码不出,想的太少,没想好就码。最后暴力错了,真是不可饶恕。第一题数论不过关,快速分解质因数统计个数都不会,要总结经验,争取以后多拿点分。相关文章推荐
- 高二&高一&初三模拟赛17 总结
- 高二&高一&初三模拟赛21 总结
- 高二&高一&初三模拟赛27 总结
- 高二&高一&初三模拟赛19 总结
- 高二&高一&初三模拟赛25 总结
- 高二&高一&初三模拟赛20 总结
- 高二&高一&初三模拟赛14 总结
- 高二&高一&初三模拟赛16 总结
- 高二&高一&初三模拟赛24 总结
- 高二&高一&初三模拟赛16 总结
- 高二&高一&初三模拟赛23 总结
- 高二&高一&初三模拟赛27 总结
- 高二&高一模拟赛13 总结
- 高二&高一&初三模拟赛25 总结
- 高二&高一&初三模拟赛24 总结
- 高二&高一&初三模拟赛22 总结
- 高二&高一&初三模拟赛15 总结
- 高二&高一&初三模拟赛22 总结
- 高二&高一&初三模拟赛26 总结
- sc2017新高二&高一模拟赛7 总结