2023.02.04 模拟赛小结

2023.02.04 模拟赛小结

更好的阅读体验戳此进入

赛时思路

T1

给定序列 $ a_i $,将序列的位置 $ i $ 染色将会获得 $ a_i $ 的贡献,但要求对于任意两个染色的位置 $ i, j $ 满足 $ a_i \le a_j $,同时对于连续 $ m $ 个未染色的位置会失去 $ \dfrac{m(m + 1)}{2} $ 的贡献,最大化贡献,求最大值。

emm 第一眼想到的是 $ \texttt{2D0D} $ 的 DP,就是设 $ dp(i, j, 0/1) $ 表示前 $ i $ 个最后染色的价值为 $ j $ 然后末尾是否染色,这个东西离散化一下理论是 $ O(n^2) $ 的,然后发现无法转移,想到添加一维 $ k $ 表示末尾连续多少个未染色的,想转移时发现 $ j $ 和 $ 0/1 $ 就没用了,于是最终 $ \texttt{2D0D} $ 的 DP 是令 $ dp(i, j) $ 表示前 $ i $ 个结尾连续 $ j $ 个未染色的最大贡献,转移显然:

\[dp(i, 0) = \max_{j \in [0, i - 1], a_i \ge a_{i - j - 1}} dp(i - 1, j) + a_i \]

\[dp(i, j) = dp(i - 1, j - 1) + \dfrac{(j - 1)j}{2} - \dfrac{j(j + 1)}{2} (j \neq 0) \]

后者整理得:

\[dp(i, j) = dp(i - 1, j - 1) - j \]

前者二维偏序可以优化到 $ O(n) $,后者发现化简完似乎并没有什么优化的方式,于是寄掉,最终 $ 41\texttt{pts} $。

Upd:也可以强行优化,发现 $ dp(i, j)(j \neq 0) $ 都可以 $ O(1) $ 求,对于 $ dp(i, 0) $ 的转移将后者所有全部转化为 $ dp(j, 0) + C $ 的形式,此时就和 $ \texttt{1D1D} $ 的方程十分相似,二维偏序然后 cdq 维护一下即可。

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;

#define INF (0x3f3f3f3f)

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

int N;

namespace Sub_1_2{
    ll A[5100];
    ll dp[5100][5100];
    void Make(void){
        memset(dp, 0xc0, sizeof dp);
        for(int i = 1; i <= N; ++i)A[i] = read();
        A[0] = -INF;
        dp[1][0] = A[1], dp[1][1] = -1;
        for(int i = 2; i <= N; ++i){
            for(int j = 0; j <= i - 1; ++j)
                if(A[i] >= A[i - 1 - j])
                    dp[i][0] = max(dp[i][0], dp[i - 1][j] + A[i]);
            for(int j = 1; j <= i; ++j)
                dp[i][j] = dp[i - 1][j - 1] + (((j - 1) * j) >> 1) - ((j * (j + 1)) >> 1);
        }
        ll ans(-INF);
        for(int j = 0; j <= N; ++j)ans = max(ans, dp[N][j]);
        printf("%lld\n", ans);
    }
}
namespace Sub_3_4{
    ll A[1100000];
    void Make(void){
        for(int i = 1; i <= N; ++i)A[i] = read();

    }
}
int main(){
    freopen("paint.in", "r", stdin);
    freopen("paint.out", "w", stdout);
    N = read();
    if(N <= 10000)Sub_1_2::Make();
    else exit(1);
    fprintf(stderr, "Time: %.6lf\n", (double)clock() / CLOCKS_PER_SEC);
    return 0;
}

/*
7
1 3 2 7 3 2 4

7
-3 -4 -2 -2 -6 -8 -1
*/

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

给定字符串 $ S $,存在 $ n - 2 $ 次修改操作,第 $ i $ 次会将 $ i + 1 $ 的字符变为 $ w_i $,每次求最长回文子串,包括未修改时,求出对于所有答案中最大值。

基于 LG-P4324 [JSOI2016]扭动的回文串 修改的。

赛时一看默认这是 PAM 之类的东西(犇犇 @sssmzy 真的写了个 PAM)于是就跳了,写了个 $ O(n) $ 预处理前缀后缀哈希,$ O(n^2) $ 枚举子串 $ O(1) $ 验证共做 $ O(n) $ 次的 $ O(n^3) $,于是寄掉了,最终 $ 30\texttt{pts} $。

正解真的巨简单

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;

#define BASE (31)

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

int N;
string S;

namespace Sub_1_2{
    int ans(0);
    unll pow_base[210];
    unll hash[210], rhash[210];
    void Cal(void){
        hash[0] = rhash[N + 1] = 0;
        for(int i = 1; i <= N; ++i)hash[i] = hash[i - 1] * BASE + S.at(i - 1);
        for(int i = N; i >= 1; --i)rhash[i] = rhash[i + 1] * BASE + S.at(i - 1);
        for(int l = 1; l <= N; ++l){
            for(int r = l; r <= N; ++r){
                unll hash1 = hash[r] - hash[l - 1] * pow_base[r - l + 1];
                unll hash2 = rhash[l] - rhash[r + 1] * pow_base[r - l + 1];
                if(hash1 == hash2)ans = max(ans, r - l + 1);
            }
        }
    }
    void Make(void){
        pow_base[0] = 1;
        for(int i = 1; i <= 205; ++i)pow_base[i] = pow_base[i - 1] * BASE;
        Cal();
        for(int i = 1; i <= N - 2; ++i){
            char c = getchar(); while(!isalpha(c))c = getchar();
            S.at(i) = c;
            Cal();
        }printf("%d\n", ans);
    }
}


int main(){
    freopen("str.in", "r", stdin);
    freopen("str.out", "w", stdout);
    N = read(); cin >> S;
    if(N <= 600)Sub_1_2::Make();
    else puts("qwq");
    fprintf(stderr, "Time: %.6lf\n", (double)clock() / CLOCKS_PER_SEC);
    return 0;
}

/*
6
ABAECB
B
C
D
E
*/

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

T3

还算是相对比较接近切掉的一道题。

给定序列 $ x_n $,给定 $ k, T $,每次操作会使其变为序列 $ y_n $ 满足:

\[y_i = \bigoplus_{j = 0}^{k - 1}x_{(i + j) \bmod{n}} \]

求操作 $ T $ 次后的序列。

Tips:序列下标从 $ 0 $ 开始。

手画一下,例如 $ k = 3 $,一次操作大概是其之后连续 $ 3 $ 个数亦或起来,即 $ 1, 1, 1 $,两次后就是 $ 1, 0, 1, 0, 1 $,即第 $ 1, 3, 5 $ 亦或起来,发现似乎就是将 $ 1, 1, 1 $ 多项式乘法乘上 $ T $ 次,然后将 $ n $ 之外的部分类似 P3321 [SDOI2015]序列统计 去覆盖上去即可,这个东西只能用两只 $ \log $ 的多项式快速幂,此处复杂度 $ O(n \log n \log T) $,最终赋值复杂度 $ O(n^2) $,可以通过 $ 50\texttt{pts}-80\texttt{pts} $,当然我最终得到 $ 50 $。

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;

#define DFT (true)
#define IDFT (false)
#define MOD (998244353ll)

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

int N, K; ll T;
int pos[410000];
ll g(3), inv_g;
ll A[410000];

ll qpow(ll a, ll b){
    ll ret(1), mul(a);
    while(b){
        if(b & 1)ret = ret * mul % MOD;
        b >>= 1;
        mul = mul * mul % MOD;
    }return ret;
}

class Polynomial{
private:
public:
    int len;
    ll poly[410000];
    Polynomial(void){
        len = 0;
        memset(poly, 0, sizeof poly);
    }
    void Reverse(void){
        for(int i = 0; i < len; ++i)
            pos[i] = (pos[i >> 1] >> 1) | (i & 1 ? len >> 1 : 0);
        for(int i = 0; i < len; ++i)if(i < pos[i])swap(poly[i], poly[pos[i]]);
    }
    void Shrink(void){
        for(int i = N; i < len; ++i)(poly[i % N] += poly[i]) %= MOD, poly[i] = 0;
        for(int i = 0; i < len; ++i)poly[i] %= 2;
        len = min(len, N);
    }
    void NTT(bool pat){
        Reverse();
        for(int siz = 2; siz <= len; siz <<= 1){
            ll gn = qpow(pat ? g : inv_g, (MOD - 1) / siz);
            for(auto p = poly; p != poly + len; p += siz){
                int mid = siz >> 1; ll g(1);
                for(int i = 0; i < mid; ++i, (g *= gn) %= MOD){
                    auto tmp = g * p[i + mid] % MOD;
                    p[i + mid] = (p[i] - tmp + MOD) % MOD;
                    p[i] = (p[i] + tmp) % MOD;
                }
            }
        }
        if(!pat){
            ll inv_len = qpow(len, MOD - 2);
            for(int i = 0; i < len; ++i)(poly[i] *= inv_len) %= MOD;
            Shrink();
        }
    }
    void Print(void){
        printf("Polynomial(len = %d): ", len);
        for(int i = 0; i < len; ++i)printf("%lld ", poly[i]);
        printf("\n");
    }
};

Polynomial ret, mul;
void qpow(Polynomial* a, ll b){
    for(int i = 0; i < ret.len; ++i)ret.poly[i] = 0;
    ret.len = 1, ret.poly[0] = 1;
    mul = *a;
    while(b){
        if(b & 1){
            int base(1); while(base < ret.len + mul.len - 1)base <<= 1;
            ret.len = mul.len = base;
            ret.NTT(DFT), mul.NTT(DFT);
            for(int i = 0; i < base; ++i)ret.poly[i] = ret.poly[i] * mul.poly[i] % MOD;
            ret.NTT(IDFT), mul.NTT(IDFT);
        }
        b >>= 1;
        int base(1); while(base < mul.len + mul.len - 1)base <<= 1;
        mul.len = base;
        mul.NTT(DFT);
        for(int i = 0; i < base; ++i)mul.poly[i] = mul.poly[i] * mul.poly[i] % MOD;
        mul.NTT(IDFT);
    }
}


int main(){
    freopen("xortwo.in", "r", stdin);
    freopen("xortwo.out", "w", stdout);
    inv_g = qpow(g, MOD - 2);
    N = read(), K = read(); T = read < ll >();
    Polynomial origin;
    origin.len = K;
    for(int i = 0; i < origin.len; ++i)origin.poly[i] = 1;
    // {
    //     int base(1); while(base < origin.len)base <<= 1;
    //     origin.len = base;
    //     origin.Print();
    //     origin.NTT(DFT), origin.NTT(IDFT);
    //     origin.Print();
    //     base = (1); while(base < origin.len)base <<= 1;
    //     origin.len = base;
    //     origin.NTT(DFT), origin.NTT(IDFT);
    //     origin.Print();
    // }
    qpow(&origin, T);
    basic_string < int > isTrue;
    for(int i = 0; i < N; ++i)if(ret.poly[i])isTrue += i;
    // ret.Print();
    for(int i = 0; i < N; ++i)A[i] = read();
    for(int i = 0; i < N; ++i){
        ll val(0);
        for(auto j : isTrue)val ^= A[(i + j) % N];
        printf("%lld%c", val, i == N - 1 ? '\n' : ' ');
    }
    fprintf(stderr, "Time: %.6lf\n", (double)clock() / CLOCKS_PER_SEC);
    return 0;
}

/*
5 3 1
3 0 2 1 2

5 3 2
3 0 2 1 2

5 3 15
3 0 2 1 2

11 5 1000000000000000000
2 2 4 5 9 1 5 7 7 1 8
*/

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

正解

T1

简单设一个 $ \texttt{1D1D} $ 的 DP 然后斜率优化十分显然,偏序部分套个 CDQ 即可,复杂度 $ O(n \log n) $。

T2

就是个哈希,类比一下原题就行,分割成中间的回文串和左右相同的两串,中间回文串满足单调性,跑一下即可。

另外对于一般的哈希也是满足单调性的,枚举每一个中点然后取最大值并令其不降最终就是 $ O(n) $ 的。

T3

考虑最终将二进制拆位并 NTT 即可,此时就能几乎拿满了。对于过程中会发现有一些难以想到的性质,emm 大概就是能推成一个新的柿子,然后就能优化,emmmmmm 这个东西吧。。反正很奇怪就对了。

代码实现就先都咕着了。

UPD

update-2023_02_04 初稿

posted @ 2023-02-15 19:07  Tsawke  阅读(26)  评论(0编辑  收藏  举报