好久没加训了......
A、MEX Table
求每一行和每一列的mex的和,显然对于mex操作,首先需要关注 ‘0’所在的位置。
然后发现对于不包括‘0’的行和列来说,mex操作之后对答案的贡献都为0,因此,我们
只需要关注‘0’所在的行和列。如果我们选择将该行的mex值增加,那么显然会占用1,2...等数。
那么对于列的话,贡献就只能为1。
#include<bits/stdc++.h>
using namespace std;
void solve(){
int n, m;
cin >> n >> m;
cout << max(n, m) + 1 << "\n";
return;
}
int main(){
int t;
cin >> t;
while(t--)solve();
return 0;
}
B、Gorilla and the Exam
首先执行k次操作,这些操作能够将数组中的一个数变为任意数。
然后执行删除操作,求使数组为空的最小删除操作次数。
注意删除操作的性质—— 一次操作能够将数组中所有最小值删除。
也就是说,每次操作能够删除一种数,那么问题变成运用k次操作,使得数组中的数的种类最少。
显然的思路是按照数的频率排序,然后依次进行k次操作即可。
#include<bits/stdc++.h>
using namespace std;
void solve(){
int n, k;
cin >> n >> k;
map<int,int>cnt;
int x;
for(int i = 0; i < n; i++){
cin >> x;
cnt[x]++;
}
vector<int>oc;
for(auto [x, y] : cnt){
oc.push_back(y);
}
sort(oc.begin(), oc.end());
int ans = 1;
for(int i = 0; i < oc.size() - 1; i++){
if(k >= oc[i])k -= oc[i];
else ans++;
}
cout << ans << "\n";
return;
}
int main(){
int t;
cin >> t;
while(t--)solve();
return 0;
}
C、Trip to the Olympiad
构造题 + 状态压缩
首先对于二进制问题,通常你可以先尝试将每一位单独处理——拆位。
对于题目中的式子,选三个数a, b, c,求max((a ^ b) + (a ^ c) + (b ^ c))
然后考虑每一位的贡献,通过异或运算的性质我们发现对于第i个比特位,当a, b, c的第i位都为0或者都为1时,对答案的贡献为0,当有两个0一个1或者两个1一个0时,对答案的贡献为2 * (1 << i)
因此,在最大化答案的情况下,我们需要尽量满足上述要求。
#include<bits/stdc++.h>
#define int long long
using namespace std;
void solve(){
int l, r;
cin >> l >> r;
vector<int>bitl(31), bitr(31);
for(int i = 0; i < 31; i++){
bitl[i] = l & (1 << i) ? 1 : 0;
bitr[i] = r & (1 << i) ? 1 : 0;
}
int st = 30;
int a = 0, b = 0, c = 0;
while(st >= 0 && bitl[st] == bitr[st]){
a += bitr[st] * (1 << st);
b += bitr[st] * (1 << st);
c += bitr[st] * (1 << st);
st--;
}
//刚好后面全为0
if(r % (1 << st) == 0){
a = r;
b = r - 1;
c = r - 2;
}
else {
a = r;
//将b的第st位变为1,后面全为0
b += 1 << st;
//将c的第st位变为0,后面全为1
c += (1 << st) - 1;
}
cout << a << ' ' << b << ' ' << c << "\n";
return;
}
signed main(){
int t;
cin >> t;
while(t--)solve();
return 0;
}
D、Gifts Order
显然,我们最终选择的区间一定是两个区间端点分别是最大值和最小值的区间
因为再扩张区间只会使答案变小。
通过上图的式子变换,我们可以发现我们需要维护a[i] + i 和a[i] - i两个值,然后分别维护其最大最小值即可得到答案。
#include<bits/stdc++.h>
#define ls (u<<1)
#define rs (ls|1)
#define mid ((l+r)>>1)
using namespace std;
const static int INF = 2e9;
struct node{
int val;
int mxv1, mxv2, mnv1, mnv2;
};
vector<node>tr;
vector<int>a;
void push_up(int u){
tr[u].val = max(tr[ls].mxv1 - tr[rs].mnv1, tr[rs].mxv2 - tr[ls].mnv2);
tr[u].val = max(tr[u].val, max(tr[ls].val, tr[rs].val));
tr[u].mnv1 = min(tr[ls].mnv1, tr[rs].mnv1);
tr[u].mnv2 = min(tr[ls].mnv2, tr[rs].mnv2);
tr[u].mxv1 = max(tr[ls].mxv1, tr[rs].mxv1);
tr[u].mxv2 = max(tr[ls].mxv2, tr[rs].mxv2);
}
void build(int u, int l, int r){
if(l == r){
tr[u].mnv1 = a[l] + l;
tr[u].mnv2 = a[l] - l;
tr[u].mxv1 = a[l] + l;
tr[u].mxv2 = a[l] - l;
tr[u].val = -INF;
return;
}
build(ls, l, mid);
build(rs, mid + 1, r);
push_up(u);
}
void add(int u, int l, int r, int idx){
if(l == r){
tr[u].mnv1 = a[idx] + idx;
tr[u].mnv2 = a[idx] - idx;
tr[u].mxv1 = a[idx] + idx;
tr[u].mxv2 = a[idx] - idx;
tr[u].val = -INF;
return;
}
if(idx <= mid)add(ls, l, mid, idx);
else add(rs, mid + 1, r, idx);
push_up(u);
}
void solve(){
int n, q;
cin >> n >> q;
tr.clear();
a.clear();
tr.resize((n << 2) + 10);
a.resize(n + 1);
for(int i = 1; i <= n; i++)cin >> a[i];
build(1, 1, n);
cout << max(0, tr[1].val) << "\n";
while(q--){
int pos, x;
cin >> pos >> x;
a[pos] = x;
add(1, 1, n, pos);
cout << max(0, tr[1].val) << "\n";
}
return;
}
int main(){
int t;
cin >> t;
while(t--)solve();
return 0;
}
E、Another Exercise on Graphs
Floyd算法 + 类似并查集加边
思路跟并查集加边类似,不过这个是去边(bushi)
首先先将全部边加入集合,然后将边按边权排序,按边权从小到大枚举要去除的边。
Floyd实时更新最短路,同时,需要保存每次去除一条边的状态。
显然,去除(边权改为0)的边越多,a到b的最短路越短。
因此对于特定的一对点(a, b),我们保存了每次去除边的f[i][a][b],去除前i条边的最短路
每次查询进行二分即可。
优化:如果再去除当前边之前,(a, b)的最短路已经为0,那么就不需要再更新f[i][a][b]了。
#include <bits/stdc++.h>
using namespace std;
const static int INF = 1e9;
bool cmp(array<int, 3> x, array<int, 3> y){
return x[2] < y[2];
}
void solve() {
int n, m, q;
cin >> n >> m >> q;
vector<array<int, 3>> e;
vector<vector<int>>flag(n, vector<int>(n, 0));
for (int i = 0; i < m; i++) {
int x, y, w;
cin >> x >> y >> w;
x--, y--;
e.push_back({x, y, w});
//标记边是否产生贡献
flag[x][y] = 1;
flag[y][x] = 1;
}
vector<vector<vector<int>>>dis(1, vector<vector<int>>(n, vector<int>(n, INF)));
for (int i = 0; i < n; i++) {
dis[0][i][i] = 0;
for (int j = 0; j < n; j++) {
if(flag[i][j])
dis[0][i][j] = 1;
}
}
for (int k = 0; k < n; k++) {
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
dis[0][i][j] = min(dis[0][i][j], dis[0][i][k] + dis[0][k][j]);
}
}
}
sort(e.begin(), e.end(), cmp);
//删除边集合
vector<int> del;
//从小到大枚举边权
for (int i = 0; i < m;) {
int j = i + 1;
while(j < m && e[i][2] == e[j][2])j++;
bool ok = true;
int last = del.size();
for (int k = i; k < j; k++) {
int x = e[k][0], y = e[k][1];
flag[x][y] = 0;
flag[y][x] = 0;
//已经无边可减
if (dis[last][x][y] == 0) continue;
if (ok) {
ok = false;
del.push_back(e[i][2]);
dis.push_back(dis[last]);
}
int ns = del.size();
dis[ns][x][y] = dis[ns][y][x] = 0;
for (int t1 = 0; t1 < n; t1++) {
for (int t2 = 0; t2 < n; t2++) {
dis[ns][t1][t2] = min(dis[ns][t1][t2], dis[ns][t1][x] + dis[ns][t2][y]);
dis[ns][t1][t2] = min(dis[ns][t1][t2], dis[ns][t2][x] + dis[ns][t1][y]);
}
}
}
//路径长度不超过n - 1
if (del.size() > n + 2) break;
i = j;
}
while(q--){
//将边权置为0的边越多,a[i][j][j]的值递减
//二分答案
int x, y, k;
cin >> x >> y >> k;
x--, y--;
int l = 0, r = dis.size() - 1;
//最初状态
if (dis[0][x][y] < k)
cout << del[0] << " ";
else {
while (l + 1 < r) {
int mid = (l + r) >> 1;
if (dis[mid][x][y] < k)
r = mid;
else
l = mid;
}
//assert(r != 0);
cout << del[r - 1] << " ";
}
}
cout << endl;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int t;
cin >> t;
while(t--)solve();
return 0;
}