高二&高一&初三模拟赛27 总结

下午开始停课了,晚上的比赛不算简单,卡常什么的也是让人心烦,排名刚好如自己预言。


回文数

题目描述

这里写图片描述
这里写图片描述


题解(错位相减法+快速幂+求逆元)

这是一道数列求前缀和的问题。学过数列的都能用错位相减法秒杀。

明显题目要求的就是

i=1n2910i1(2i1)

这就是一个等差数列乘一个等比数列,直接错位相减,求出来是

209(10n11)1+10n(2n1)

直接快速幂。由于要取模,而模数 233333=353661 ,于是用欧拉定理或扩欧求逆元,逆元是 25926

时间复杂度 O(Tlogn)


代码

#include <cstdio>
#include <iostream>
#include <cstdlib>
#include <cstring>
#include <cmath>
#include <algorithm>
#define MOD 233333

using namespace std;

int T, n;

typedef long long LL;


LL Pow(LL x, int y){
    LL res = 1LL;
    while(y){
        if(y & 1)  res = res * x % MOD;
        x = x * x % MOD;
        y >>= 1;
    }
    return res;
}

int main(){

    freopen("2255.in", "r", stdin);
    freopen("2255.out", "w", stdout);

    scanf("%d", &T);
    while(T --){
        scanf("%d", &n);
        n = (n + 1) >> 1;

        LL ans1 = ((Pow(10LL, n-1) - 1LL + MOD) % MOD * 25926LL % MOD * -20LL + MOD) % MOD;
        LL ans2 = ((Pow(10LL, n) * ((2LL * n - 1LL + MOD) % MOD)) % MOD - 1LL + MOD) % MOD;
        LL ans = (ans1 + ans2 + MOD) % MOD;
        printf("%lld\n", ans);
    }

    return 0;
}

购物

题目描述

这里写图片描述
这里写图片描述
这里写图片描述


题解(动态规划)

题目中 happy 值的下界为1。

由于平方的和不大于和的平方,很容易想到一种贪心:每次选取 happy 值的和最大的一段区间先做,然后将相交的区间删去重合部分,然后继续做。一开始要先将区间数量变成不超过 n ,保证左指针递增同时右指针也递增。

这样贪能拿90分,为什么呢?因为这是错的!假如 happy 值都是 1 ,区间有[1,5],[6,10],[3,8],我们不能毫不犹豫地先选 [3,8] ,应该先选另外两个才更优。于是贪心不行,继而转向 DP 的思考。扶我起来我还能贪

我们发现 n 比较小,于是以n作为状态:记 dp[i] 为当前做到第 i 个商店,取得的最大值是多少。然后我们就将问题转化为当前在[1,n]中选一些区间,使得这些区间是给出来的区间的子区间。由于长区间包含的短区间无用,于是我们只用预处理出包含 i 的区间的左端点最左到哪里,记为Left[i]。这个可以通过对给出的区间按 r 排序,单调就可以搞定。可能出现没有的情况,代表这个商店不能被取到。

然后一个O(n2) DP ,每个点由前面的点转移过来,由于被包含了的区间断开肯定不如连在一起的贡献大,于是贡献必然是连续一段一起算的,得出状态转移方程:

dp[i]=max(dp[i],dp[j1]+(sum[i]sum[j1])(sum[i]sum[j1]))

其中 sum 是前缀和, j 的取值范围就是[Left[i],i]

值得注意的是,如果没有合法的转移, dp[i]=dp[i1]

总结:如果题目要求具有次序,不妨找一个确定次序的状态(或强制其有序)来进行 DP ,一般 DP 的题要消除次序限制,只考虑前一个状态对后面某个状态的贡献。


代码

#include <cstdio>
#include <cstdlib>
#include <iostream>
#include <cstring>
#include <algorithm>
#include <cmath>
#define maxn 5005
#define maxm 1000010
using namespace std;

typedef long long LL;
int n, m, Left[maxn];
LL happy[maxn];
LL dp[maxn], sum[maxn];
struct Data{
    int l, r;
    bool operator < (const Data& Q) const{
        return r > Q.r;
    }
}q[maxm];

int main(){

    freopen("2256.in", "r", stdin);
    freopen("2256.out", "w", stdout);

    scanf("%d%d", &n, &m);
    for(int i = 1; i <= n; i++)
        scanf("%lld", &happy[i]);

    for(int i = 1; i <= n; i++)  sum[i] = sum[i-1] + happy[i];  

    for(int i = 1; i <= m; i++)  scanf("%d%d", &q[i].l, &q[i].r);

    sort(q+1, q+m+1);

    for(int i = n, j = 1, re = n+1; i >= 1; i--){
        while(j <= m && q[j].r >= i){  
            re = min(re, q[j].l);
            j ++;
        }
        Left[i] = re;
    }

    for(int i = 1; i <= n; i++){
        if(Left[i] > i)  dp[i] = dp[i-1];
        for(int j = Left[i]; j <= i; j++)
            dp[i] = max(dp[i], dp[j-1] + (sum[i] - sum[j-1]) * (sum[i] - sum[j-1]));
    }

    printf("%lld\n", dp[n]);

    return 0;
}

宗教

题目描述

这里写图片描述
这里写图片描述


题解(后缀数组+RMQ/二分+hash/暴力+特判/FFT+优化??)

本题较难,题目可以看作求 K 次最长公共前缀。。。

直接用加了底层优化的暴力+特判即可过。当然这是一种“无味”的过法。正解是二分+hash,这个我还不会,日后再填坑。

后缀数组+ RMQ 的坑也日后再填, NOIP 考后缀数组的概率较小。另外考场上我写了 26 次的 FFT ,求汉明距离就是个卷积。明显差一定反过来卷,然后把不等号变成乘号就好了。和 UVA 某题一样,但是 FFT 常数太大过不了这题的,比暴力还低分。不知道有没有可靠的优化能救民于水火。

总之这题就留坑待填吧。


代码(暴力+特判)

#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cmath>
#include <cstring>
#include <cstdlib>
#define maxn 200010

using namespace std;

char s1[maxn], s2[maxn];
int K, len1, len2, ans, cnt;

int main(){

    freopen("2257.in", "r", stdin);
    freopen("2257.out", "w", stdout);

    scanf("%s%s", &s1, &s2);
    scanf("%d", &K);

    len1 = strlen(s1);
    len2 = strlen(s2);

    bool cheat = true;
    for(int i = 1; i < len1; i++)  if(s1[i] != s1[i-1])  cheat = false;
    for(int i = 1; i < len2; i++)  if(s2[i] != s2[i-1])  cheat = false;

    if(cheat){
        printf("%d\n", len1 - len2 + 1);
        return 0;
    }

    for(register int i = 0; i < len1-len2+1; i++){
        cnt = 0;
        for(register int j = 0; j < len2; j++){
            if(s1[i+j] != s2[j]){
                cnt ++;
                if(cnt > K)  break;
            }
        }
        if(cnt <= K)  ans ++;
    }
    printf("%d\n", ans);

    return 0;
}

代码(FFT)

#include <cstdio>
#include <cstdlib>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <cmath>
#define MAXN 200010

using namespace std;

const double PI = acos(-1.0);
int len1, len2, n, K;
char s1[MAXN], s2[MAXN];
int P[MAXN], ans;

struct Complex{
    double real, image;
    Complex() {}
    Complex(double _real, double _image){
        real = _real;
        image = _image;
    }
    friend Complex operator + (Complex A, Complex B){return Complex(A.real + B.real, A.image + B.image);}
    friend Complex operator - (Complex A, Complex B){return Complex(A.real - B.real, A.image - B.image);}
    friend Complex operator * (Complex A, Complex B){return Complex(A.real * B.real - A.image * B.image, A.real * B.image + A.image * B.real);}
}a[MAXN<<2], b[MAXN<<2], c[26][MAXN<<2];

void Get_P(){
    for(register int i = 1, t = 1; i < MAXN; i++){
        if(t < i)  t <<= 1;
        P[i] = t;
    }
}

void Reverse(Complex *A){
    for(register int i = 1; i < n-1; i++){
        int j = 0;
        for(register int k = 1, tmp = i; k < n; k <<= 1, tmp >>= 1)
            j = ((j << 1) | (tmp & 1));
        if(j > i)  swap(A[i], A[j]);
    }
}

void FFT(Complex *A, int n, int DFT){
    Reverse(A);
    for(register int s = 1; (1<<s) <= n; s++){
        int m = (1 << s);
        Complex wm = Complex(cos(DFT*2*PI/m), sin(DFT*2*PI/m));
        for(register int k = 0; k < n; k += m){
            Complex w = Complex(1, 0);
            for(register int j = 0; j < (m>>1); j++){
                Complex u = A[j + k], t = w * A[j + k + (m>>1)];
                A[j + k] = u + t;
                A[j + k + (m>>1)] = u - t;
                w = w * wm;
            }
        }
    }
    if(DFT == -1)  for(register int i = 0; i < n; i++)  A[i].real /= n, A[i].image /= n;
}

void Work(char x, int id){
      for(register int i = 0; i < len1; i++)  a[i] = Complex(s1[i]==x, 0);
      for(register int i = len1; i < n; i++)  a[i] = Complex(0, 0);
      for(register int i = 0; i < len2; i++)  b[len2-i-1] = Complex(s2[i]==x, 0);
      for(register int i = len2; i < n; i++)  b[i] = Complex(0, 0);

      FFT(a, n, 1);
      FFT(b, n, 1);
      for(register int i = 0; i < n; i++)  c[id][i] = a[i] * b[i];
      FFT(c[id], n, -1);
}

int main(){

    freopen("2257.in", "r", stdin);
    freopen("2257.out", "w", stdout);

    Get_P();

    scanf("%s%s", &s1, &s2);
    scanf("%d", &K);

    len1 = strlen(s1); 
    len2 = strlen(s2);

    if(len1 < len2){
        puts("0");
        return 0;
    }

    n = P[max(len1, len2)] << 1;

    for(register int i = 0; i < 26; i++)  Work(i + 'a', i);

    for(register int i = len2-1; i < len1; i++){
        int sum = 0;
        for(register int j = 0; j < 26; j++)
            sum += (int)(c[j][i].real+0.5);
        if(len2 - sum <= K)  ans ++;
    }

    printf("%d\n", ans);

    return 0;
}

总结

这次考试之前教练叫我们在做题前预估解题时间并记录真正的完成时间。我在这次比赛中时间并不是调节得很恰当,第一题这样的水题因为一点细节做了那么久时间,后面的时间就略显不足。

另外第二题想贪心不对就没想过 DP ,钻进一个地方,这是很致命的,考试经验还需要增加。

第三题这样的题目也没有先写个暴力交一下看看刷了几次。


这里写图片描述

海底月是天上月

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值