杂题小记(2023.02.28)
杂题小记(2023.02.28)
更好的阅读体验戳此进入
SP2713 GSS4 - Can you answer these queries IV
题面
给定序列,区间开方,区间求和。
Solution
考虑对于 $ 10^{18} $ 的数最多开方 $ 6 $ 次就会变为 $ 1 $,则额外维护一个标记表示是否区间均为 $ 0 $ 或 $ 1 $,遇到标记则不继续修改,可以证明复杂度是对数级的。
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, M;
ll A[110000];
class SegTree{
private:
ll sum[110000 << 2];
bool flag[110000 << 2];
#define LS (p << 1)
#define RS (LS | 1)
#define MID ((gl + gr) >> 1)
public:
void Clear(int p = 1, int gl = 1, int gr = N){
sum[p] = flag[p] = 0;
if(gl == gr)return;
Clear(LS, gl, MID), Clear(RS, MID + 1, gr);
}
void Pushup(int p){
sum[p] = sum[LS] + sum[RS];
flag[p] = flag[LS] & flag[RS];
}
void Build(int p = 1, int gl = 1, int gr = N){
if(gl == gr)return sum[p] = A[gl = gr], flag[p] = sum[p] == 0 || sum[p] == 1, void();
Build(LS, gl, MID), Build(RS, MID + 1, gr);
Pushup(p);
}
void Modify(int l, int r, int p = 1, int gl = 1, int gr = N){
if(gl == gr)return sum[p] = (int)sqrt(sum[p]), flag[p] = sum[p] == 0 || sum[p] == 1, void();
if(flag[p])return;
if(l <= MID)Modify(l, r, LS, gl, MID);
if(r >= MID + 1)Modify(l, r, RS, MID + 1, gr);
Pushup(p);
}
ll Query(int l, int r, int p = 1, int gl = 1, int gr = N){
if(l <= gl && gr <= r)return sum[p];
if(l > gr || r < gl)return 0;
return Query(l, r, LS, gl, MID) + Query(l, r, RS, MID + 1, gr);
}
}st;
int main(){
int cnt(0);
while(true){
N = read();
// if(!~N)exit(0);
printf("Case #%d:\n", ++cnt);
for(int i = 1; i <= N; ++i)A[i] = read < ll >();
st.Build();
M = read();
while(M--){
int opt = read();
if(opt == 0){
int l = read(), r = read();
st.Modify(min(l, r), max(l, r));
}else{
int l = read(), r = read();
printf("%lld\n", st.Query(min(l, r), max(l, r)));
}
}printf("\n");
st.Clear();
}
fprintf(stderr, "Time: %.6lf\n", (double)clock() / CLOCKS_PER_SEC);
return 0;
}
template < typename T >
inline T read(void){
T ret(0);
int flag(1);
char c = getchar();
if(c == EOF)exit(0);
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;
}
LG-P4391 [BOI2009]Radio Transmission 无线传输
题面
给定字符串,其是由一个字符串多次重复得到的(是一个串无限重复后串的任意子串),求该字符串的最短长度。
Solution
只能说这题确实高妙!感觉推导过程和 border 的那一套理论差不多。
首先结论就是用 KMP 求出 $ nxt $ 后 $ n - nxt_n $ 即为答案。
按照类似 border 某些前置知识的证明思路证明即可,也就是考虑对于减去最长前后缀后剩下的一段,会对应着前缀末尾段,然后再对应到后缀对应段,一直对应下去,发现一定合法。
对于其一定为最优的证明,可以考虑若存在一个更小的循环节,多次循环之后最后会剩一个残块,不考虑这个残块及其影响后一定会形成一个更长前后缀,得证(这东西适合画图李姐一下,口糊肯定很抽象)。
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 S(i) (S.at(i - 1))
template < typename T = int >
inline T read(void);
int N;
string S;
int nxt[1100000];
int main(){
N = read();
cin >> S;
int lst(0);
for(int i = 2; i <= N; ++i){
while(lst && S(lst + 1) != S(i))lst = nxt[lst];
if(S(lst + 1) == S(i))++lst;
nxt[i] = lst;
}printf("%d\n", N - nxt[N]);
fprintf(stderr, "Time: %.6lf\n", (double)clock() / CLOCKS_PER_SEC);
return 0;
}
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;
}
LG-P2375 [NOI2014] 动物园
题面
给定字符串 $ S $,求 $ S $ 的每个前缀子串的相同且不重叠前后缀的数量。
Solution
想麻烦了,找了半天性质,实际上很简单。
首先不考虑重叠,对于一般的求数量我们只需要在求 $ nxt $ 的时候同时转移一下 $ num $ 即可,然后考虑重叠,不难想到重新跑一遍 KMP,对于每次求出来的 $ nxt_i $ 一直跳到 $ nxt_i \le \lfloor \dfrac{i}{2} \rfloor $,然后取 $ num_{nxt_i} $ 即为答案。
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 MOD (ll)(1e9 + 7)
#define S(i) (S.at(i - 1))
template < typename T = int >
inline T read(void);
string S;
int nxt[1100000];
int num[1100000];
int main(){
int T = read();
while(T--){
ll ans(1);
cin >> S;
int N = S.length();
int lst(0);
nxt[0] = nxt[1] = num[0] = 0, num[1] = 1;
for(int i = 2; i <= N; ++i){
while(lst && S(lst + 1) != S(i))lst = nxt[lst];
if(S(lst + 1) == S(i))++lst;
nxt[i] = lst, num[i] = num[lst] + 1;
}lst = 0;
for(int i = 2; i <= N; ++i){
while(lst && S(lst + 1) != S(i))lst = nxt[lst];
if(S(lst + 1) == S(i))++lst;
while(lst > (i >> 1))lst = nxt[lst];
(ans *= (num[lst] + 1)) %= MOD;
}
// int same(1);
// while(same + 1 <= N && S(same + 1) == S(same))++same;
// ll ans(1);
// for(int i = 1; i <= N; ++i)(ans *= nxt[i] ? (S(1) == S(i) ? min(nxt[i] + 1, same + 1) : 2) : 1) %= MOD;
printf("%lld\n", ans);
}
fprintf(stderr, "Time: %.6lf\n", (double)clock() / CLOCKS_PER_SEC);
return 0;
}
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;
}
LG-P3193 [HNOI2008]GT考试
题面
求长度为 $ n $ 的序列中不存在长度为 $ m $ 的子串的方案数。
Solution
首先按照一般思路考虑一个朴素 DP,即令 $ dp(i, j) $ 表示匹配了文本串的前 $ i $ 位与模式串的前 $ j $ 位的方案数,发现并不好进行转移,可能性太多,于是想到可以预处理出 $ g(i, j) $ 表示对于模式串,从匹配了前 $ i $ 转移到匹配了前 $ j $ 的方案数,则转移显然:
显然可以等效写成:
发现其满足矩阵形式,且 $ g $ 为定值,可以通过矩阵快速幂优化。
对于 $ g $ 的预处理,考虑 KMP,先朴素跑一遍 KMP,然后枚举每一位,钦定当前的最长前后缀为目前的整个前缀,这是显然的,然后按照一般的 KMP 的匹配思路往前跳到合法能匹配的最长,如此大概统计一下复杂度卡满应该是 $ O(m^2v) $ 的,其中 $ v = 10 $ 为值域。
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 S(i) (S.at(i - 1))
#define MOD (K)
template < typename T = int >
inline T read(void);
int N, M, K;
string S;
int nxt[30];
int G[30][30];
class Matrix{
private:
public:
int v[20][20];
Matrix(void){memset(v, 0, sizeof v);}
friend Matrix operator * (const Matrix &a, const Matrix &b){
Matrix ret;
for(int i = 0; i < M; ++i)
for(int j = 0; j < M; ++j)
for(int k = 0; k < M; ++k)
(ret.v[i][j] += a.v[i][k] * b.v[k][j] % MOD) %= MOD;
return ret;
}
void Print(void){
printf("Matrix:\n");
for(int i = 0; i < M; ++i)
for(int j = 0; j < M; ++j)
printf("%d%c", v[i][j], j == M - 1 ? '\n' : ' ');
}
}base, ans;
Matrix qpow(Matrix a, ll b){
Matrix ret, mul(a);
for(int i = 0; i < M; ++i)ret.v[i][i] = 1;
while(b){
if(b & 1)ret = ret * mul;
b >>= 1;
mul = mul * mul;
}return ret;
}
int main(){
N = read(), M = read(), K = read();
cin >> S;
int lst(0);
for(int i = 2; i <= M; ++i){
while(lst && S(lst + 1) != S(i))lst = nxt[lst];
if(S(lst + 1) == S(i))++lst;
nxt[i] = lst;
}
for(int i = 0; i < M; ++i)
for(int j = '0'; j <= '9'; ++j){
int lst = i;
while(lst && S(lst + 1) != j)lst = nxt[lst];
if(S(lst + 1) == j)++lst;
++G[i][lst];
}
for(int i = 0; i < M; ++i)
for(int j = 0; j < M; ++j)
base.v[i][j] = G[i][j] % MOD;
ans.v[0][0] = 1;
ans = ans * qpow(base, N);
int rans(0);
for(int i = 0; i < M; ++i)(rans += ans.v[0][i]) %= MOD;
printf("%d\n", rans);
fprintf(stderr, "Time: %.6lf\n", (double)clock() / CLOCKS_PER_SEC);
return 0;
}
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;
}
LG-P4180 [BJWC2010] 严格次小生成树
题面
求连通图的严格次小生成树。
Solution
存在如下方法,正确性可感性理解,理性证明未知。
考虑任意建出一棵 MST,然后枚举所有没有在 MST 中的边,分别找出每条边的 $ s, t $ 对应路径中与该边权值不同的最大权值,本次的答案即为原来 MST 的权值和减去该边的权值加上枚举的原本不在 MST 的边的权值,最终取答案最小值即可。
对于维护可以考虑用树剖 + 线段树,边权下放到点权,然后合并时可以强行讨论,也可无脑开 basic_string
排序后 unique
实现,此写法常数较大但可以通过。
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, M;
struct Edge{
Edge* nxt;
int to;
int val;
OPNEW;
}ed[210000];
ROPNEW;
Edge* head[110000];
class UnionFind{
private:
int fa[110000];
public:
UnionFind(void){for(int i = 0; i <= 101000; ++i)fa[i] = i;}
int Find(int x){return x == fa[x] ? x : fa[x] = Find(fa[x]);}
void Union(int s, int t){int fs = Find(s), ft = Find(t); fa[fs] = ft;}
}uf;
struct Edg{int s, t, v;};
basic_string < Edg > edgs;
basic_string < Edg > trash;
int dep[110000], fa[110000], hson[110000], siz[110000], dfn[110000], idx[110000], top[110000];
void dfs_pre(int p = 1, int fa = 0){
::fa[p] = fa, dep[p] = dep[fa] + 1, siz[p] = 1;
for(auto i = head[p]; i; i = i->nxt){
if(SON == fa)continue;
dfs_pre(SON, p);
siz[p] += siz[SON];
if(siz[SON] > siz[hson[p]])hson[p] = SON;
}
}
void dfs_make(int p = 1, int top = 1){
static int cdfn(0);
dfn[p] = ++cdfn, idx[cdfn] = p, ::top[p] = top;
if(hson[p])dfs_make(hson[p], top);
for(auto i = head[p]; i; i = i->nxt)
if(SON != fa[p] && SON != hson[p])dfs_make(SON, SON);
}
ll ans(LONG_LONG_MAX);
int val[110000];
void PushdownVal(int p = 1, int fa = 0){
for(auto i = head[p]; i; i = i->nxt)
if(SON != fa)
val[SON] = i->val, PushdownVal(SON, p);
}
class SegTree{
private:
int mx[110000 << 2], mx2[110000 << 2];
#define LS (p << 1)
#define RS (LS | 1)
#define MID ((gl + gr) >> 1)
public:
SegTree(void){memset(mx, -1, sizeof mx), memset(mx2, -1, sizeof mx2);}
void Pushup(int p){
basic_string < int > vals;
if(~mx[LS])vals += mx[LS];
if(~mx[RS])vals += mx[RS];
if(~mx2[LS])vals += mx2[LS];
if(~mx2[RS])vals += mx2[RS];
sort(vals.begin(), vals.end(), greater < int >());
vals.erase(unique(vals.begin(), vals.end()), vals.end());
mx[p] = (int)vals.size() >= 1 ? *vals.begin() : -1;
mx2[p] = (int)vals.size() >= 2 ? *next(vals.begin()) : -1;
}
void Build(int p = 1, int gl = 1, int gr = N){
if(gl == gr)return mx[p] = val[idx[gl = gr]], void();
Build(LS, gl, MID), Build(RS, MID + 1, gr);
Pushup(p);
}
pair < int, int > Query(int l, int r, int p = 1, int gl = 1, int gr = N){
if(l <= gl && gr <= r)return {mx[p], mx2[p]};
if(l > gr || r < gl)return {-1, -1};
basic_string < int > vals;
auto ret = Query(l, r, LS, gl, MID);
if(~ret.first)vals += ret.first;
if(~ret.second)vals += ret.second;
ret = Query(l, r, RS, MID + 1, gr);
if(~ret.first)vals += ret.first;
if(~ret.second)vals += ret.second;
sort(vals.begin(), vals.end(), greater < int >());
vals.erase(unique(vals.begin(), vals.end()), vals.end());
return {(int)vals.size() >= 1 ? *vals.begin() : -1, (int)vals.size() >= 2 ? *next(vals.begin()) : -1};
}
}st;
ll origin(0);
int QueryMx2(int s, int t, int ban){
basic_string < int > vals;
while(top[s] != top[t]){
if(dep[top[s]] < dep[top[t]])swap(s, t);
auto ret = st.Query(dfn[top[s]], dfn[s]);
if(~ret.first)vals += ret.first;
if(~ret.second)vals += ret.second;
// printf("Querying %d->%d, [%d, %d], ret %d %d\n", top[s], s, dfn[top[s]], dfn[s], ret.first, ret.second);
s = fa[top[s]];
}if(dep[s] < dep[t])swap(s, t);
auto ret = st.Query(dfn[hson[t]], dfn[s]);
// printf("Querying %d->%d, [%d, %d], ret %d %d\n", hson[t], s, dfn[hson[t]], dfn[s], ret.first, ret.second);
if(~ret.first)vals += ret.first;
if(~ret.second)vals += ret.second;
sort(vals.begin(), vals.end(), greater < int >());
vals.erase(unique(vals.begin(), vals.end()), vals.end());
int mx = (int)vals.size() >= 1 ? *vals.begin() : -1, mx2 = (int)vals.size() >= 2 ? *next(vals.begin()) : -1;
return mx != ban ? mx : mx2;
}
int main(){
N = read(), M = read();
for(int i = 1; i <= M; ++i){
int s = read(), t = read(), v = read();
if(s == t)continue;
edgs += Edg{s, t, v};
}sort(edgs.begin(), edgs.end(), [](const Edg &a, const Edg &b)->bool{return a.v < b.v;});
for(auto edg : edgs){
auto [s, t, v] = edg;
if(uf.Find(s) != uf.Find(t))
head[s] = new Edge{head[s], t, v},
head[t] = new Edge{head[t], s, v},
origin += v,
uf.Union(s, t);
else trash += edg;
}dfs_pre(), dfs_make(), PushdownVal();
st.Build();
// printf("origin is %lld\n", origin);
for(auto edg : trash){
auto [s, t, v] = edg;
int ret = QueryMx2(s, t, v);
if(!~ret)continue;
// printf("[%d - %d], v = %d, ret = %d\n", s, t, v, ret);
ans = min(ans, origin - ret + v);
}printf("%lld\n", ans);
fprintf(stderr, "Time: %.6lf\n", (double)clock() / CLOCKS_PER_SEC);
return 0;
}
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;
}
LG-P3403 跳楼机
题面
给定 $ x, y, z, h $,求有多少 $ k \in [1, h] $ 满足 $ ax + by + cz = k $。
Solution
经典同余最短路问题,令 $ f(i) $ 表示由 $ y, z $ 凑成的模 $ x $ 余 $ i $ 的最小楼层,则有转移 $ f((i + y) \bmod x) = f(i) + y, f((i + z) \bmod x) = f(i) + z $,发现其满足最短路松弛操作的形式,直接以此建图跑一遍,每个 $ dis $ 对答案的贡献即为 $ \lfloor \dfrac{h - dis}{x} \rfloor + 1 $,最后的 $ +1 $ 则是取不选 $ x $ 的方案,同时注意判断每个是否满足 $ h \ge dis $。
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);
ll H;
int x, y, z;
struct Edge{
Edge* nxt;
int to;
int val;
OPNEW;
}ed[210000];
ROPNEW;
Edge* head[110000];
bitset < 110000 > vis;
ll dis[110000];
ll ans(0);
void Dijkstra(void){
memset(dis, 0x3f, sizeof dis);
static priority_queue < pair < ll, int >, vector < pair < ll, int > >, greater < pair < ll, int > > > cur;
dis[1] = 1, cur.push({0, 1});
while(!cur.empty()){
int p = cur.top().second; cur.pop();
if(vis[p])continue;
vis[p] = true;
for(auto i = head[p]; i; i = i->nxt)
if(dis[p] + i->val < dis[SON])
dis[SON] = dis[p] + i->val, cur.push({dis[SON], SON});
}
}
int main(){
H = read < ll >();
x = read(), y = read(), z = read();
if(x == 1 || y == 1 || z == 1)printf("%lld\n", H), exit(0);
for(int i = 0; i < x; ++i)
head[i] = new Edge{head[i], (i + y) % x, y},
head[i] = new Edge{head[i], (i + z) % x, z};
Dijkstra();
for(int i = 0; i < x; ++i)
if(H >= dis[i])
ans += (H - dis[i]) / x + 1;
printf("%lld\n", ans);
fprintf(stderr, "Time: %.6lf\n", (double)clock() / CLOCKS_PER_SEC);
return 0;
}
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;
}
UPD
update-2023_02_28 初稿