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 \} \}\) 。
考虑转移:
时间复杂度 \(\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;
}