2023.02.24 模拟赛小结

2023.02.24 模拟赛小结

更好的阅读体验戳此进入

赛时思路

T1

给定序列 $ A_n $ 和模数 $ p $,求从 $ [1, n] $ 每个位置向左或右延申形成的子串的所有元素模意义下的和的最大值。

签到题,考虑维护模意义下的前缀和和后缀和,然后如对于 $ i $ 位置,假设其前缀和为 $ sum_i $,那么显然如果为了让其向右延申之后的值更大那么最优是找到一个在 $ i $ 以后的前缀和满足等于 $ p - 1 + sum_{i - 1} $,如果不存在则最优是找到小于其的任意数,如果不存在小于的那么直接找剩余的最大数即可。对于向前延伸的逆过来做一遍即可。最开始考虑的直接开个 basic_string 然后二分找,但是发现需要删除元素,然后就上了个权值线段树然后在上面乱搞,然而实际上直接用 set 就可以了。

Code

#define _USE_MATH_DEFINES
#include <bits/stdc++.h>

#define PI M_PI
#define E M_E
#define npt nullptr
#define SON i->to
#define OPNEW void* operator new(size_t)
#define ROPNEW void* Edge::operator new(size_t){static Edge* P = ed; return P++;}
#define ROPNEW_NODE void* Node::operator new(size_t){static Node* P = nd; return P++;}

using namespace std;

mt19937 rnd(random_device{}());
int rndd(int l, int r){return rnd() % (r - l + 1) + l;}
bool rnddd(int x){return rndd(1, 100) <= x;}

typedef unsigned int uint;
typedef unsigned long long unll;
typedef long long ll;
typedef long double ld;



template < typename T = int >
inline T read(void);

int N, P;
int w[110000];
int sum[110000];
int rsum[110000];
int ans[110000];
int mx(-1), rmx(-1);
basic_string < int > vals;

class SegTree{
private:
    int tr[65536 << 2];
    #define LS (p << 1)
    #define RS (LS | 1)
    #define MID ((gl + gr) >> 1)
public:
    void Pushup(int p){
        tr[p] = tr[LS] + tr[RS];
    }
    void Modify(int val, int cnt = 1, int p = 1, int gl = 0, int gr = P - 1){
        // printf("In mdf val = %d, gl = %d, gr = %d\n", val, gl, gr);
        // for(int i = 1; i <= 50000000; ++i);
        if(gl == gr)return tr[p] += cnt, void();
        if(val <= MID)Modify(val, cnt, LS, gl, MID);
        else Modify(val, cnt, RS, MID + 1, gr);
        Pushup(p);
    }
    int QueryRight(int p, int gl, int gr){
        if(gl == gr)return tr[p] ? gl = gr : -1;
        if(tr[RS])return QueryRight(RS, MID + 1, gr);
        return QueryRight(LS, gl, MID);
    }
    int QueryLeftMost(int val, int p = 1, int gl = 0, int gr = P - 1){
        if(gl == gr)return gl <= val && tr[p] ? gl : -1;
        int mx(-1);
        if(MID <= val)mx = max(mx, QueryRight(LS, gl, MID));
        else return QueryLeftMost(val, LS, gl, MID);
        return max(mx, QueryLeftMost(val, RS, MID + 1, gr));
    }
    int QueryLastRight(int p = 1, int gl = 0, int gr = P - 1){
        if(gl == gr)return tr[p] ? gl = gr : -1;
        if(tr[RS])return QueryLastRight(RS, MID + 1, gr);
        return QueryLastRight(LS, gl, MID);
    }
}st1, st2;

int main(){
    freopen("clean.in", "r", stdin);
    freopen("clean.out", "w", stdout);
    N = read(), P = read();
    for(int i = 1; i <= N; ++i)
        w[i] = read(),
        sum[i] = (sum[i - 1] + w[i]) % P,
        st1.Modify(sum[i]),
        mx = max(mx, sum[i]);
    for(int i = N; i >= 1; --i)
        rsum[i] = (rsum[i + 1] + w[i]) % P,
        st2.Modify(rsum[i]),
        rmx = max(rmx, rsum[i]);
    // sort(sum + 1, sum + N + 1);
    // for(int i = 1; i <= N; ++i)vals += sum[i];
    // sort(vals.begin(), vals.end());
    for(int i = 1, j = N; i <= N; ++i, --j){
        
        // auto it = lower_bound(vals.begin(), vals.end(), val);
        // int ans(-1);
        // if(it != vals.end())ans = max(ans, (*it - sum[i] + P) % P);
        // if(it != vals.end() && next(it) != vals.end())ans = max(ans, (*next(it) - sum[i] + P) % P);
        // if(it != vals.begin())ans = max(ans, (*prev(it) - sum[i] + P) % P);
        int val = (P - 1 + sum[i - 1]) % P;
        int ret = st1.QueryLeftMost(val);
        // printf("QLM1 i = %d, val = %d, ret = %d, sum is %d\n", i, val, ret, sum[i - 1]);
        if(!~ret)ret = st1.QueryLastRight();
        ans[i] = max(ans[i], (ret - sum[i - 1] + P) % P);
        st1.Modify(sum[i], -1);
        val = (P - 1 + rsum[j + 1]) % P;
        ret = st2.QueryLeftMost(val);
        // printf("QLM2 j = %d, val = %d, ret = %d, rsum is %d\n", j, val, ret, rsum[j + 1]);
        if(!~ret)ret = st2.QueryLastRight();
        ans[j] = max(ans[j], (ret - rsum[j + 1] + P) % P);
        st2.Modify(rsum[j], -1);
    }
    for(int i = 1; i <= N; ++i)printf("%d%c", ans[i], i == N ? '\n' : ' ');
    fprintf(stderr, "Time: %.6lf\n", (double)clock() / CLOCKS_PER_SEC);
    return 0;
}

/*
4 16
2 7 9 11
*/

template < typename T >
inline T read(void){
    T ret(0);
    int flag(1);
    char c = getchar();
    while(c != '-' && !isdigit(c))c = getchar();
    if(c == '-')flag = -1, c = getchar();
    while(isdigit(c)){
        ret *= 10;
        ret += int(c - '0');
        c = getchar();
    }
    ret *= flag;
    return ret;
}

T2

存在 $ n $ 个元素每个元素存在 $ I_i, S_i $,保证 $ I_i \le S_i $,你需要排列其以使得对于每相邻两个都有 $ S_i \gt I_{i + 1} $。

奇怪题,也没什么阳间部分分,全贪心。。不会。。

T3

题面很长不想简化了。。。。总之题意理解错了暴力寄了。

T4

存在 $ n = 2^k $ 个人,每个人有 $ h_i $ 的权值,保证给定的权值为 $ [1, n] $ 的排列,一共执行 $ k $ 轮竞赛每次奇数编号的与比其编号多 $ 1 $ 的比赛,权值更高的留下。在线地多次独立询问,给定 $ x, y $ 求对于权值为 $ x $ 的人若可以在赛前交换最多 $ y $ 次人的位置最多可以使得子集赢得多少场比赛。

拼了个 $ k = 1, k = 2 $ 的部分分,总之就是还是不会,寄。

正解

T2

个人认为这道题的关键是需要考虑到之后当前的 $ S_i $ 会对后面造成影响,$ I_i $ 会因为合法的插入已经满足对 $ i - 1 $ 的限制而失效。

看题解才发现 nt 的我忽略了 $ I_i \le S_i $,对于部分分即不存在 $ I_i = S_i $ 可以考虑直接贪心,以 $ S_i $ 为第一关键字 $ I_i $ 为第二关键字排序,这样不难发现存在 $ S_i \ge S_{i + 1} \gt I_{i + 1} $,显然满足题意。

考虑正解,对于 $ I_i = S_i $ 的花不能无脑地放在一起,因为可能可以通过在其中插一些花使得其变为合法,于是考虑贪心。

对于 $ I $ 相同的优先取 $ S $ 更大的,对于 $ I $ 不同的优先取 $ I $ 更大的,用 set 维护即可。

T3

大概就是首先把问题简化一下,发现撤销没有额外代价,于是考虑可以回收的时候回收所有能回收的点的贡献一定是最优的。

于是发现问题转换为仅有两种行进方式,即从 $ i $ 到 $ j $ 走一条均同色的道路,以及从 $ i $ 到 $ j $ 走同色道路后再走到异色 $ k $ 并回收 $ i $ 到 $ j $,代价是 $ k $ 的代价。

于是问题再次转化,发现从 $ i $ 出发到 $ k $ 与从 $ k $ 出发的代价相同,于是直接转化为从一个与 $ t $ 颜色相同的点到 $ t $ 的代价。于是用 Floyd 快速求出从 $ i $ 到 $ j $ 的只走同色路的最短路,然后处理一下再跑一遍求出来任意两点之间只按照后者形式走的代价,然后对于任意的 $ s, t $ 枚举中转点然后求一下即可。

T4

感觉后几个部分分和离线的部分分还是可想的,在线正解有点智慧了,总之跳了。。

UPD

update-2023_02_24 初稿

posted @ 2023-03-05 12:47  Tsawke  阅读(11)  评论(0编辑  收藏  举报