[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;
}
View Code

 

 


 

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;
}
View Code

 


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;
}
View Code

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;
}
View Code

 

posted @ 2022-04-03 09:24  _onglu  阅读(564)  评论(0编辑  收藏  举报