您的位置:首页 > 其它

高二&高一&初三模拟赛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;
}


总结

这次考试的时间过得好快,时间分配不对,起码第二题还是能拿很多分的。第三题码不出,想的太少,没想好就码。最后暴力错了,真是不可饶恕。第一题数论不过关,快速分解质因数统计个数都不会,要总结经验,争取以后多拿点分。

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: