YACS月赛考后总结

因为一些原因,一开始没有办法注册,所以李有维考完之后发了题目给我然后做的,以犯了很多低级错误。

甲组

T1 队列覆盖

考试的时候打扫描线算法,结果因为不太熟悉所以打爆了。好像是线段树查询写错了的关系。写这一题的时候联想起了之前三角形求面积和的那一道题,题目意思其实很相似,但是那一题我考试的时候也写错了。

T2 评测队列

第一感觉是一个贪心,但是没有想到贪心方案,想了几个都证伪了。因为缺少数据的,手造的数据又不太彳亍,所以最后写了一个错误的贪心。暴力分倒是拿到了,但不应该仅仅满足与暴力。

T3 殊途同归

这一题让我联想起了传纸条一题。同样是寻找一个从一个点出发再返回的路径,这一题和那一题也有些许不同。传纸条是每一个点只能经过一次,这一题则是每一条边只能经过一次。感觉策略应该会有所不同,甚至有可能毫不相干,但也有可能大体相同,等标算出来了以后再去看一下。

乙组

T1 子集和

这一题如果没有后面的限制,应该就是一个普通的完全背包(至少我是这样想的),暴力做法就是枚举禁止使用的数字,然后每次进行完全背包的动态规划,这样是 \(O(n^3)\) 级别的算法。至于对正解的想法,我觉得应该是预处理出没有禁止的答案,并且算出禁止时候对答案产生的影响,但是计算影响这一步似乎有些麻烦,我写了好几种写法最后不是错误就是时间复杂度不降反升。

T2 路径问题

这题给出的图是一个基环树森林。最为暴力的做法是直接模拟 \(k\) 次移动,但显然是会超时的。明智一点的想法是:由于不管怎么走,一直移动下去的话一定会走到一个环上,所以只要绕环一圈之后就没有必要继续走下去了,可以直接输出答案,我就是这样写的,但是这样的话如果环足够大依旧会超时。至于更明智一点的想法……估计是记忆化之类的吧,我就没有想到了。

T3 航海探险

(唯一一道过了的题目)

因为题目保证没有矛盾的情况,也就是说两个点之间的相对距离确定之后再怎么加边也不会对距离有任何影响,而且题目并没有强制在线,所以,考虑把询问全部存起来,然后通过广搜或者深搜处理出两个点之间的相对位置。根据题意,只要两个点之间的距离已经确定,就一定是这个值。接着再来处理没有确定的情况。这种情况只需要简单的并查集就可以处理。

下面贴出代码。

#include<bits/stdc++.h>
#define MAXN 40010
#define MAXM 100010
using namespace std;
struct order{
    char op[3];
    int a, b, c;
};
struct node{
    int x, y;
};
struct edge{
    int pre,to;
    order w;
};
order ask[MAXM];
edge e[MAXM << 1];
node island[MAXN];
int n, m, cnt;
int fa[MAXN], head[MAXN], ans[MAXM];
bool vis[MAXN];
int get_fa(int now){
    if(now == fa[now]) return now;
    return fa[now] = get_fa(fa[now]);
}
void add_edge(int u, int v, order w){
    e[++cnt].pre = head[u];
    e[cnt].to = v; e[cnt].w = w;
    head[u] = cnt;
}
void build(int root){
    queue<int> q;
    q.push(root); vis[root] = true;
    while(!q.empty()){
        int now = q.front(); q.pop();
        for(int i = head[now]; i; i = e[i].pre){
            switch (e[i].w.op[0]){
                case 'S':
                    island[e[i].to].x = island[now].x;
                    island[e[i].to].y = island[now].y - e[i].w.c;
                    break;
                case 'N':
                    island[e[i].to].x = island[now].x;
                    island[e[i].to].y = island[now].y + e[i].w.c;
                    break;
                case 'E':
                    island[e[i].to].x = island[now].x + e[i].w.c;
                    island[e[i].to].y = island[now].y;
                    break;
                case 'W':
                    island[e[i].to].x = island[now].x - e[i].w.c;
                    island[e[i].to].y = island[now].y;
                    break;
            }
            if(!vis[e[i].to]){
                q.push(e[i].to);
                vis[e[i].to] = true;
            }
        }
    }
}
int main(){
    scanf("%d%d",&n,&m);
    for(int i = 1; i <= m; i++){
        scanf("%s",&ask[i].op);
        if(ask[i].op[0] == '?'){
            scanf("%d%d",&ask[i].a, &ask[i].b);
        }else{
            scanf("%d%d%d",&ask[i].a, &ask[i].b, &ask[i].c);
        }
    }
    for(int i = 1; i <= n; i++) fa[i] = i;
    for(int i = 1; i <= m; i++){
        if(ask[i].op[0] != '?'){
            order tmp = ask[i];
            add_edge(ask[i].b, ask[i].a, tmp);
            tmp.c = -tmp.c;
            add_edge(ask[i].a, ask[i].b, tmp);
        }
    }
    for(int i = 1; i <= n; i++){
        if(!vis[i]) build(i);
    }
    for(int i = 1; i <= m; i++){
        if(ask[i].op[0] == '?'){
            ans[i] = abs(island[ask[i].a].x - island[ask[i].b].x) + abs(island[ask[i].a].y - island[ask[i].b].y);
        }
    }
    for(int i = 1; i <= n; i++) fa[i] = i;
    for(int i = 1; i <= m; i++){
        if(ask[i].op[0] == '?'){
            int x = get_fa(ask[i].a), y = get_fa(ask[i].b);
            if(x == y) printf("%d\n",ans[i]);
            else printf("?\n");
        }else{
            int x = get_fa(ask[i].a), y = get_fa(ask[i].b);
            fa[x] = y;
        }
    }
    return 0;
}

T4 没有考试的天数

题目要求的是没有考试的天数,那么可以转化为有考试的天数。考虑用容斥原理做,\(n \le 20\),所以可以暴力枚举每个数是否选择然后乘起来,并按照选取数的个数分别存储起来。最后用容斥原理求出有多少天是有考试的。但是乘的时候要注意,不是简单相乘,而是求出它们的最小公倍数,否则会错误。(我觉得应该是对的)。

posted @ 2022-06-07 23:31  Night_Tide  阅读(205)  评论(2)    收藏  举报