[gym] XXII Open Cup, Grand Prix of Daejeon

A. Points

题意:

你要维护两个二维平面上的可重点集U,V,每个点集支持插入,删除,以及查询下面这个式子的操作。

D(U,V)=min(ux,uy)U(vx,vy)Vmax(ux+vx,uy+vy)

 

题解:

比较难想到的是拆掉max,我们讨论max取左边的时候是什么条件:

ux+vx>=uy+vy

我们维护成两个关于u和v自身的量,就是

uxuy>=vyvx

我们把(uxuy)作为U点集的点的id,(vyvx)作为V点集的点的id。

我们使用线段树维护答案。

我们考虑[l,r]的答案是什么,我们可以取点的方案是:

1. 都取左边

2.都取右边

3.u取左,v取右

4.u取右,v取左

前两种方案是子树答案,我们考虑后两种方案怎么计算答案。我们这样取之后u的id跟v的id的取值不相交,这里就可以用到上面的性质:假如uid>=vid,答案就是ux+vx,否则答案就是uy+vy

所以u取左,v取右的时候,答案一定是ux+vx,我们只要找出[l,mid]ux的最小值和[mid+1,r]vx的最小值就是答案。

情况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

题意:

给定一个2n长度的序列A,让你给出一组i,j使得Ai+Aj<Aij+Aij.

题解:

没做出来,官方题解写的很烂,一大半看不懂啥意思,也不给解释,下面是官方题解的个人理解。

我们首先对式子进行拆分,把i,j看成集合,按照i,j共有部分,i专有部分,j专有部分拆分成

x=ij,y=ix,z=jx

这样之后,原式可以改写成

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()<f(z)

这样变化有什么用呢?

当x,y固定的时候,假设我们存在一个z,满足f()<f(z),那么我们可以尝试在空集中逐位加入z的每一位,观察函数的变化

也就是我们拆分出zj表示只包含z的前j个1的集合。

这样有什么用呢,我们发现由于最后满足f()<f(z),在j=0|z|的过程中,一定存在一个位置j,使得f(zj1)<f(zj)

如果不满足这个条件的话,原序列显然是小于等于f()的。

这样的话,我们令x=x+zj,z=zj+1|z|,于是我们得到一个新的解f()<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 @   _onglu  阅读(573)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 地球OL攻略 —— 某应届生求职总结
· 提示词工程——AI应用必不可少的技术
· Open-Sora 2.0 重磅开源!
· 周边上新:园子的第一款马克杯温暖上架
点击右上角即可分享
微信分享提示