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

前言

为什么我的点双还没有调出来啊啊啊!!


联通

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


题解(暴力+卡常数)

如果这题直接暴力的话,时间复杂度是 O(km) ,好像会超时。但是我的莫队又太菜了,不知道怎么做。于是我就和米娜桑一样都写了暴力,然而最后米娜桑都过了,我却只有60分。我要好好反思一下,明明我都加了register了,为什么同为暴力,分数却不一样?(明明是味道相同的花,为什么颜色却不一样)

因为我实在太懒了,卡常数都懒得卡,就只会卡评测。首先,我没有将操作排序, 然后重复加的那些就可以不用再做,这是一大败笔,因为这样真的跑得快(莫队里就有体现这种思想)。我加上后还是T,于是我把我判断连通块的dfs改成了并查集,然后就过了。dfs每次都是严格 O(n+m) ,所以挺慢的,换成 O(mα(n)) 之后会快的。因为这是最坏的情况,所以平均跑起来就挺快了。

具体做法就是按l升序,r降序排,每次多加入一条边就看看边的端点原来是不是同一个连通块(删边就重新做),不是的话,答案-1。


代码

#include <cstdio>
#include <cstring>
#include <iostream>
#include <cmath>
#include <cstdlib>
#include <algorithm>
#define maxn 505
#define maxm 10010
#define maxk 20010

using namespace std;

int n, m, k, head_p[maxn], cur = -1, res, ans[maxk], Fa[maxn];
struct Data{
    int x, y;
}save[maxm];

struct Query{
    int l, r, id;
    bool operator < (const Query& Q) const{
        return (l < Q.l) || (l == Q.l && r > Q.r);
    }
}q[maxk];

bool Del[maxm];

int find_r(int x){
    if(Fa[x] == x)  return x;
    return Fa[x] = find_r(Fa[x]);
}

int main(){

    scanf("%d%d", &n, &m);

    for(register int i = 1; i <= n; ++i)  head_p[i] = -1;

    for(register int i = 1; i <= m; ++i)
        scanf("%d%d", &save[i].x, &save[i].y);

    scanf("%d", &k);

    for(register int i = 1; i <= k; ++i){
        scanf("%d%d", &q[i].l, &q[i].r);
        q[i].id = i;
    }

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

    res = n;

    for(register int i = 1, last; i <= k; ++i){

        if(i != 1 && q[i].l == q[last].l){
            for(register int j = q[last].r; j > q[i].r; --j){
                int rx = find_r(save[j].x), ry = find_r(save[j].y);
                if(rx != ry){
                    Fa[rx] = ry;
                    res --;
                }
            }
        }
        else{

            for(register int j = 1; j <= n; ++j)  Fa[j] = j;

            res = n;
            for(register int j = 1; j < q[i].l; ++j){
                int rx = find_r(save[j].x), ry = find_r(save[j].y);
                if(rx != ry){
                    Fa[rx] = ry;
                    res --;
                }
            }

            for(register int j = q[i].r+1; j <= m; ++j){
                int rx = find_r(save[j].x), ry = find_r(save[j].y);
                if(rx != ry){
                    Fa[rx] = ry;
                    res --;
                }
            }
            last = i;
        }
        ans[q[i].id] = res;
    }

    for(register int i = 1; i <= k; ++i)
        printf("%d\n", ans[i]);

    return 0;
}

Two strings

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


题解(贪心+尺取法)

这题也是一道贪心的水题,然而我考试时心态不好,看错题了,又想着快点水完去码(点)双,于是那时我就真没想出来。码了个暴力,还忘了特判一种情况,瞬间炸成40分。再见。

赛后我很快码完了正解(很慢才对),其实不难,我考试的时候已经有一点思路了,但由于一开始我看错了题,取走连续一段看成了剩下连续一段,结果写了个单调调了很久,发现自己第三个样例崩了,然后顿时就觉得自己要送了。然后我理解完题意后才发现是一定要从左边和右边开始匹配的!于是我又想过些许一点贪心枚举之类的方法,但是由于前面浪费了太多时间,后面就直接上了个暴力,而且,暴力也调了半天,又炸了。。

言归正传,我们考虑记 fL[i] s2 的第 i 个字符在s1中的最左边的位置,且要满足 fL[i]>fL[i1] 。因为要取走连续的一段,匹配到 i 的前提是前i1为必须匹配。如果 i 匹配不到,就让其等于oo。这个 fL 可以单调一遍求出来。类似的,我们记 fR[i] s2 的第 i 个字符在s1中的最右边的位置,且要满足 fR[i]<fR[i+1] 。没有就令其等于 oo 。同样可以逆推一遍求出。这个预处理是很重要的,这是本题解题的关键。

完成预处理后,我们用尺取法单调的枚举被去掉区间的l和r。于是剩余的必须要匹配成功的那部分就是[0,l-1]和[r+1,len2-1]。如果 fL[l1]<fR[r+1] 的话,代表当前可以贡献答案。如果不行,就要多删,不断令r++,直到可以为止并在此时记录答案。外层循环令l++,看看能否使答案更优。这样显然是 O(n) 的。这里有一个细节就是在判断当前状态是否可以贡献这里,如果有一端没有删,就直接看看另一端的 f 值是否合法,另外一个都不删的情况(和输出“-”的情况)要特判。

另外除了这种单调方法,二分也是可以的,这题很坑的是,答案要求保证最左端尽量小,而它没说,不过如果外层枚举的是l,就不会出错。


代码

#include <cstdio>
#include <cstdlib>
#include <iostream>
#include <cmath>
#include <cstring>
#include <algorithm>
#define maxn 100010
#define oo 0x7fffffff

using namespace std;

char s1[maxn], s2[maxn];
int len1, len2, start, end, ans = oo;
int fl[maxn], fr[maxn];

bool Judge(int l, int r){
    if(!l)  return fr[r+1] == -oo;
    if(r+1 == len2)  return fl[l-1] == oo;
    return fl[l-1] >= fr[r+1];
}

int main(){

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

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

    for(int i = 0, j = 0; i < len2; i++){
        while(j < len1){
            if(s1[j] == s2[i])  break;
            j ++;
        }
        if(j >= len1)  fl[i] = oo;
        else  fl[i] = j++;
    }

    for(int i = len2-1, j = len1-1; i >= 0; i--){
        while(j >= 0){
            if(s1[j] == s2[i])  break;
            j --;
        }
        if(j < 0)  fr[i] = -oo;
        else  fr[i] = j--;
    }

    if(fl[len2-1] != oo){
        for(int i = 0; i < len2; i++)
            printf("%c", s2[i]);
        return 0;
    }

    if(fl[0] == oo && fr[len2-1] == -oo){
        printf("-");
        return 0;
    }

    for(int l = 0, r = 0; l < len2; l++){
        while(r < len2 && Judge(l, r))  r ++;
        if(r >= len2)  break;
        if(r - l + 1 < ans){
            ans = r - l + 1;
            start = l - 1;
            end = r + 1;
        }
    }

    for(int i = 0; i <= start; i++)
        printf("%c", s2[i]);
    for(int i = end; i < len2; i++)
        printf("%c", s2[i]);

    return 0;
}

时间机器

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


题解(暴力是标算/(treap/CDQ分治)+离散化)

这题一看就是一道数据结构题,然而暴力竟然也能过!!(所谓的n方过10万)

暴力就不说了,我们先对数字离散化,然后对每个数字开一个treap,treap占用的内存最多就是操作数。然后对于每一个1,2操作,就在对应的treap中插入,维护几个域,val,sum,t。t就是时间,是treap的关键字,二叉查找就是根据时间来做。val就是加记为1,减记为-1,然后在treap上维护和sum。这里将2操作也转换为插入,而不是直接删除,是比较妙的。对于每个3操作,我们直接在treap中二叉查找,如果当前的t小于等于询问的t,则将当前点的左子树的sum加上自己的val算进答案里,并继续向右边走,否则向左边走。(开始找了前驱,错得一塌糊涂)这样treap的简单做法就讲完了。

然后还有一种更简单的方法,CDQ分治。我们照样离散化,然后标记左边,按t排序,然后左边贡献右边,这时由于只问同一种数字,我们开个随便记录一下就可以了。最后贡献完反向标记再排回来,递归左右。说了那么多,就说了5个字:CDQ裸题。

两种做法的时间都是O(nlogn),跑得的确比暴力要快一点。


代码(treap)

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

using namespace std;

int n, rank[maxn], cnt;

struct Data{
    int val, t, num, id;
}op[maxn];

bool cmp1(Data A, Data B){
    return A.num < B.num;
}

bool cmp2(Data A, Data B){
    return A.id < B.id;
}

struct Treap{
    Treap *L, *R;
    int t, val, sum, fix;
    int Lsum(){return L ? L->sum : 0;}
    int Rsum(){return R ? R->sum : 0;}
    void Up(){
        sum = Lsum() + Rsum() + val;
    }
}Node[maxn], *Root[maxn];

Treap *NewTnode(int t, int val){
    Node[cnt].L = Node[cnt].R = NULL;
    Node[cnt].t = t;
    Node[cnt].val = Node[cnt].sum = val;
    Node[cnt].fix = rand();
    return Node+cnt++;
}

void Treap_L_Rot(Treap *&a){
    Treap *b = a->R;
    a->R = b->L;
    b->L = a;
    a = b;
    a->L->Up();
    a->Up();
}

void Treap_R_Rot(Treap *&a){
    Treap *b = a->L;
    a->L = b->R;
    b->R = a;
    a = b;
    a->R->Up();
    a->Up();
}

void Treap_Insert(Treap *&p, int t, int val){
    if(!p){
        p = NewTnode(t, val);
        return;
    }

    if(p->t == t){
        p->val += val;
        p->sum += val;
    }

    else if(p->t > t){
        Treap_Insert(p->L, t, val);
        p->sum += val;
        if(p->L->fix < p->fix)  Treap_R_Rot(p);
    }

    else{
        Treap_Insert(p->R, t, val);
        p->sum += val;
        if(p->R->fix < p->fix)  Treap_L_Rot(p);
    }
}

int Treap_work(Treap *p, int t){
    if(!p)  return 0;
    if(p->t <= t)  return p->Lsum()+p->val+Treap_work(p->R, t);
    else  return Treap_work(p->L, t);
}

int main(){

    scanf("%d", &n);

    for(int i = 1; i <= n; i++){
        scanf("%d%d%d", &op[i].val, &op[i].t, &op[i].num);
        if(op[i].val == 2)  op[i].val = -1;
        op[i].id = i;
    }

    sort(op+1, op+n+1, cmp1);

    rank[op[1].id] = 1;
    for(int i = 2; i <= n; i++){
        rank[op[i].id] = rank[op[i-1].id];
        if(op[i].num != op[i-1].num)  rank[op[i].id] ++;
    }

    sort(op+1, op+n+1, cmp2);

    for(int i = 1; i <= n; i++){
        if(op[i].val != 3)  Treap_Insert(Root[rank[i]], op[i].t, op[i].val);
        else  printf("%d\n", Treap_work(Root[rank[i]], op[i].t));
    }

    return 0;
}

代码(CDQ分治)

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

using namespace std;

int n, rank[maxn], cnt[maxn], ans[maxn];
bool Left[maxn];

struct Data{
    int type, t, num, id;
}op[maxn];

bool cmp1(Data A, Data B){
    return A.num < B.num;
}

bool cmp2(Data A, Data B){
    return A.id < B.id;
}

bool cmp3(Data A, Data B){
    return A.t < B.t;
}

void CDQ(int L, int R){
    if(L == R)  return;

    int mid = (L + R) >> 1;
    for(int i = L; i <= mid; i++)  Left[op[i].id] = true;

    sort(op+L, op+R+1, cmp3);

    for(int i = L; i <= R; i++){
        if(Left[op[i].id]){
            if(op[i].type == 1)  cnt[rank[op[i].id]] ++;
            else if(op[i].type == 2)  cnt[rank[op[i].id]] --;
        }
        else if(op[i].type == 3)
            ans[op[i].id] += cnt[rank[op[i].id]];
    }

    for(int i = L; i <= R; i++)
        if(Left[op[i].id]){
            if(op[i].type == 1)  cnt[rank[op[i].id]] --;
            else if(op[i].type == 2)  cnt[rank[op[i].id]] ++;
            Left[op[i].id] = false;
        }

    sort(op+L, op+R+1, cmp2);

    CDQ(L, mid);
    CDQ(mid+1, R);
}

int main(){

    scanf("%d", &n);

    for(int i = 1; i <= n; i++){
        scanf("%d%d%d", &op[i].type, &op[i].t, &op[i].num);
        op[i].id = i;
    }

    sort(op+1, op+n+1, cmp1);

    rank[op[1].id] = 1;
    for(int i = 2; i <= n; i++){
        rank[op[i].id] = rank[op[i-1].id];
        if(op[i].num != op[i-1].num)  rank[op[i].id] ++;
    }

    sort(op+1, op+n+1, cmp2);

    CDQ(1, n);

    for(int i = 1; i <= n; i++)
        if(op[i].type == 3)
            printf("%d\n", ans[i]);

    return 0;
}

总结

这次比赛太怠惰了,一边比赛一边谈笑风生,结果导致码程序的时间不足,T2看错题导致浪费了时间,又搞得很紧张,不淡定。以后看题一定要认真,正式比赛的时候看错题就是送命啊。还有一定不要懒,T1的常数优化加不加就决定能否多拿40分。还有一件事,调试代码的能力一定要增强,以前的算法要经常回头复习,温故而知新。


这里写图片描述

『我是否住进了某人的心房呢』
『是啊』
『我是否住进了你的心房呢』
『连鞋也没脱就住进来了呢』

——《四月是你的谎言》

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值