ABC278 整合题解

A

  • A 题,送分题。
  • link

思路

数据范围很小,其实直接模拟也是可以通过的。

不过我们很容易想到 \(O(n)\) 的算法。

对于前 \(k\) 个数,不输出,其他数正常输出。

然后再在末尾补上 \(k\)\(0\)

容易发现,这样也是正确的。只要特判一下 \(k \ge n\) 的情况就行了,这个时候全部输出 \(0\)

代码

省去了大段的缺省源。

void solve()
{
    int n, k;
    scanf("%d%d", &n, &k);
    if (k >= n)
    {
        for (int i = 1; i <= n; i++) printf("0 ");
        puts("");
        exit(0);
    }
    for (int i = 1; i <= n; i++)
    {
        int x;
        scanf("%d", &x);
        if (i > k) printf("%d ", x);
    }
    while (k--) printf("0 ");
    puts("");
}

B

  • B 题,模拟题,难度较低。
  • link

思路

我们发现,所谓的交换位置,就是:交换 \(h\) 的个位与 \(m\) 的十位。

所以直接模拟就行。而 \(24\) 小时制,就是指:满足 \(0 \le h < 24, 0 \le m < 60\)

那么我们就可以判断这个时刻是否合法了。

然后一直枚举就行。由于判断的过程是 \(O(1)\) 的,并且没有多少种时刻,所以很快就能跑完。

代码

省去了大段的缺省源。

bool chk(int h, int m) //判断这个时刻是否合法
{
    int th = h, tm = m;
    m = 10 * (th % 10) + (tm % 10), h = 10 * (th / 10) + (tm / 10);
    return (0 <= h && h < 24 && 0 <= m && m < 60);
}
void solve()
{
    int h, m;
    scanf("%d%d", &h, &m);
    while (true) //暴力枚举
    {
        if (chk(h, m)) printf("%d %d\n", h, m), exit(0);
        if (h == 23 && m == 59) h = m = 0;
        else if (m == 59) h++, m = 0;
        else m++;
    }
}

C

  • 为什么我每次都会将 C 题做得很复杂呢。
  • link

思路

由于用户编号很大,所以我们要离散化。

显然,最多只会出现 \(2 \times q\) 种编号,所以离散化后也只有这么多个数。

然后暴力即可。这里你想用什么就用什么,比如我用了 set

代码

省去了大段的缺省源。

const int N = 4e5 + 5;
int op[N], a[N], b[N];
int t[N], m, cur;
int calc(int x) {return lower_bound(t + 1, t + m + 1, x) - t;}
void solve()
{
    int n, q;
    scanf("%d%d", &n, &q);
    for (int i = 1; i <= q; i++)
    {
        scanf("%d%d%d", &op[i], &a[i], &b[i]);
        t[++cur] = a[i], t[++cur] = b[i];
    }
    sort(t + 1, t + cur + 1); //离散化
    m = unique(t + 1, t + cur + 1) - t;

    set <int> st[N << 1];
    for (int i = 1; i <= q; i++) //暴力计算答案
    {
        a[i] = calc(a[i]), b[i] = calc(b[i]);
        if (op[i] == 1) st[a[i]].insert(b[i]);
        else if (op[i] == 2) st[a[i]].erase(b[i]);
        else
        {
            if (st[a[i]].count(b[i]) && st[b[i]].count(a[i])) puts("Yes");
            else puts("No");
        }
    }
}

D

思路

很容易想到线段树。维护 \(cov_i\) 表示覆盖的懒标记。

单点加与单点查都非常简单。全局覆盖只需要给每一层都打懒标记即可。

对于 pushdown 操作,看是否有 \(cov\) 标记,有就先覆盖,再加。

代码

事实上,如果你做过 P1253,你会发现这题只需要改一改操作就行。

所以我两分钟过了 D!

由于代码是从 P1253 贴过来的,所以很诡异,就不放出来了。

E

  • 非常套路的题目,为啥要放在 E。
  • link

思路

容易发现,相邻查询的覆盖区间不会差太远。所以考虑用较短的时间处理两个查询。

思路也很容易想到:维护两个操作 add 与 del,支持 \(O(1)\) 增加、删除一个数。

void add(int x)
{
    if (!vis[x]) ans++;
    vis[x]++;
}
void del(int x)
{
    vis[x]--;
    if (!vis[x]) ans--;
}

然后,对于相邻两个操作,只需要删除掉前一列,并增加新的一列即可。

只需要特殊处理一下第一个区间即可。

这样,时间复杂度就是 \(O(n^3)\) 了。

代码

省去了大段的缺省源。

const int N = 305;
int vis[N], zlt[N]; //zlt 是备份的原 vis
int a[N][N];

int ans, tans; //tans 是备份的原 ans
void add(int x)
{
    if (!vis[x]) ans++;
    vis[x]++;
}
void del(int x)
{
    vis[x]--;
    if (!vis[x]) ans--;
}
int ANS[N][N];
void solve()
{
    int n, m, k, h, w; 
    scanf("%d%d%d%d%d", &n, &m, &k, &h, &w);
    for (int i = 1; i <= n; i++)
        for (int j = 1; j <= m; j++)
        {
            scanf("%d", &a[i][j]);
            if (!vis[a[i][j]]) ans++, tans++;
            vis[a[i][j]]++, zlt[a[i][j]]++;
        }
    for (int i = 1; h + i - 1 <= n; i++)
    {
        ans = tans;
        for (int j = 1; j <= k; j++) vis[j] = zlt[j];

        for (int j = i; j <= h + i - 1; j++)
            for (int k = 1; k <= w; k++)
                del(a[j][k]);
        ANS[i][1] = ans;
        for (int j = 1; j + w - 1 <= m; j++) //纵坐标
        {
            for (int k = i; k <= h + i - 1; k++) add(a[k][j]), del(a[k][j + w]); //横坐标
            ANS[i][j + 1] = ans; //统计答案
        }
    }
    for (int i = 1; i <= n - h + 1; i++, putchar('\n'))
        for (int j = 1; j <= m - w + 1; j++, putchar(' '))
            printf("%d", ANS[i][j]);
}

F

  • 博弈论,状压,记忆化搜索。
  • link

思路

看到很小的 \(n\),容易联想到状压、搜索。本题就是状压加搜索。

设状态 \(x\) 的每一位表示:如果第 \(i\) 位是 \(0\),则当前数没有被选过。否则已经选过了。

每次 dfs 的时候,记录当前状态,以及上一次选的字符串。

如果有一种情况能使对手必败,那么我们就必胜。如果所有情况都是对手胜,那么我们必败。

注意要记忆话,否则会被卡到 \(O(n!)\) 然后 TLE 几个点。

代码

省去了大段的缺省源。

const int N = 17;
int n;
string a[N];

bool vis[1 << N][N], ans[1 << N][N]; //vis 是看 ans 有没有出现过
bool dfs(int x, int lst) //x 是状压
{
    if (vis[x][lst]) return ans[x][lst]; //记忆化搜索
    for (int i = 1; i <= n; i++)
        if ( !(x & (1 << (i - 1))) ) //意思:x的第i位是0
            if (lst == 0 || a[lst][a[lst].length() - 1] == a[i][0]) //如果可以接上去
                if ( !dfs(x | (1 << (i - 1)), i) ) //尝试接
                    {vis[x][lst] = true; return ans[x][lst] = true;}  //对手必败,说明我方必胜
    vis[x][lst] = true; return ans[x][lst] = false; //怎样都是对手胜,那么我们必败
}
void solve()
{
    scanf("%d", &n);
    for (int i = 1; i <= n; i++) cin >> a[i];

    if (dfs(0, 0)) puts("First");
    else puts("Second");
}
posted @ 2022-11-21 09:59  liangbowen  阅读(60)  评论(0编辑  收藏  举报