- 寫在前面
- M 簽到
- F 笛卡爾樹 or 單調棧,dfs or ST 表,排序
- A 大力討論,結論
- G 二分答案,字首和
- C 結論,圖論,剩餘系,線性代數
- L 圖論轉化,建圖技巧,最短路
- H 括號序列,網路流
- 寫在最後
寫在前面
補題地址:https://codeforces.com/contest/2005。
以下按個人難度向排序。
復刻 CCPC 網賽開頭超順利但是三個人坐牢同一個題四個小時沒出哈哈太唐樂
M 簽到
模擬即可。
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define ull unsigned long long
const ll p = 998244353;
const int kN = 1e6 + 10;
int num, cnt[26], solved[kN][26];
std::map <std::string, int> id;
int main() {
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
int T; std::cin >> T;
while (T --) {
for (int i = 0; i < 26; ++ i) cnt[i] = 0;
num = 0;
id.clear();
int n; std::cin >> n;
while (n --) {
std::string s, t;
char name;
std::cin >> s >> name >> t;
if (t[0] != 'a') continue;
if (!id.count(s)) {
id[s] = ++ num;
for (int i = 0; i < 26; ++ i) solved[num][i] = 0;
}
int d = id[s], p = name - 'A';
if (solved[d][p]) continue;
solved[d][p] = 1;
++ cnt[p];
}
int ans = 0, c = 0;
for (int i = 0; i < 26; ++ i) if (cnt[i] > c) ans = i, c = cnt[i];
cout << (char) ('A' + ans) << "\n";
}
return 0;
}
F 笛卡爾樹 or 單調棧,dfs or ST 表,排序
場上 wenqizhi 直接高呼笛卡爾樹秒了,我一聽笛卡爾樹就嗯了一看題真就傻逼題直接秒了。
發現最優情況下,每次操作一定僅會操作兩個數,且合併的過程一定是每次找到極大的全域性最小值的區間,並依次將每個全域性最小值與相鄰的第一個大於它的值操作,直至全部變成這個值。
發現這個過程直接放到笛卡爾樹上,自底向下地根據區間長度統計貢獻即可。建樹後直接 dfs,總時間複雜度 \(O(n)\) 級別。
當然用單調棧+排序 / ST 表 + dfs 實現也可以,複雜度多一個 \(\log\)。
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define ull unsigned long long
const ll p = 998244353;
const int kN = 2e5 + 10;
int n, rt, top, a[kN], st[kN];
int lson[kN], rson[kN];
ll ans;
void dfs(int u_, int L_, int R_) {
if (lson[u_]) {
dfs(lson[u_], L_, u_ - 1);
if (a[lson[u_]] < a[u_]) ans += u_ - 1 - L_ + 1;
}
if (rson[u_]) {
dfs(rson[u_], u_ + 1, R_);
if (a[rson[u_]] < a[u_]) ans += R_ - (u_ + 1) + 1;
}
}
int main() {
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
int T; std::cin >> T;
while (T --) {
std::cin >> n;
for (int i = 1; i <= n; ++ i) std::cin >> a[i], lson[i] = rson[i] = 0;
st[top = 0] = rt = 0;
for (int i = 1; i <= n; ++ i) {
int k = top;
while (k > 0 && a[st[k]] < a[i]) -- k;
if (k) rson[st[k]] = i;
if (k < top) lson[i] = st[k + 1];
st[++ k] = i;
top = k;
}
rt = st[1];
ans = 0;
dfs(rt, 1, n);
cout << ans << "\n";
}
return 0;
}
A 大力討論,結論
顯然實際的實力值是無用的,僅需考慮有多少隊伍比中國隊弱即可,稱他們為弱弱隊。
然後場上和 dztlb 大力模擬討論下達到每個階段所需的弱弱隊數量就做完了。
一個很天才的地方是發現 8 強進 4 強,和 4 強進 2 強規則是一致的,然後發現 8 強進 4 強所需的弱弱隊數變化為 \(13 = 6\times 2 + 1\),於是大膽猜測 4 強進 2 強的變化也類似地有:\(27 = 13\times 2 + 1\)。
Code by dztlb:
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define ull unsigned long long
const ll p = 998244353;
int read()
{
int x = 0; bool f = false; char c = getchar();
while(c < '0' || c > '9') f |= (c == '-'), c = getchar();
while(c >= '0' && c <= '9') x = (x << 1) + (x << 3) + (c & 15), c = getchar();
return f ? -x : x;
}
ll qpow(ll x_, ll y_, ll mod_ = p) {
ll ret = 1;
while (y_) {
if (y_ & 1) ret = ret * x_ % mod_;
x_ = x_ * x_ % mod_, y_ >>= 1ll;
}
return ret;
}
int T;
int a[50];
int main() {
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
cin>>T;
while(T--){
for(int i=1;i<=32;++i){
cin>>a[i];
}
int cnt=0;
for(int i=2;i<=32;++i){
if(a[1]>a[i]) ++cnt;
}
if(cnt>=31){
puts("1"); continue;
}
if(cnt>=27){
puts("2"); continue;
}
if(cnt>=13){
puts("4"); continue;
}
if(cnt>=6){
puts("8"); continue;
}
if(cnt>=2){
puts("16"); continue;
}
puts("32");
}
return 0;
}
G 二分答案,字首和
對於最最佳化中位數,一個眾所周知的套路是考慮二分答案 \(\operatorname{mid}\),僅需檢查數列中不小於 \(\operatorname{mid}\) 的數的數量,是否不小於 \(\left\lfloor\frac{\operatorname{len}}{2}\right\rfloor+1\) 即可。
#include <bits/stdc++.h>
using namespace std;
#define int long long
#define ll long long
#define ull unsigned long long
const ll p = 998244353;
int read()
{
int x = 0; bool f = false; char c = getchar();
while(c < '0' || c > '9') f |= (c == '-'), c = getchar();
while(c >= '0' && c <= '9') x = (x << 1) + (x << 3) + (c & 15), c = getchar();
return f ? -x : x;
}
const int N = 2005;
int n, A[N], a[N], d[N], b[N][N];
ll sum1[N][N], sum2[N][N], c[N][N];
bool check(int mid)
{
for(int i = 1; i <= n; ++i) a[i] = (A[i] >= mid);
for(int i = 1; i <= n; ++i) sum1[i][i] = a[i];
for(int i = 1; i <= n; ++i) sum2[i][i] = b[i][i] = a[i];
for(int len = 2; len <= n; ++len)
for(int l = 1, r = l + len - 1; r <= n; ++l, ++r)
{
sum1[l][r] = sum1[l][r - 1] + a[r];
sum2[l][r] = b[l][r] = (len / 2 + 1 <= sum1[l][r]);
}
for(int r = 1; r <= n; ++r)
for(int l = 1; l <= r; ++l)
sum2[l][r] += sum2[l - 1][r];
for(int i = 1; i <= n; ++i) c[i][i] = b[i][i];
for(int len = 2; len <= n; ++len)
for(int l = 1, r = l + len - 1; r <= n; ++l, ++r)
{
c[l][r] = c[l][r - 1] + sum2[r][r] - sum2[l - 1][r];
}
int ans = 0;
for(int i = 1; i <= n; ++i)
for(int j = i; j <= n; ++j)
ans += (c[i][j] >= (j - i + 1) * (j - i + 2) / 2 / 2 + 1);
return ans >= (n * (n + 1) / 2) / 2 + 1;
}
signed main() {
n = read();
for(int i = 1; i <= n; ++i) A[i] = d[i] = a[i] = read();
sort(d + 1, d + n + 1);
int l = 1, r = n;
while(l < r)
{
int mid = (l + r + 1) >> 1;
if(check(d[mid])) l = mid;
else r = mid - 1;
}
printf("%lld\n", d[l]);
return 0;
}
C 結論,圖論,剩餘系,線性代數
牛逼提!讓我想起 ICPC2021 Jinan 的 J(這場補題在 PTA 上),賽後一看也是北大出的題那可以理解了,感覺這兩題肯定是一塊出出來的。
//
/*
By:Luckyblock
*/
#include <bits/stdc++.h>
#define LL long long
const int kN = 1e6 + 10;
//=============================================================
int n, fa[kN];
//=============================================================
int find(int x_) {
return fa[x_] == x_ ? x_ : fa[x_] = find(fa[x_]);
}
void merge(int x_, int y_) {
int fx = find(x_), fy = find(y_);
if (fx == fy) return ;
fa[fx] = fy;
}
//=============================================================
int main() {
//freopen("1.txt", "r", stdin);
std::ios::sync_with_stdio(0), std::cin.tie(0);
int T; std::cin >> T;
while (T --) {
std::cin >> n;
for (int i = 0; i <= n; ++ i) fa[i] = i;
int ans = 1;
for (int i = 1; i <= n; ++ i) {
int l, r; std::cin >> l >> r;
if (find(l - 1) == find(r)) ans = 0;
merge(l - 1, r);
}
std::cout << ans << "\n";
}
return 0;
}
L 圖論轉化,建圖技巧,最短路
//
/*
By:Luckyblock
*/
#include <bits/stdc++.h>
#define LL long long
#define pr std::pair
#define mp std::make_pair
const int kN = 2010;
//=============================================================
int n, l, q;
int fa[kN][2], into[kN];
std::vector<pr<int, int> > edge[kN];
int dis[kN][kN];
bool vis[kN];
//=============================================================
int get(char a_, char b_) {
int ret = ((int) a_ - 48) * 50 + ((int) b_ - 48);
return ret;
}
int find(int id_, int x_) {
return fa[x_][id_] == x_ ? x_ : fa[x_][id_] = find(id_, fa[x_][id_]);
}
void merge(int id_, int x_, int y_) {
int fx = find(id_, x_), fy = find(id_, y_);
if (fx == fy) return ;
fa[fx][id_] = fy;
}
void addedge(int u_, int v_, int w_) {
edge[u_].push_back(mp(v_, w_));
}
void init() {
for (int i = 1; i <= n; ++ i) edge[i].clear(), fa[i][0] = fa[i][1] = i;
for (int time = 1; time <= l; ++ time) {
std::string s; std::cin >> s;
int flag = 0;
for (int i = 1; i <= n; ++ i) {
vis[i] = 0, into[i] = 0, fa[i][1] = i;
}
for (int i = 0; i < 2 * n; i += 2) {
int x = get(s[i], s[i + 1]);
++ into[x];
if (into[x] == 2) ++ flag;
if (into[x] == 3) flag = kN;
merge(1, i / 2 + 1, x);
}
if (flag >= 2) continue;
if (flag) {
int u = 0, v1 = 0, v2 = 0;
for (int i = 0; i < 2 * n; i += 2) {
int p = i / 2 + 1, x = get(s[i], s[i + 1]);
if (into[p] == 0) u = p;
if (into[x] == 2 && v1 != 0 && v2 == 0) v2 = p;
if (into[x] == 2 && v1 == 0) v1 = p;
}
addedge(v1, u, time), addedge(v2, u, time);
} else {
for (int i = 1; i <= n; ++ i) {
if (find(0, find(1, i)) == find(0, i)) continue;
addedge(i, find(1, i), time), addedge(find(1, i), i, time);
merge(0, find(1, i), i);
}
}
}
}
int query(int a_, int b_, int c_) {
return dis[a_][b_] <= c_;
}
void dijkstra(int s_) {
std::priority_queue <pr <int, int> > q;
for (int i = 1; i <= n; ++ i) vis[i] = 0, dis[s_][i] = kN;
dis[s_][s_] = 0;
q.push(mp(0, s_));
while (!q.empty()) {
int u = q.top().second; q.pop();
if (vis[u]) continue;
vis[u] = 1;
for (auto [v, w]: edge[u]) {
if (dis[s_][v] > std::max(dis[s_][u], w)) {
dis[s_][v] = std::max(dis[s_][u], w);
q.push(mp(-dis[s_][v], v));
}
}
}
}
//=============================================================
int main() {
// freopen("1.txt", "r", stdin);
std::ios::sync_with_stdio(0), std::cin.tie(0);
int T; std::cin >> T;
while (T --) {
std::cin >> n >> l >> q;
init();
for (int i = 1; i <= n; ++ i) dijkstra(i);
while (q --) {
std::string s; std::cin >> s;
int a = get(s[0], s[1]), b = get(s[2], s[3]), c = get(s[4], s[5]);
std::cout << query(a, b, c);
}
std::cout << "\n";
}
return 0;
}
H 括號序列,網路流
見過括號序列轉換成差分約束的,這下又見到轉換成網路流的了,括號序列真是牛逼。
寫在最後
學到了什麼:
- C:有特殊的數學限制,考慮轉化成數學模型,並考慮數學模型下限制的等價形式。
- G:最最佳化中位數,套路二分答案;
- H:小範圍下,有數量的約束關係,考慮跑網路流確定方案。
然後日常夾帶私貨: