Bronze
Problem1. Lonely Photo
Description
給定一個長度為 \(n\) 的字符串 \(s\),\(s\) 僅由 G
和 H
組成,求有多少個長度不小於 \(3\) 的子串只包含一個 G
或 H
。
\(3\le n \le 5\times 10^5\)
Solution
考慮對於每一個位置 \(i\),有多少個以 \(i\) 結尾的合法的子串,不難發現子串的開頭是一段連續的,所以只需要預處理一下 \(i\) 前面第一個與 \(s_i\) 相同的位置 \(pre_i\) 和第一個與 \(s_i\) 不同的位置 \(lst_i\)
然后只需要分類討論一下以 \(i\) 為結尾的子串中出現一次的字符是 G
還是 H
。
- \(s_i=s_{i-1}\) 或 \(s_{i-1}\neq s_{i-2}\) 時,也就是只出現一次的字符不是 \(s_i\),那么我們需要找到上一個與 \(s_i\) 不同的字符 \(s_j\),和 \(s_j\) 前面第一個與 \(s_j\) 相同的字符,中間都是合法的。
- 否則,也就是只出現一次的字符是 \(s_i\),那么直接找到上一個與 \(s_i\) 相同的即可。
算是銅組最惡心的一道了吧,其實也不難就是細節比較多。
Code
#include <bits/stdc++.h>
#define ll long long
#define db double
#define gc getchar
#define pc putchar
using namespace std;
namespace IO
{
template <typename T>
void read(T &x)
{
x = 0; bool f = 0; char c = gc();
while(!isdigit(c)) f |= c == '-', c = gc();
while(isdigit(c)) x = x * 10 + c - '0', c = gc();
if(f) x = -x;
}
template <typename T>
void write(T x)
{
if(x < 0) pc('-'), x = -x;
if(x > 9) write(x / 10);
pc('0' + x % 10);
}
}
using namespace IO;
const int N = 5e5 + 5;
int n, pre[N], lst[N];
char s[N];
ll ans;
int main()
{
read(n);
scanf("%s", s + 1);
int x = 0, y = 0;
pre[n + 1] = n;
for(int i = 1; i <= n; i++)
{
if(s[i] == 'G')
{
pre[i] = x;
lst[i] = y ? y : n + 1;
x = i;
}
else
{
pre[i] = y;
lst[i] = x ? x : n + 1;
y = i;
}
}
for(int i = 3; i <= n; i++)
{
if(s[i - 1] == s[i] || s[i - 2] != s[i - 1]) ans += max(min(lst[i], i - 2) - pre[lst[i]], 0);
else ans += max(i - pre[i] - 2, 0);
}
write(ans), pc('\n');
return 0;
}
// A.S.
Problem2. Air Cownditioning
Description
給定兩個長為 \(n\) 的序列 \(a,b\),每次操作可以將 \(a\) 序列中連續的一段就、 \(+1\) 或 \(-1\),求最少需要操作多少次。
\(1\le n \le 10^5\)
Solution
直接做差,然后就轉化成典中典了
就是有正有負,通過手玩不難發現每次操作不可能同時操作正負,分成兩邊單獨操作一樣是最優的
所以只需要指針掃一遍找出每個正、負區間,然后跑一遍單調棧即可。
Code
#include <bits/stdc++.h>
#define ll long long
#define db double
#define gc getchar
#define pc putchar
using namespace std;
namespace IO
{
template <typename T>
void read(T &x)
{
x = 0; bool f = 0; char c = gc();
while(!isdigit(c)) f |= c == '-', c = gc();
while(isdigit(c)) x = x * 10 + c - '0', c = gc();
if(f) x = -x;
}
template <typename T>
void write(T x)
{
if(x < 0) pc('-'), x = -x;
if(x > 9) write(x / 10);
pc('0' + x % 10);
}
}
using namespace IO;
const int N = 1e6 + 5;
int n, a[N];
int stk[N], top;
ll ans;
int main()
{
read(n);
for(int i = 1; i <= n; i++) read(a[i]);
for(int i = 1, b; i <= n; i++) read(b), a[i] = b - a[i];
for(int l = 1, r; l <= n; l = r + 1)
{
r = l + 1;
while(r <= n && a[r] * a[r - 1] > 0) r++;
r--;
top = 0;
for(int i = l; i <= r; i++)
{
a[i] = abs(a[i]);
if(a[i] > stk[top]) ans += a[i] - stk[top];
while(top && stk[top] > a[i]) top--;
stk[++top] = a[i];
}
}
write(ans), pc('\n');
return 0;
}
// A.S.
Problem3. Walking Home
Description
給定一個 \(n\times n\) 的矩陣,.
可以通過,H
不可以通過,只能向右或向下走,求在至多轉向 \(k\) 次的前提下從左上角走到右下角有多少種方案。
\(2\le n \le 50,1\le k\le 3\)
Solution
很容易想到狀態
\(f_{i,j,k,d}\) 表示從 \((1,1)\) 走到 \((i,j)\) 共轉向 \(k\) 次,走過來后面向 \(d(0右,1下)\) 的方案數。
轉移也比較簡單,對於 \(d=0/1\) 分類討論即可。
Code
#include <bits/stdc++.h>
#define ll long long
#define db double
#define gc getchar
#define pc putchar
using namespace std;
namespace IO
{
template <typename T>
void read(T &x)
{
x = 0; bool f = 0; char c = gc();
while(!isdigit(c)) f |= c == '-', c = gc();
while(isdigit(c)) x = x * 10 + c - '0', c = gc();
if(f) x = -x;
}
template <typename T>
void write(T x)
{
if(x < 0) pc('-'), x = -x;
if(x > 9) write(x / 10);
pc('0' + x % 10);
}
}
using namespace IO;
const int N = 55;
int n, m;
char s[N][N];
int f[N][N][10][2];
void solve()
{
memset(f, 0, sizeof(f));
read(n), read(m);
for(int i = 1; i <= n; i++)
scanf("%s", s[i] + 1);
f[1][0][0][0] = f[0][1][0][1] = 1;
for(int i = 1; i <= n; i++)
{
if(s[1][i] == '.') f[1][i][0][0] = f[1][i - 1][0][0];
if(s[i][1] == '.') f[i][1][0][1] = f[i - 1][1][0][1];
}
for(int i = 2; i <= n; i++)
for(int j = 2; j <= n; j++)
{
if(s[i][j] == 'H') continue;
for(int k = 0; k <= m; k++)
{
f[i][j][k][0] += f[i][j - 1][k][0];
if(k) f[i][j][k][0] += f[i][j - 1][k - 1][1];
f[i][j][k][1] += f[i - 1][j][k][1];
if(k) f[i][j][k][1] += f[i - 1][j][k - 1][0];
}
}
ll ans = 0;
for(int i = 0; i <= m; i++)
ans += f[n][n][i][0] + f[n][n][i][1];
write(ans), pc('\n');
}
int main()
{
int T; read(T);
while(T--) solve();
return 0;
}
// A.S.
Silver
Problem1. Closest Cow Wins
Description
在一條數軸上,有 \(k\) 個點有權值, \(p_i\) 位置的權值為 \(t_i\),現在,對方已經占了 \(m\) 個位置 \(f_1,f_2,\dots f_m\),你要占 \(n\) 個位置(可以是小數),求最大的權值和為多少。
對於每個點,當這個點距離你占的最近位置小於對方占的最近位置時,你可以獲得這個點的權值。
\(1\le k \le 2\times 10^5,1\le m\le 2\times 10^5\),所有這些 \(k+m\) 個位置均是 \([0,10^9]\) 內的不同整數。
Solution
考慮對方占的每兩個位置之間
當在這個區間內放 \(2\) 個點時,是可以得到這個區間內所有點的權值的。
所以只需要計算只放 \(1\) 個點時,最多能得到多少權值。
因為在這個區間內的點距離對面占的最近位置就是到兩端的最小值,所以我們可以知道要想得到任意一個點的權值需要在哪個區間占位置
畫一下圖發現從左到右的點需要的區間的左右端點都是遞增的,所以就可以雙指針亂掃了(
兩個指針 \(i,j\),表示欽定得到 \(i\) 的權值時,最遠能得到 \(j\) 的權值,這里顯然貪心地占能得到 \(i\) 的最靠右的位置,然后前綴和減一下即可得到之間的權值和。
我們有了在每個區間內占 \(1/2\) 個位置能得到的權值,接下來考慮如何占位置。
這里需要反悔貪心,先把占一個的權值都加到大根堆里,然后在被選時,將占兩個的權值減去占一個的權值加到大根堆里,這樣在下次選到時相當於占了兩個。
代碼細節比較多,二分也需要寫兩遍,一個搜大於等於某個坐標的第一個點,另一個搜小於等於某個坐標的第一個點。
Code
#include <bits/stdc++.h>
#define ll long long
#define db double
#define gc getchar
#define pc putchar
#define fi first
#define se second
using namespace std;
namespace IO
{
template <typename T>
void read(T &x)
{
x = 0; bool f = 0; char c = gc();
while(!isdigit(c)) f |= c == '-', c = gc();
while(isdigit(c)) x = x * 10 + c - '0', c = gc();
if(f) x = -x;
}
template <typename T>
void write(T x)
{
if(x < 0) pc('-'), x = -x;
if(x > 9) write(x / 10);
pc('0' + x % 10);
}
}
using namespace IO;
typedef pair<ll, int> P;
const int N = 2e5 + 5;
int k, m, n, b[N];
struct grass
{
int p, t;
friend bool operator < (grass x, grass y)
{
return x.p < y.p;
}
} a[N];
ll sum[N], val1[N], val2[N];
bool flag[N];
int SearchL(int x)
{
int l = 0, r = k, pos = k;
while(l <= r)
{
int mid = (l + r) >> 1;
if(a[mid].p >= x) pos = mid, r = mid - 1;
else l = mid + 1;
}
return pos;
}
int SearchR(int x)
{
int l = 0, r = k, pos = 1;
while(l <= r)
{
int mid = (l + r) >> 1;
if(a[mid].p <= x) pos = mid, l = mid + 1;
else r = mid - 1;
}
return pos;
}
ll getsum(int l, int r)
{
if(b[l] >= b[r]) return 0;
int x = SearchL(b[l]), y = SearchR(b[r]);
return x > y ? 0 : sum[y] - sum[x - 1];
}
ll calc(int l, int r)
{
int x = SearchL(b[l]), y = SearchR(b[r]);
ll res = 0;
for(int i = x, j = x; i <= y; i++)
{
int lst = a[i].p + min(a[i].p - b[l], b[r] - a[i].p);
while(j <= y && a[j].p - min(a[j].p - b[l], b[r] - a[j].p) < lst) j++;
res = max(res, sum[j - 1] - sum[i - 1]);
}
return res;
}
int main()
{
read(k), read(m), read(n);
for(int i = 1; i <= k; i++) read(a[i].p), read(a[i].t);
for(int i = 1; i <= m; i++) read(b[i]);
sort(a + 1, a + 1 + k);
sort(b + 1, b + 1 + m);
for(int i = 1; i <= k; i++) sum[i] = sum[i - 1] + a[i].t;
b[0] = 0, b[m + 1] = max(b[m], a[k].p), m++;
val1[1] = getsum(0, 1);
val1[m] = getsum(m - 1, m);
for(int i = 2; i < m; i++)
{
val1[i] = calc(i - 1, i);
val2[i] = getsum(i - 1, i);
}
priority_queue <P> q;
for(int i = 1; i <= m; i++)
q.push(P(val1[i], i));
ll ans = 0;
while(n && !q.empty())
{
--n;
P p = q.top();
q.pop();
ans += p.fi;
if(!flag[p.se]) q.push(P(val2[p.se] - val1[p.se], p.se));
flag[p.se] = 1;
}
write(ans), pc('\n');
return 0;
}
// A.S.
Problem2. Connecting Two Barns
Description
\(T\) 組數據,每組數據給定 \(n\) 個點 \(m\) 條無向邊,現在至多建兩條邊,使得從 \(1\) 到 \(n\) 有路徑,求建邊的最小權值。
建一條從 \(i\) 到 \(j\) 的邊的權值為 \((i-j)^2\)
\(1\le T \le 20,1\le n \le 10^5,\sum(n+m)\le 5\times 10^5\)
Solution
先用並查集求出每個連通塊,然后只需要求出 \(1\) 和 \(n\) 所在連通塊到其他連通塊的最短距離,枚舉一遍求最小值即可。
在求最短距離時可以直接枚舉每個點,在 \(1\) 和 \(n\) 所在連通塊內二分找最小值
但我考場上沒看見最后一個數據范圍,就以為 \(O(Tn\log n)\) 過不去,然后寫雙指針亂掃,結果還沒二分快 qaq
(事實證明拿指針掃是可以的,大概是我寫假了
Code
#include <bits/stdc++.h>
#define ll long long
#define db double
#define gc getchar
#define pc putchar
#define pb push_back
using namespace std;
namespace IO
{
template <typename T>
void read(T &x)
{
x = 0; bool f = 0; char c = gc();
while(!isdigit(c)) f |= c == '-', c = gc();
while(isdigit(c)) x = x * 10 + c - '0', c = gc();
if(f) x = -x;
}
template <typename T>
void write(T x)
{
if(x < 0) pc('-'), x = -x;
if(x > 9) write(x / 10);
pc('0' + x % 10);
}
}
using namespace IO;
const int N = 1e5 + 5;
int n, m, f[N], id[N], cnt;
vector <int> vec[N];
int Find(int a)
{
return f[a] == a ? a : f[a] = Find(f[a]);
}
int d1[N], d2[N];
void work()
{
read(n), read(m);
for(int i = 1; i <= n; i++) f[i] = i;
for(int i = 1; i <= m; i++)
{
int u, v; read(u), read(v);
if(Find(u) != Find(v)) f[Find(v)] = Find(u);
}
if(Find(1) == Find(n))
{
puts("0");
return;
}
cnt = 0;
for(int i = 1; i <= n; i++)
{
int x = Find(i);
if(!id[x]) id[x] = ++cnt;
vec[id[x]].pb(i);
}
int S = id[Find(1)], T = id[Find(n)];
memset(d1, 0x3f, sizeof(d1));
memset(d2, 0x3f, sizeof(d2));
for(int i = 1; i <= n; i++)
{
int x = id[Find(i)];
int pos = lower_bound(vec[S].begin(), vec[S].end(), i) - vec[S].begin();
d1[x] = min(d1[x], min(pos ? abs(i - vec[S][pos - 1]) : n, pos < (int)vec[S].size() ? abs(i - vec[S][pos]) : n));
pos = lower_bound(vec[T].begin(), vec[T].end(), i) - vec[T].begin();
d2[x] = min(d2[x], min(pos ? abs(i - vec[T][pos - 1]) : n, pos < (int)vec[T].size() ? abs(i - vec[T][pos]) : n));
}
ll ans = 1ll * (n - 1) * (n - 1);
for(int i = 1; i <= cnt; i++)
ans = min(ans, 1ll * d1[i] * d1[i] + 1ll * d2[i] * d2[i]);
write(ans), pc('\n');
memset(id, 0, sizeof(id));
for(int i = 1; i <= cnt; i++) vec[i].clear();
return;
}
signed main()
{
int T; read(T);
while(T--) work();
return 0;
}
// A.S.
Problem3. Convoluted Intervals
Description
給定 \(n\) 個區間,第 \(i\) 個區間為 \([a_i,b_i]\),在其中選兩個區間(可以重復),對於 \(0\le k \le 2m\) 的每個 \(k\),求滿足 \(a_i+a_j\le k \le b_i+b_j\) 的方案數。
\(1\le n\le 2\times 10^5,1\le m\le 5000,a_i\le b_i\)
Solution
只要你發現 \(n\le 2\times10^5\),而 \(m\le 5000\),這道題你就做出來一半了(
因此我們可以開個桶,然后 \(O(m^2)\) 枚舉值域,將 \(a_i+a_j=k\) 和 \(b_i+b_j=k\) 的方案數都求出來,注意當 \(a_i=a_j\) 時要特殊處理一下。
考慮 \(k\) 變成 \(k+1\) 有什么變化,只需要將 \(a_i+a_j=k+1\) 的方案數加上,將 \(b_i+b_j=k\) 的方案數減掉。
也相當於差分,可以求出差分數組后做個前綴和。
Code
#include <bits/stdc++.h>
#define ll long long
#define db double
#define gc getchar
#define pc putchar
using namespace std;
namespace IO
{
template <typename T>
void read(T &x)
{
x = 0; bool f = 0; char c = gc();
while(!isdigit(c)) f |= c == '-', c = gc();
while(isdigit(c)) x = x * 10 + c - '0', c = gc();
if(f) x = -x;
}
template <typename T>
void write(T x)
{
if(x < 0) pc('-'), x = -x;
if(x > 9) write(x / 10);
pc('0' + x % 10);
}
}
using namespace IO;
const int N = 2e5 + 5;
int n, m, a[N], b[N];
ll ca[N], cb[N];
ll l[N], r[N];
int main()
{
read(n), read(m);
for(int i = 1; i <= n; i++) read(a[i]), read(b[i]), ca[a[i]]++, cb[b[i]]++;
for(int i = 0; i <= m; i++)
for(int j = 0; j <= m; j++)
if(i != j) l[i + j] += ca[i] * ca[j], r[i + j] += cb[i] * cb[j];
for(int i = 0; i <= m; i++) l[i + i] += ca[i] * ca[i], r[i + i] += cb[i] * cb[i];
ll ans = 0;
for(int i = 0; i <= m + m; i++)
ans += l[i], write(ans), pc('\n'), ans -= r[i];
return 0;
}
// A.S.