Codeforces Round #809 (Div. 2)

Codeforces Round #809 (Div. 2)

C(贪心)

题意

有一排楼,要往上建,认为如果一座楼比它左右都高,那么就是一座酷楼。在保证这种楼最多的情况,求最少往上盖。

思路

很容易想到最多就是隔一个建一个酷楼,就是 \(0101010\) 的形式。这在总楼数为奇数的情况下是符合的。

当楼数为偶数,允许一次中间隔连续隔两个楼建酷楼。这相当于对上面的01序列切片,我们处理前后缀后枚举切片点就行。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
#include<set>
#include<queue>
#include<map>
#include<stack>
#include<string>
#include<random>
#include<iomanip>
#define yes puts("yes");
#define inf 0x3f3f3f3f
#define ll long long
#define linf 0x3f3f3f3f3f3f3f3f
#define ull unsigned long long
#define endl '\n'
#define int long long
#define rep(i,a,n) for(int i = a;i <= n;i++)
#define per(i,n,a) for(int i = n;i >= a;i--)
using namespace std;
mt19937 mrand(random_device{}());
int rnd(int x) { return mrand() % x;}
typedef pair<int,int> PII;
const int MAXN =10 + 2e5 ,mod=1e9 + 7;

void solve()
{    
    int n; cin >> n;
    vector<int> a(n + 1);
    rep(i,1,n) cin >> a[i];
    vector<int> pre(n + 1),suf(n + 2);
    for(int i = 2;i + 1 <= n;i += 2) {
        pre[i] = pre[i - 2] + max(0ll, max(a[i + 1],a[i - 1]) - a[i] + 1);
    }
    for(int i = n - 1;i > 0;i -= 2) {
        suf[i] = suf[i + 2] + max(0ll, max(a[i + 1],a[i - 1]) - a[i] + 1);
    }
    if(n & 1) cout << pre[n - 1] << endl;
    else {
        // [1,i][0,0][i + 3,n]
        int ans = pre[n - 2];
        for(int i = 0;i + 2 <= n;i += 2) 
            ans = min(ans, pre[i] + suf[i + 3]);
        cout << ans << endl;
    }
    
    
}
signed main()
{
    ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);

    int T;cin>>T;
    while(T--)
        solve();

    return 0;
}

D12(整除)

题意

给一堆数 \(a\) ,任意构造一个序列 \(p\) 求这个的最小值

\[max_{1 \le i \le n}(\lfloor\frac{a_i}{p_i} \rfloor) - min_{1 \le i \le n}(\lfloor\frac{a_i}{p_i} \rfloor) \]

思路

数据量比较小,枚举可以接受。

最小值范围是 \([0,a_{min}]\) 考虑确定最小值 \(min\) 后,\(max\) 的贡献。

贪心考虑,\(max\) 尽可能小,且满足 $ \lfloor\frac{a_i}{p_i}\rfloor \ge min$ 。变形后,\(p_i *min \le a_i\) 。我们希望 \(p_i\) 尽可能大,整除分块告诉我们, \(\lfloor \frac{a_i}{min} \rfloor\) 就是 \(p_i\) 的最大值。我们考虑所有 \(a_i\) 找出 \(max\) 计算答案即可。


顺便说一下整除分块小结论:$ 对 k = \frac{x}{y} ,有~~ \lfloor \frac{x}{k+1} \rfloor< y \le \lfloor \frac{x}{k} \rfloor$

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
#include<set>
#include<queue>
#include<map>
#include<stack>
#include<string>
#include<random>
#include<iomanip>
#define yes puts("yes");
#define inf 0x3f3f3f3f
#define ll long long
#define linf 0x3f3f3f3f3f3f3f3f
#define ull unsigned long long
#define endl '\n'
#define int long long
#define rep(i,a,n) for(int i = a;i <= n;i++)
#define per(i,n,a) for(int i = n;i >= a;i--)
using namespace std;
mt19937 mrand(random_device{}());
int rnd(int x) { return mrand() % x;}
typedef pair<int,int> PII;
const int MAXN =10 + 2e5 ,mod=1e9 + 7;

void solve()
{    
    int n,k;cin >> n >> k;
    vector<int>a(n + 1);
    rep(i,1,n) cin >> a[i];
    sort(a.begin() + 1,a.end());
    int ans = 1e9;
    for(int mn = 0;mn <= a[1];mn += 1) {
        int mx = -1;
        for(int i = 1;i <= n;i += 1) {
            // a_i / p_i >= mn
            // a_i / mn >= p_i
            int p = !mn ? k : min(k, a[i] / mn);
            mx = max(mx, a[i] / p);
        }
        ans = min(mx - mn, ans);
    }
    cout << ans << endl;
}
signed main()
{
    ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);

    int T;cin>>T;
    while(T--)
        solve();

    return 0;
}

D2 种 \(a_i\) 范围拉到 \(1e5\) ,且空间只有 \(64MB\)

不考虑空间的话,我们直接把 \(a_i\) 整除处理出来之后对 \(min\) 二分或者维护双指针都可以得到答案,但处理 \(a_i\) 整除的时空复杂度 \(O(n \sqrt n)\) 就会非常惨的 MLE了。

考虑对 \(a_i\) 求出所有可行商:\(s_1,s_2,s_3,...,s_t\)

按D1我们确定一个 \(min\) ,把求出的可行商放在一个 “值域-i” 的二维坐标系下,取 \(min\) 就是类似扫描线一样在该坐标系下画一条竖线。

对每个 \(a_i\) 的最大值 \(max_i\) 贡献分析:当 $ s_j < min < s_{j+1} $ 时,\(max_i = s_{j+1}\) ,特别的当 $j =t $ 时不合法,当 \(min < s_1\)\(max_i = \frac{a_i}{k}\)

于是我们可以维护一个 \(top[i]\) 数组表示最小值为 \(i\) 时的 \(max\) 。这里 \(max = max(max_{1,2,3,...,i,...,n})\) 。答案就是 \(min(top[i]-i)\)

在枚举求可行商时,因为 \([s_j,s_{j+1}]\) 种的最小值取值情况是一样的,因此只要单点维护 \(top[s_j+1]=max(top[s_j+1],s_{j+1})\) 即可,而不用对整个区间维护。在将 $a_1,a_2,a_3,...,a_n $ 全枚举完后取前缀 \(max\) 即可完成对未处理部分的更新。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
#include<set>
#include<queue>
#include<map>
#include<stack>
#include<string>
#include<random>
#include<iomanip>
#define yes puts("yes");
#define inf 0x3f3f3f3f
#define ll long long
#define linf 0x3f3f3f3f3f3f3f3f
#define ull unsigned long long
#define endl '\n'
// #define int long long
#define rep(i,a,n) for(int i = a;i <= n;i++)
#define per(i,n,a) for(int i = n;i >= a;i--)
using namespace std;
mt19937 mrand(random_device{}());
int rnd(int x) { return mrand() % x;}
typedef pair<int,int> PII;
const int MAXN =10 + 2e5 ,mod=1e9 + 7;

void solve()
{    
    int n,k; cin >> n >> k;
    vector<int> a(n + 1);
    rep(i,1,n) cin >> a[i];
    if(k == 1) {
        cout << a[n] - a[1] << endl;
        return;
    }
    vector<int> top(a[n] + 2);
    for(int i = 1;i <= n;i += 1) {
        vector<int> s;
        for(int j = 1;j <= k;j = 1 + (a[i] / (a[i] / j))) {
            s.push_back(a[i] / j);
            if(s.back() == 0) break;
        }
        reverse(s.begin(),s.end());
        for(int j = 0;j < s.size();j += 1) {
            if(j + 1 < s.size()) top[s[j] + 1] = max(top[s[j] + 1], s[j + 1]);
            else top[s[j] + 1] = inf;
        }
        top[0] = max(a[i] / k, top[0]);
    }
    rep(i,1,a[1]) top[i] = max(top[i - 1],top[i]);
    int ans = inf;
    rep(i,1,a[1]) ans = min(ans, top[i] - i);
    cout << ans << endl;
}
signed main()
{
    ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);

    int T;cin>>T;
    while(T--)
        solve();

    return 0;
}

枚举 \(max\) 似乎可以做到更优的复杂度,之后再看看。

E(思维,倍增)

题意

\(n\) 个点 \(m\) 条边的无向联通图,边权为边的编号。

\(q\) 次询问,每次询问 \([l,r]\) 内的点互相可达所需要用到的边权最大值的最小值。

思路

很容易想到只有最小生成树上的边有用。

在生成树上考虑,需要观察出一个结论:定义 \(f[i]\)\([i-1,i]\) 的答案,那么 \([l,r]\) 的答案就是 \(max(f[l+1],f[l+2],...,f[r])\)


证明:

对区间 \([l,r]\) 的答案为 \(k\)\(i\) 为区间内一点 ,显然有 \(k \ge f[i]\)

考虑是否有 \(k < f[i]\) ,假设小于,说明 \([i-1,i]\) 可达需要的边比 \(k\) 大,不符合 \(k\) 定义。

关于如何猜到:询问是一段区间,在和图有关的时候是很少见的,又是一个最大值问题,于是猜测有如上结论。


维护区间最值用st表,求 \([i-1,i]\) 的答案可以倍增解决。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
#include<set>
#include<queue>
#include<map>
#include<stack>
#include<string>
#include<random>
#include<iomanip>
#define yes puts("yes");
#define inf 0x3f3f3f3f
#define ll long long
#define linf 0x3f3f3f3f3f3f3f3f
#define ull unsigned long long
#define endl '\n'
#define int long long
#define rep(i,a,n) for(int i = a;i <= n;i++)
#define per(i,n,a) for(int i = n;i >= a;i--)
using namespace std;
mt19937 mrand(random_device{}());
int rnd(int x) { return mrand() % x;}
typedef pair<int,int> PII;
const int MAXN =10 + 2e5 ,mod=1e9 + 7;

void solve()
{    
    int n,m,q; cin >> n >> m >> q;
    vector<array<int,3>> edge(m + 1);
    for(int i = 1;i <= m;i += 1) {
        int u,v; cin >> u >> v;
        edge[i] = {u,v,i};
    }
    vector<int> fa(n + 1);
    iota(fa.begin(),fa.end(),0);
    function<int(int)> root = [&](int x) {
        return fa[x] == x ? x : (fa[x] = root(fa[x]));
    };
    auto getTree = [&]() {
        vector<vector<PII>> adj(n + 1);
        for(int i = 1;i <= m;i += 1) {
            auto [u,v,w] = edge[i];
            if(root(u) != root(v)) {
                adj[u].push_back({v,w});
                adj[v].push_back({u,w});
                fa[root(u)] = root(v);
            }
        }
        return adj;
    };
    auto adj = getTree();
    vector<vector<int>> par(n + 1,vector<int>(21));
    vector<vector<int>> wig(n + 1,vector<int>(21));
    vector<int> dep(n + 1);
    
    function<void(int,int,int)> dfs = [&](int u,int f,int w) {
        dep[u] = dep[f] + 1;
        par[u][0] = f;
        wig[u][0] = w;
        for(int j = 1;(1 << j) < dep[u];j += 1){
            par[u][j] = par[par[u][j - 1]][j - 1];
            wig[u][j] = max(wig[u][j - 1], wig[par[u][j - 1]][j - 1]);
        }
        for(auto [v,w] : adj[u]) if(v != f) dfs(v,u,w);
    };
    dfs(1,0,0);
    auto getDis = [&](int x,int y) {
        if(dep[x] < dep[y]) swap(x,y);
        int ans = 0;
        for(int j = 20;j >= 0;j -= 1) {
            if(dep[par[x][j]] >= dep[y]) {
                ans = max(ans, wig[x][j]);
                x = par[x][j];
            }
        }
        if(x == y) return ans;
        for(int j = 20;j >= 0;j -= 1) {
            if(par[x][j] != par[y][j]) {
                ans = max(ans,wig[x][j]);
                x = par[x][j];
                ans = max(ans, wig[y][j]);
                y = par[y][j];
            }
        }
        return max({ans, wig[x][0],wig[y][0]});
    };
    vector<vector<int>> f(n + 1,vector<int>(21));
    rep(i,2,n) f[i][0] = getDis(i - 1,i);
    for(int j = 1;j < 21;j += 1)
        for(int i = 2;i + (1 << j) - 1 <= n;i += 1)
            f[i][j] = max(f[i][j - 1], f[i + (1 << j - 1)][j - 1]);
    auto getMax = [&](int l,int r) {
        if(l > r) return 0ll; 
        int k = __lg(r - l + 1);
        return max(f[l][k],f[r - (1 << k) + 1][k]);
    };
    while(q --) {
        int l,r; cin >> l >> r;
        cout << getMax(l + 1,r) << ' ';
    }
    cout << endl;
}
signed main()
{
    ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);

    int T;cin>>T;
    while(T--)
        solve();

    return 0;
}
posted @ 2022-07-22 22:02  Mxrurush  阅读(68)  评论(0)    收藏  举报