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 $ 个未染色的最大贡献,转移显然:
后者整理得:
前者二维偏序可以优化到 $ 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 $ 满足:
求操作 $ 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 初稿