1117GRYZ模擬賽解題報告
期望得分:\(60+100+60 = 220pts\)
實際得分:\(80+70+60 = 210pts\)
T1
大概是發現如何判斷一個顏色是否覆蓋了其他顏色。
可以簡單歸納一下,
- 對於每種顏色處理出他的四個邊界,然後標記這四個邊界所構成的矩形出現的其他顏色。
- 需要特判的是,全域性只有一個顏色,而 \(k > 1\)。
然後你暴力標記的話是 \(\mathcal O(knm)\) 的。
你考慮二維差分 + 二位字首和。如果一個位置被加了兩次,那這個位置的顏色就是不合法的。
時間複雜度 \(\mathcal O(nm)\) ,可以通過。
因為這裡資料範圍寫的是 \(n \times m \le 10^5\)
/* 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;
}