[gym] XXII Open Cup, Grand Prix of Daejeon
A. Points
题意:
你要维护两个二维平面上的可重点集U,V,每个点集支持插入,删除,以及查询下面这个式子的操作。
$$D(U, V) = \min\limits_{\begin{smallmatrix}(u_x, u_y) \in U \\ (v_x, v_y) \in V\end{smallmatrix}}\max(u_x + v_x, u_y + v_y)$$
题解:
比较难想到的是拆掉max,我们讨论max取左边的时候是什么条件:
$$u_x + v_x >= u_y + v_y$$
我们维护成两个关于u和v自身的量,就是
$$u_x -u_y>=v_y - v_x$$
我们把$(u_x - u_y)$作为U点集的点的id,$(v_y-v_x)$作为V点集的点的id。
我们使用线段树维护答案。
我们考虑$[l,r]$的答案是什么,我们可以取点的方案是:
1. 都取左边
2.都取右边
3.u取左,v取右
4.u取右,v取左
前两种方案是子树答案,我们考虑后两种方案怎么计算答案。我们这样取之后u的id跟v的id的取值不相交,这里就可以用到上面的性质:假如$u_{id} >= v_{id}$,答案就是$u_x + v_x$,否则答案就是$u_y+v_y$。
所以u取左,v取右的时候,答案一定是$u_x + v_x$,我们只要找出$[l, mid]$中$u_x$的最小值和$[mid + 1, r]$中$v_x$的最小值就是答案。
情况4与情况3类似,这里不做叙述。
然后,题目中维护的点集是一个可重集(就算不可重,加入id后也会出现重复)。维护这个重复的线段树不好写,我们可以直接用multiset维护每个id的最小值,更新的时候,我们先找出最小值,然后强制在线段树上更新就行了,线段树就不需要维护集合了。
这里注意的是multiset的erase方法,如果参数是一个元素的话,他会把所有相同元素删掉!!!WA45就是这个原因。
所以我们要把S.erase(x)改成S.erase(S.find(x)),这样就只会删一个元素了。
// // Created by onglu on 2022/4/3. // #include <bits/stdc++.h> #define all(a) a.begin(),a.end() #define rall(a) a.rbegin(),a.rend() #define endl '\n' #define lson (rt << 1) #define rson (rt << 1 | 1) #define Mid ((l + r) / 2) //#define int long long using namespace std; //const int N = 2e6 + 1009; const int N = 5e5 + 1009; //const int N = 5009; //const int N = 309; struct node { int x, y; }; const int B = 250010; int n, m; int x[2][N * 4], y[2][N * 4], minn[N * 4]; void update(int rt) { for(int i = 0; i < 2; i++) { x[i][rt] = min(x[i][lson], x[i][rson]); y[i][rt] = min(y[i][lson], y[i][rson]); } minn[rt] = min({minn[lson], minn[rson], y[0][lson] + y[1][rson], x[1][lson] + x[0][rson]}); } void build(int l, int r, int rt) { if(l == r) { for(int i = 0; i < 2; i++) { x[i][rt] = 0x3f3f3f3f; y[i][rt] = 0x3f3f3f3f; } minn[rt] = 0x3f3f3f3f; return ; } build(l, Mid, lson); build(Mid + 1, r, rson); update(rt); } void modify(int l, int r, int rt, int f, int a, int b, int pos) { if(l == r) { x[f][rt] = a; y[f][rt] = b; minn[rt] = min(y[0][rt] + y[1][rt], x[1][rt] + x[0][rt]); return ; } if(pos <= Mid) modify(l, Mid, lson, f, a, b, pos); else modify(Mid + 1, r, rson, f, a, b, pos); update(rt); } multiset<int> Sx[2][N], Sy[2][N]; void work() { cin >> n; build(1, N, 1); while(n--) { int opt, f, xx, yy; cin >> opt >> f >> xx >> yy; f -= 1; int pos = f ? yy - xx : xx - yy; pos += B; if(opt == 1) { Sx[f][pos].insert(xx); Sy[f][pos].insert(yy); modify(1, N, 1, f, *Sx[f][pos].begin(), *Sy[f][pos].begin(), pos); } else { Sx[f][pos].erase(Sx[f][pos].find(xx)); Sy[f][pos].erase(Sy[f][pos].find(yy)); int a, b; a = Sx[f][pos].empty() ? 0x3f3f3f3f : *Sx[f][pos].begin(); b = Sy[f][pos].empty() ? 0x3f3f3f3f : *Sy[f][pos].begin(); modify(1, N, 1, f, a, b, pos); } if(minn[1] > N) { cout << -1 << endl; } else { cout << minn[1] << endl; } } } signed main() { #ifdef LOCAL freopen("C:\\Users\\onglu\\CLionProjects\\acm\\data.in", "r", stdin); freopen("C:\\Users\\onglu\\CLionProjects\\acm\\data.out", "w", stdout); #endif ios::sync_with_stdio(false); cin.tie(0); int Case = 1; // cin >> Case; while (Case--) work(); return 0; }
B. Bingo
题意:
给定一个n*n的矩形,在里面画k个#,使得没有任意n个#同行或者同列或者在对角线上。
题解:
一开始直接占了一个对角线,喜提WA7,原因是偶数的情况,两条对角线没有交点。直接交换一下两个对角线的第一个格子就行了,左下,右上留空,然后中间是从左上到右下留空,其他填满就行,一共能填n*(n-1)格。
#include <bits/stdc++.h> using namespace std; int n, k; int g[109][109]; signed main() { cin >> n >> k; if(k == 0) { cout << "YES" << endl; for(int i = 1; i <= n; i++) { for(int j = 1; j <= n; j++) { cout << "."; } cout << endl; } return 0; } if(n == 2) { if(k == 1) { cout << "YES" << endl; cout << "#.\n.." << endl; } else { cout << "NO" << endl; } return 0; } if(k > n * n - n) { cout << "NO" << endl; return 0; } cout << "YES" << endl; g[1][n] = 1; g[n][1] = 1; for(int i = 2; i < n; i++) g[i][i] = 1; int cnt = 0; for(int i = 1; i <= n; i++) { for(int j = 1; j <= n; j++) { if(!g[i][j] && cnt < k) { cout << "#"; cnt++; } else { cout << "."; } } cout << endl; } return 0; }
C. AND PLUS OR
题意:
给定一个$2^n$长度的序列$A$,让你给出一组$i,j$使得$A_i + A_j < A_{i\and j} + A_{i\or j}$.
题解:
没做出来,官方题解写的很烂,一大半看不懂啥意思,也不给解释,下面是官方题解的个人理解。
我们首先对式子进行拆分,把i,j看成集合,按照i,j共有部分,i专有部分,j专有部分拆分成
$$x = i \cap j, y = i - x, z = j - x$$
这样之后,原式可以改写成
$$A(x + y) + A(x + z) < A(x) + A(x + y + z)$$
$$A(x + y) - A(x) < + A(x + y + z) - A(x + z)$$
我们把它看成一个关于集合的函数$f(S) = A(x + y + S) - A(x + S)$,显然原式等价于$f(\emptyset) < f(z)$
这样变化有什么用呢?
当x,y固定的时候,假设我们存在一个$z$,满足$f(\emptyset) < f(z)$,那么我们可以尝试在空集中逐位加入z的每一位,观察函数的变化
也就是我们拆分出$z_j$表示只包含z的前j个1的集合。
这样有什么用呢,我们发现由于最后满足$f(\emptyset) < f(z)$,在$j = 0 \rightarrow |z|$的过程中,一定存在一个位置$j$,使得$f(z_{j - 1}) < f(z_{j})$。
如果不满足这个条件的话,原序列显然是小于等于$f(\emptyset)$的。
这样的话,我们令$x = x + z_j, z' = z_{j + 1 \cdots |z|}$,于是我们得到一个新的解$f'(\emptyset) < f'(z')$。
因为无论如何都可以这样变化,那么我们最后一定可以得到$|z| = 1$,也就是说j可以只包含1个不在x中的另外的元素。
然后我们注意到上述变化,我们只对x和z进行操作,y是没有改变的,并且y跟z是等价的。也就是说我们应用相同的操作在y上,y也可以只包含1个不在x中的元素。
于是答案就很明确了,枚举x,再枚举两个不包含在x中的元素。
// // Created by onglu on 2022/4/3. // #include <bits/stdc++.h> #define all(a) a.begin(),a.end() #define rall(a) a.rbegin(),a.rend() #define endl '\n' #define lson (rt << 1) #define rson (rt << 1 | 1) #define Mid ((l + r) / 2) //#define int long long using namespace std; const int N = 2e6 + 1009; //const int N = 2e5 + 1009; //const int N = 5009; //const int N = 309; int n, m, a[N]; void work() { cin >> n; for(int i = 0; i < 1 << n; i++) { cin >> a[i]; } for(int x = 0; x < 1 << n; x++) { for(int y = 1; y < 1 << n; y <<= 1) if(!(x & y)) { for(int z = 1; z < 1 << n; z <<= 1) if(!(x & z) && y != z) { if(a[x + y] + a[x + z] < a[x] + a[x + y + z]) { cout << x + y << " " << x + z << endl; return ; } } } } cout << -1 << endl; return ; } signed main() { #ifdef LOCAL freopen("C:\\Users\\onglu\\CLionProjects\\acm\\data.in", "r", stdin); freopen("C:\\Users\\onglu\\CLionProjects\\acm\\data.out", "w", stdout); #endif ios::sync_with_stdio(false); cin.tie(0); int Case = 1; // cin >> Case; while (Case--) work(); return 0; }
E. Yet Another Interval Graph Problem
题意:
给定数轴上n条线段,两条线段相交表示他们之间有一条无向边。每个线段有删除的代价,问最小的删边代价使得图中的最大连通块大小不超过k。
题解:
一开始想到$f[i][j]$表示前i条边,最后一个连通块为j的最小代价,发现不好转移,原因是没办法知道最后一条边的位置在哪里,也就没办法转移。
正确做法是:把删边改成取边,这样就可以人工枚举分段点了。
$f[x]$表示x位置之前的取边的最大答案,那么我们钦定$[x + 1, i]$这一段强制连通,这样我们要使代价最大只需要取最大的k条边就行。向前扫描的时候维护最大k条边,用小根堆维护就行。
// // Created by onglu on 2022/4/3. // #include <bits/stdc++.h> #define all(a) a.begin(),a.end() #define rall(a) a.rbegin(),a.rend() #define endl '\n' #define lson (rt << 1) #define rson (rt << 1 | 1) #define Mid ((l + r) / 2) #define int long long using namespace std; //const int N = 2e6 + 1009; const int N = 2e5 + 1009; //const int N = 2509; //const int N = 309; int n, m; int f[N]; struct node { int x, y, w; }; int sum; vector<node> v, a[N], tt[N]; vector<int> t; priority_queue<int> q; void Insert(int x) { if(q.size() < m) { sum += x; q.push(-x); } else if(-q.top() < x) { sum -= -q.top(); q.pop(); sum += x; q.push(-x); } } void work() { int ans = 0; cin >> n >> m; for(int i = 1; i <= n; i++) { int a, b, c; cin >> a >> b >> c; t.push_back(a); t.push_back(b); v.push_back({a, b, c}); ans += c; } std::sort(t.begin(), t.end()); t.resize(std::unique(t.begin(), t.end()) - t.begin()); for(int i = 0; i < v.size(); i++) { v[i].x = std::lower_bound(t.begin(), t.end(), v[i].x) - t.begin() + 1; v[i].y = std::lower_bound(t.begin(), t.end(), v[i].y) - t.begin() + 1; a[v[i].y].push_back({v[i].x, v[i].y, v[i].w}); } for(int i = 1; i <= t.size(); i++) { sum = 0; while(q.size()) q.pop(); for(int j = 0; j <= i; j++) { tt[j].clear(); } for(int j = i; j > 0; j--) { for(auto r : a[j]) { tt[r.x].push_back(r); } } for(int j = i; j >= 1; j--) { for(auto r : tt[j]) { Insert(r.w); } f[i] = max(f[i], f[j - 1] + sum); } } cout << ans - f[t.size()] << endl; } signed main() { #ifdef LOCAL freopen("C:\\Users\\onglu\\CLionProjects\\acm\\data.in", "r", stdin); freopen("C:\\Users\\onglu\\CLionProjects\\acm\\data.out", "w", stdout); #endif ios::sync_with_stdio(false); cin.tie(0); int Case = 1; // cin >> Case; while (Case--) work(); return 0; }