1117GRYZ模拟赛解题报告

期望得分:\(60+100+60 = 220pts\)

实际得分:\(80+70+60 = 210pts\)

心路历程

T1

大概是发现如何判断一个颜色是否覆盖了其他颜色。

可以简单归纳一下,

  • 对于每种颜色处理出他的四个边界,然后标记这四个边界所构成的矩形出现的其他颜色。
  • 需要特判的是,全局只有一个颜色,而 \(k > 1\)

然后你暴力标记的话是 \(\mathcal O(knm)\) 的。

你考虑二维差分 + 二位前缀和。如果一个位置被加了两次,那这个位置的颜色就是不合法的。

时间复杂度 \(\mathcal O(nm)\) ,可以通过。

因为这里数据范围写的是 \(n \times m \le 10^5\) ,你可以采用标号的形式把二维转化成一维存储,类似于 \(id(i,j) = m \times (i-1) + j\)。 这个转化比较经典。

/*
Work by: Suzt_ilymtics
Problem: 不知名屑题
Knowledge: 垃圾算法
Time: O(能过)
*/
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<queue>
#define LL long long
#define orz cout<<"lkp AK IOI!"<<endl

using namespace std;
const int MAXN = 1e6+5;
const int INF = 1e9+7;
const int mod = 1e9+7;

struct node {
    int sx, sy, ex, ey;
}a[MAXN];

int n, m, K, Ans = 0;
int col[MAXN], cnt[MAXN];
bool vis[MAXN];

int read(){
    int s = 0, f = 0;
    char ch = getchar();
    while(!isdigit(ch))  f |= (ch == '-'), ch = getchar();
    while(isdigit(ch)) s = (s << 1) + (s << 3) + ch - '0' , ch = getchar();
    return f ? -s : s;
}

int P(int x, int y) { return (x - 1) * m + y; }

int main()
{
//    freopen("paint.in","r",stdin);
//    freopen("paint.out","w",stdout);
    n = read(), m = read(), K = read();
    for(int i = 1; i <= n * m ; ++i) a[i].sx = a[i].sy = INF, a[i].ex = a[i].ey = 0;
    for(int i = 1; i <= n; ++i) {
       for(int j = 1; j <= m; ++j) {
           int x = P(i, j);
           col[x] = read();
           a[col[x]].sx = min(a[col[x]].sx, i);
           a[col[x]].sy = min(a[col[x]].sy, j);
           a[col[x]].ex = max(a[col[x]].ex, i);
           a[col[x]].ey = max(a[col[x]].ey, j);
       } 
    }
    bool Flag = false;
    for(int i = 1; i <= n * m; ++i) if(col[i] != col[1]) { Flag = true; break; }
    if(!Flag) return printf("%d", max(1, K - 1)), 0;
    for(int i = 1; i <= K; ++i) {
        if(a[i].sx > a[i].ex) continue;
        if(a[i].sy > a[i].ey) continue;
        cnt[P(a[i].sx, a[i].sy)] ++;
        cnt[P(a[i].ex + 1, a[i].ey + 1)] ++;
        cnt[P(a[i].sx, a[i].ey + 1)] --;
        cnt[P(a[i].ex + 1, a[i].sy)] --;
    }
    for(int i = 1; i <= n; ++i)
        for(int j = 1; j <= m; ++j) 
            cnt[P(i, j)] += cnt[P(i, j - 1)];
    for(int i = 1; i <= n; ++i) 
        for(int j = 1; j <= m; ++j) 
            cnt[P(i, j)] += cnt[P(i - 1, j)];
    for(int i = 1; i <= n; ++i) 
        for(int j = 1; j <= m; ++j) 
            if(cnt[P(i, j)] > 1) vis[col[P(i, j)]] = true;
    for(int i = 1; i <= K; ++i) if(!vis[i]) ++ Ans;
    printf("%d\n", Ans);
    return 0;
}
/*
复杂度 O(Knm) 
3 4 8
2 3 0 5 
2 3 7 7
2 7 7 7 

3 4 5
1 1 1 1 
1 1 1 1
1 1 1 1

4 4 10
1 2 2 1
1 3 3 1
1 4 4 1
1 5 5 1
*/

T2

考虑对 \(a\) 序列做一个前缀和,然后一个询问就变成了 \(sum_r - sum_{l - 1} \equiv v \pmod p\) ,转化一下变成 \(sum_r \equiv sum_{l-1} + v \pmod p\) ,然后你把 \(l-1, r\) 都看作一个点,把一个这个信息看作两个点连边,然后这个东西就可以用带权并查集维护。

如果你不懂什么是带权并查集

当你发现两个点在同一联通块内时,你要判断一下是否满足条件,如果不满足就直接退出输出答案好了。

/*
Work by: Suzt_ilymtics
Problem: 不知名屑题
Knowledge: 垃圾算法
Time: O(能过)
*/
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<queue>
#define int long long
#define orz cout<<"lkp AK IOI!"<<endl

using namespace std;
const int MAXN = 1e6+500;
const int INF = 1e9+7;
const int mod = 1e9+7;

int n, m, p;
int fa[MAXN], val[MAXN];

int read(){
    int s = 0, f = 0;
    char ch = getchar();
    while(!isdigit(ch))  f |= (ch == '-'), ch = getchar();
    while(isdigit(ch)) s = (s << 1) + (s << 3) + ch - '0' , ch = getchar();
    return f ? -s : s;
}

int find(int x) {
    if(fa[x] == x) return x;
    int fa_ = fa[x];
    fa[x] = find(fa[x]);
    val[x] = (val[x] + val[fa_]) % p;
    return fa[x];
}

signed main()
{
//    freopen("seq.in","r",stdin);
//    freopen("seq.out","w",stdout);
    n = read(), m = read(), p = read();
    for(int i = 1; i <= n; ++i) fa[i] = i, val[i] = 0;
    for(int i = 1, l, r, v; i <= m; ++i) {
        l = read(), r = read(), v = read();
        if(l > r) continue;
        int uf = find(l - 1), vf = find(r);
        int t = (val[r] - val[l - 1] + p) % p;
        if(uf != vf) {
            fa[uf] = vf;
            val[uf] = (t - v + p) % p;
        } else {
            if(t != v) return printf("%lld\n", i - 1), 0;
        }
    }
    printf("%lld\n", m);
    return 0;
}

/*
10 5 2
1 2 0
3 4 1
5 6 0
1 6 0
7 10 1
*/

T3

你感觉很像二分,你考虑二分爆炸能量枚举爆炸点然后用贪心 \(O(n)\) Check。

时间复杂度 \(\mathcal O(n^2 \log V)\),其中 \(V = 5 \times 10^8\),是值域。

然后这样做没有前途,因为你不知道那个爆炸点最优,这个位置也不是单调的或单峰的。

然后你考虑 DP。

我们设 \(f_i\) 表示在第 \(i\) 个位置爆炸,把前 \(i\) 个全部引爆所需要的最少能量,同理我们设一个 \(g_i\) 表示后面来的贡献。

那么答案就是 \(\displaystyle \min_{1 \le i \le n} \{ \max \{f_i, g_i \} \}\)

考虑转移:

\[f_i = \min_{j < i} \{ \max \{ a_i - a_j, \left\lceil \frac{f_j \times 3}{2} \right\rceil \} \} \]

时间复杂度 \(\mathcal O(n^2)\)

然后你不难(?)发现这个东西可以单调队列优化。

我们设 \(F(i,j) = \max \{\mid a_i - a_j \mid, \left\lceil \frac{f_j \times 3}{2} \right\rceil \} \}\)

如果 \(F(i,j) \ge F(i,k)\) 就把 \(j\) 弹出队列,因为它不满足 \(\min\) 的条件。

在加入一个位置 \(i\) 之前,如果 \(F(i,j) \ge F(i,i)\) ,就把 \(j\) 弹出队列,因为这个单调队列要维持单调递增。

然后就做完了,时间复杂度 \(\mathcal O(n)\)

/*
Work by: Suzt_ilymtics
Problem: 不知名屑题
Knowledge: 垃圾算法
Time: O(能过)
*/
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<queue>
#define int long long
#define orz cout<<"lkp AK IOI!"<<endl

using namespace std;
const int MAXN = 1e6+5;
const int INF = 1e15+7;
const int mod = 1e9+7;

int n, Ans = INF;
int a[MAXN];
int f[MAXN], g[MAXN];
int q[MAXN], head = 1, tail = 0; 

int read(){
    int s = 0, f = 0;
    char ch = getchar();
    while(!isdigit(ch))  f |= (ch == '-'), ch = getchar();
    while(isdigit(ch)) s = (s << 1) + (s << 3) + ch - '0' , ch = getchar();
    return f ? -s : s;
}

int Calcf(int i, int j) { return max(abs(a[i] - a[j]), (f[j] * 3 + 1) >> 1); }
int Calcg(int i, int j) { return max(abs(a[i] - a[j]), (g[j] * 3 + 1) >> 1); }

signed main()
{
    n = read();
    for(int i = 1; i <= n; ++i) a[i] = read();
    sort(a + 1, a + n + 1);
    memset(f, 0x3f, sizeof f);
    memset(g, 0x3f, sizeof f);
    f[0] = g[n] = 0;
    head = 1, tail = 0;
    q[++tail] = 1;
    for(int i = 2; i <= n; ++i) {
        while(head < tail && Calcf(i, q[head]) >= Calcf(i, q[head + 1])) ++ head;
        f[i] = Calcf(i, q[head]);
        while(head <= tail && Calcf(i, q[tail]) >= Calcf(i, i)) -- tail;
        q[++tail] = i;
    }
    head = 1, tail = 0;
    q[++tail] = n;
    for(int i = n - 1; i >= 1; --i) {
        while(head < tail && Calcg(i, q[head]) >= Calcg(i, q[head + 1])) ++ head;
        g[i] = Calcg(i, q[head]);
        while(head <= tail && Calcg(i, q[tail]) >= Calcg(i, i)) -- tail;
        q[++tail] = i;
    }
    for(int i = 1; i <= n; ++i) Ans = min(Ans, max(f[i], g[i]));
    printf("%lld\n", Ans);
    return 0;
}
posted @ 2021-11-17 19:04  Suzt_ilymtics  阅读(77)  评论(0编辑  收藏  举报