2022.7.21-阶段性考试总结
作者:互联网
T1 前缀
给定字符串 \(s\),求它的所有非空前缀在 \(s\) 中出现的次数之和。
考虑用 KMP 算法求出 nxt 数组,并通过其求出答案。
通过 nxt 数组的含义可以很容易推出答案的递推式:\(f_i=f_{nxt[i]} + 1\)
但是考试的时候完全不记得 KMP 算法了,然后又数据结构学傻了,就顺手打了一个字典树,暴力分都没有拿到 QAQ
考场代码:
#include<bits/stdc++.h>
#define MAXN 1000010
using namespace std;
int n, tot;
int trie[MAXN][26], tag[MAXN * 26];
char str[MAXN];
void insert(char s[]){
int len = strlen(s), now = 0;
for(int i = 0; i < len; i++){
if(trie[now][s[i] - 'a']) now = trie[now][s[i] - 'a'];
else{
trie[now][s[i] - 'a'] = ++tot;
now = tot;
}
tag[now]++;
}
}
int solve(){
int now = 0, res = 0;
for(int i = 1; i <= n; i++){
now = trie[now][str[i] - 'a'];
res += tag[now];
}
return res;
}
int main(){
scanf("%s",str + 1); n = strlen(str + 1);
for(int i = 1; i <= n; i++) insert(str + i);
printf("%d\n",solve());
return 0;
}
考后订正代码:
\(80pts\) 的做法是没有递推的:
int main(){
scanf("%s",s + 1); n = strlen(s + 1);
for(int i = 1; i <= n; i++){
int j = nxt[i];
while(j){
res[j]++;
j = nxt[j];
}
}
int ans = 0;
for(int i = 1; i <= n; i++) ans += res[i] + 1;
printf("%d\n",ans);
return 0;
}
\(100pts\) 做法
#include<bits/stdc++.h>
#define MAXN 10000010
using namespace std;
typedef long long ll;
int n, tot;
ll nxt[MAXN], res[MAXN];
char s[MAXN];
void get_nxt(){
for(int i = 2, j = 0; i <= n; i++){
while(j && s[i] != s[j + 1]) j = nxt[j];
if(s[j + 1] == s[i]) j++;
nxt[i] = j;
}
}
int main(){
scanf("%s",s + 1); n = strlen(s + 1);
get_nxt();
for(int i = 1; i <= n; i++){
res[i] = res[nxt[i]] + 1;
}
ll ans = 0;
for(int i = 1; i <= n; i++){
ans += res[i];
}
printf("%lld\n",ans);
return 0;
}
T2 斐波那契
有一个长度为n的正整数序列a(下标从1开始),共有m次操作,每次操作是如下2种操作中的一个:
1 l r x
:将下标区间 \([l,r]\) 内的所有 \(a_i\) 都加上 \(x\)。
2 l r
:查询下标区间 \([l,r]\) 内所有 \(a_i\) 的斐波那契数列的第 \(a_i\) 项的和,也就是 \(f(a_l) + f(a_{l+1}) + … + f(a_r)\)。
其中,\(f(1) = f(2) = 1,f(i) = f(i-1) + f(i-2)\)。
考场线段树常数太大 T 飞而且懒惰标记没有开 long long
我也真的是 speechless。
$100 pts + $ 常数 \(+\) int
$ = 50 pts$
关于线段树 \(70pts\) 的做法。
考虑到利用矩阵计算斐波那契数列的方法,可以用线段树维护区间修改和区间查询。
正常的线段树节点结构体的定义是这样的:
struct node{
int l, r;
int res, lazy_tag;
};
而我们现在维护的是一个矩阵,所以是这样定义的:
struct matrix{
long long data[5][5];
};
struct node{
int l, r, lazy_tag;
matrix res;
};
利用矩阵的分配律,用线段树维护,对 \([l, r]\) 加上 \(x\) 也就是对 \([l, r]\) 的矩阵都乘上 \(A^x\)。其中 \(A\) 表示斐波那契数列计算式子中的矩阵。
于是可以写出如下代码:
#include<bits/stdc++.h>
#define MOD 1004535809
#define MAXN 100010
#define lson 2 * now
#define rson 2 * now + 1
using namespace std;
typedef long long ll;
struct matrix{
ll data[5][5];
matrix operator * (const matrix b) const{
matrix res;
for(ll i = 1; i <= 2; i++){
for(ll j = 1; j <= 2; j++){
res.data[i][j] = 0;
}
}
for(ll i = 1; i <= 2; i++){
for(ll j = 1; j <= 2; j++){
for(ll k = 1; k <= 2; k++){
res.data[i][j] += ( this->data[i][k] * b.data[k][j] ) % MOD;
res.data[i][j] %= MOD;
}
}
}
return res;
}
matrix operator + (const matrix b) const{
matrix res;
for(ll i = 1; i <= 2; i++){
for(ll j = 1; j <= 2; j++){
res.data[i][j] = 0;
}
}
for(ll i = 1; i <= 2; i++){
for(ll j = 1; j <= 2; j++){
res.data[i][j] = (this->data[i][j] + b.data[i][j]) % MOD;
}
}
return res;
}
void init(){
memset(data, 0, sizeof(data));
for(ll i = 1; i <= 2; i++) data[i][i] = 1;
}
void print(){
for(int i = 1; i <= 2; i++){
for(int j = 1; j <= 2; j++){
printf("%lld ",data[i][j]);
}
printf("\n");
}
}
};
struct node{
int l, r;
ll lazy_tag;
matrix res;
};
matrix a, b, tmp;
node tree[MAXN << 2];
int n, m;
int num[MAXN];
inline long long read(){
long long s = 0,w = 1;
char ch = getchar();
while(!isdigit(ch)) { if(ch=='-') w=-1; ch=getchar(); }
while(isdigit(ch)) s=s*10+ch-'0', ch=getchar();
return s * w;
}
matrix matrix_power(matrix mat,ll exp){
exp %= (MOD - 1);
matrix res; res.init();
while(exp){
if(exp & 1) res = res * mat;
mat = mat * mat;
exp >>= 1;
}
return res;
}
void push_up(int now){
tree[now].res = tree[lson].res + tree[rson].res;
}
void push_down(int now){
if(tree[now].lazy_tag){
tree[lson].lazy_tag += tree[now].lazy_tag;
tree[rson].lazy_tag += tree[now].lazy_tag;
matrix mat = matrix_power(a, tree[now].lazy_tag);
tree[lson].res = mat * tree[lson].res;
tree[rson].res = mat * tree[rson].res;
tree[now].lazy_tag = 0;
}
}
void build(int now, int l, int r){
tree[now].l = l; tree[now].r = r;
if(tree[now].l == tree[now].r){
tree[now].lazy_tag = 0;
matrix tmp = matrix_power(a, num[l] - 1);
tree[now].res = tmp * b;
return ;
}
int mid = (tree[now].l + tree[now].r) >> 1;
build(lson, l, mid); build(rson, mid + 1, r);
push_up(now);
}
void update(int now, int l, int r, int x){
if(tree[now].l >= l && tree[now].r <= r){
tree[now].lazy_tag += x;
tree[now].res = tmp * tree[now].res;
return ;
}
push_down(now);
int mid = (tree[now].l + tree[now].r) >> 1;
if(r <= mid) update(lson, l, r, x);
else if(l > mid) update(rson, l, r, x);
else update(lson, l, mid, x), update(rson, mid + 1, r, x);
push_up(now);
}
ll query(int now, int l, int r){
if(tree[now].l >= l && tree[now].r <= r){
return tree[now].res.data[1][1];
}
push_down(now);
int mid = (tree[now].l + tree[now].r) >> 1;
if(r <= mid) return query(lson, l, r);
else if(l > mid) return query(rson, l, r);
else return (query(lson, l, mid) + query(rson, mid + 1, r)) % MOD;
}
int main(){
a.data[1][1] = 1; a.data[1][2] = 1; a.data[2][1] = 1; a.data[2][2] = 0;
b.data[1][1] = 1; b.data[1][2] = 0; b.data[2][1] = 0; b.data[2][2] = 0;
n = read(); m = read();
for(int i = 1; i <= n; i++) num[i] = read();
build(1, 1, n);
for(int i = 1; i <= m; i++){
int op, l, r, x;
op = read();
if(op == 1){
l = read(); r = read(); x = read();
tmp = matrix_power(a, x);
update(1, l, r, x);
}else if(op == 2){
l = read(); r = read();
printf("%lld\n",query(1, l, r));
}
}
return 0;
}
然后 T 成 \(70pts\),和树状数组暴力单点修改的分数一样,是不是很亏?
我们观察到优化的空间已经不多了,唯一可以优化的是幂运算。我们每次修改和查询的时候 push_down()
函数都会执行幂运算,即使是快速幂复杂度也有 \(\log(x)\),何况 \(x\) 还可以取到 \(10^9\)。这相当于我们的复杂度带了两个 \(\log\)。
我们可以把考虑把修改由加法改成乘法,懒惰标记也可以改成矩阵类。
具体来说,既然我们每次都是乘上一个矩阵 \(A^x\),我们可以直接把题目转化为:
有一个长度为n的矩阵序列a(下标从1开始),共有m次操作,每次操作是如下2种操作中的一个:
1 l r x
:将下标区间 \([l,r]\) 内的所有矩阵都乘上 \(A^x\)。
2 l r
:查询下标区间 \([l,r]\) 内所有矩阵的和。
那么就可以省去 push_down()
中繁琐的快速幂了:
#include<bits/stdc++.h>
#define MOD 1004535809
#define MAXN 100010
#define lson 2 * now
#define rson 2 * now + 1
using namespace std;
typedef long long ll;
struct matrix{
ll data[5][5];
matrix operator * (const matrix b) const{
matrix res;
for(ll i = 1; i <= 2; i++){
for(ll j = 1; j <= 2; j++){
res.data[i][j] = 0;
}
}
for(ll i = 1; i <= 2; i++){
for(ll j = 1; j <= 2; j++){
for(ll k = 1; k <= 2; k++){
res.data[i][j] += ( this->data[i][k] * b.data[k][j] ) % MOD;
res.data[i][j] %= MOD;
}
}
}
return res;
}
matrix operator + (const matrix b) const{
matrix res;
for(ll i = 1; i <= 2; i++){
for(ll j = 1; j <= 2; j++){
res.data[i][j] = 0;
}
}
for(ll i = 1; i <= 2; i++){
for(ll j = 1; j <= 2; j++){
res.data[i][j] = (this->data[i][j] + b.data[i][j]) % MOD;
}
}
return res;
}
void init(){
memset(data, 0, sizeof(data));
for(ll i = 1; i <= 2; i++) data[i][i] = 1;
}
void print(){
for(int i = 1; i <= 2; i++){
for(int j = 1; j <= 2; j++){
printf("%lld ",data[i][j]);
}
printf("\n");
}
}
};
struct node{
int l, r;
matrix res, lazy_tag;
};
matrix a, b;
node tree[MAXN << 2];
int n, m;
int num[MAXN];
inline long long read(){
long long s = 0,w = 1;
char ch = getchar();
while(!isdigit(ch)) { if(ch=='-') w=-1; ch=getchar(); }
while(isdigit(ch)) s=s*10+ch-'0', ch=getchar();
return s * w;
}
matrix matrix_power(matrix mat,ll exp){
exp %= (MOD - 1);
matrix res; res.init();
while(exp){
if(exp & 1) res = res * mat;
mat = mat * mat;
exp >>= 1;
}
return res;
}
void push_up(int now){
tree[now].res = tree[lson].res + tree[rson].res;
}
void push_down(int now){
tree[lson].lazy_tag = tree[lson].lazy_tag * tree[now].lazy_tag;
tree[rson].lazy_tag = tree[rson].lazy_tag * tree[now].lazy_tag;
tree[lson].res = tree[now].lazy_tag * tree[lson].res;
tree[rson].res = tree[now].lazy_tag * tree[rson].res;
tree[now].lazy_tag.init();
}
void build(int now, int l, int r){
tree[now].l = l; tree[now].r = r;
tree[now].lazy_tag.init();
if(tree[now].l == tree[now].r){
tree[now].res = matrix_power(a, num[l] - 1) * b;
return ;
}
int mid = (tree[now].l + tree[now].r) >> 1;
build(lson, l, mid); build(rson, mid + 1, r);
push_up(now);
}
void update(int now, int l, int r, matrix x){
if(tree[now].l >= l && tree[now].r <= r){
tree[now].lazy_tag = x * tree[now].lazy_tag;
tree[now].res = x * tree[now].res;
return ;
}
push_down(now);
int mid = (tree[now].l + tree[now].r) >> 1;
if(r <= mid) update(lson, l, r, x);
else if(l > mid) update(rson, l, r, x);
else update(lson, l, mid, x), update(rson, mid + 1, r, x);
push_up(now);
}
ll query(int now, int l, int r){
if(tree[now].l >= l && tree[now].r <= r){
return tree[now].res.data[1][1];
}
push_down(now);
int mid = (tree[now].l + tree[now].r) >> 1;
if(r <= mid) return query(lson, l, r);
else if(l > mid) return query(rson, l, r);
else return (query(lson, l, mid) + query(rson, mid + 1, r)) % MOD;
}
int main(){
a.data[1][1] = 1; a.data[1][2] = 1; a.data[2][1] = 1; a.data[2][2] = 0;
b.data[1][1] = 1; b.data[1][2] = 0; b.data[2][1] = 0; b.data[2][2] = 0;
n = read(); m = read();
for(int i = 1; i <= n; i++) num[i] = read();
build(1, 1, n);
for(int i = 1; i <= m; i++){
ll op, l, r, x;
op = read();
if(op == 1){
l = read(); r = read(); x = read();
update(1, l, r, matrix_power(a, x));
}else if(op == 2){
l = read(); r = read();
printf("%lld\n",query(1, l, r) % MOD);
}
}
return 0;
}
这样丢掉一个 \(\log\),可以惊险地卡过。
T3 过路费
某国共有 \(n\) 座城市,有 \(m\) 条高速公路连接这些城市,每条高速公路只能单向通行。
第 \(i\) 条高速公路的起点城市是 \(u_i\),终点城市是 \(v_i\),经过一次需要 \(w_i\) 的费用。
节假日到了,国家决定对高速公路通行费进行减免,政策如下:
如果一条路线经过的高速公路不超过 \(k\) 条,将按照原价收取费用。
如果一条路线经过的高速公路超过 \(k\) 条,将只收取其中费用最高的 \(k\) 条高速公路的费用。
求:从城市 \(s\) 到城市 \(t\) 的最小花费。
听说这题暴力可以拿到 70pts 的说
直接奔向正解。
考虑枚举第 \(k\) 大的边权 \(x\)。将所有边的边权都减去 \(x\),当然,要和 \(0\) 取最大值。
然后在这张图上跑最短路,得出的答案再加上 \(k * x\) 就是第 \(k\) 大的边权为 \(x\) 时的最小花费。
答案取最小值就好。
很神奇对吗?
首先,假设我们当前枚举的是 \(x\),那么对于所有第 \(k\) 大边恰好是 \(x\) 的路径,在这一步我们都会正确地考虑到。因为小于 \(x\) 的边全变成0了,而大于 \(x\) 的边一共有 \(k\) 条,最后刚好把多减的加回来了。
如果路径中边权 \(\ge x\) 的边比 \(k\) 多,那么会把一些本应没有贡献的边算上一些贡献,当前求出的最小值所以不会是这样的路径。
同样的,如果路径中 \(\ge x\) 的边比 \(k\) 少,那么会有一些边,被减掉了不到 \(x\) 的权值,但在最后却被加回来了 \(x\) 的权值,所以当前求出的最小值所以也不会是这样的路径。
所以当前得出的最短路再加上 \(k * x\) 就是第 \(k\) 大的边权为 \(x\) 时的最小花费。
代码很简单:
#include<bits/stdc++.h>
#define MAXN 1010
#define MAXM 2010
#define INF 0x7f7f7f7f7f7f7f7f
using namespace std;
typedef long long ll;
struct edge{
ll pre, to, w;
};
struct node{
ll id, dis;
};
bool operator < (node a, node b){ return a.dis < b.dis; };
bool operator > (node a, node b){ return a.dis > b.dis; };
edge e[MAXM];
ll n, m, k, s, t, cnt, ans = INF;
ll head[MAXN], len[MAXM], dis[MAXN];
bool vis[MAXN];
void add_edge(ll u, ll v, ll w){
e[++cnt].pre = head[u];
e[cnt].to = v; e[cnt].w = w;
head[u] = cnt;
}
void dijkstra(int st){
memset(vis, 0, sizeof(vis));
memset(dis, 0x7f, sizeof(dis));
priority_queue<node, vector<node>, greater<node>> q;
q.push((node){st, 0}); dis[st] = 0;
while(!q.empty()){
int now = q.top().id; q.pop();
if(vis[now]) continue;
vis[now] = true;
for(int i = head[now]; i; i = e[i].pre){
if(dis[e[i].to] > dis[now] + e[i].w){
dis[e[i].to] = dis[now] + e[i].w;
if(!vis[e[i].to]) q.push((node){e[i].to, dis[e[i].to]});
}
}
}
}
int main(){
scanf("%lld%lld%lld%lld%lld",&n,&m,&k,&s,&t);
for(ll i = 1; i <= m; i++){
ll u, v, w;
scanf("%lld%lld%lld",&u,&v,&w);
add_edge(u, v, w); len[i] = w;
}
sort(len + 1, len + m + 1);
for(ll i = 1; i <= m; i++){
for(ll j = 1; j <= m; j++) e[j].w = max(e[j].w - (len[i] - len[i - 1]), 0ll);
dijkstra(s); ans = min(ans, dis[t] + len[i] * k);
}
printf("%lld\n",ans);
return 0;
}
T4 股票
题目描述
小 b 热衷于炒股,这天他花重金买入了一只股票,计划持有 \(n\) 天后卖出。
这n天中的每一天,股价都会发生变化,每天的变化是如下 \(3\) 种情况之一:
1、股价上涨,这一天小 b 可以赚 \(1\) 万元。
2、股价保持不动,这一天小 b 不赚也不亏。
3、股价下跌,这一天小 b 会亏 \(1\) 万元。
\(n\) 天后小 b 卖出了股票,他想要总结一下这段时间的炒股经历,突然发现自己忘记了一部分天数中的股价变化情况。
但是他记得很清楚的是,赚得最多的时候(可能是最后一天之后,可能是中间的某一天之后,也可能是一开始时),他比一开始一共赚了 \(k\) 万元。
小 b 想要还原出每一天的股价变化情况,当然他知道,由于信息不全,可能有很多种方案都是符合要求的。请你求出符合要求的方案一共有多少种。
由于小 \(b\) 要进一步分析股价的变化规律,所以他还希望对于每个 \(0\le k \le n\) 都求出答案。
我绝对不会说是因为考前看来计数 DP 的课程才想到这一题的 DP 递推式的
考虑朴素的计数 DP。
设 \(f_{i,j,k}\) 表示当前是第 \(i\) 天,目前的最大收益是 \(j\),第 \(i\) 天收益为 \(k\)。
- 如果当天股价上涨,则 \(f_{i, max(j, k + 1), k + 1} += f_{i - 1, j, k}\)
- 如果当天股价下跌,则 \(f_{i, j, k - 1} += f_{i - 1, j, k}\)
- 如果当天股价不变,则 \(f_{i, j, k} += f_{i - 1, j, k}\)
这样很容易拿到 \(60pts\)
void main(){
dp[0][0][ADD] = 1;
for(int i = 1; i <= n; i++){
memset(dp[i & 1], 0, sizeof(dp[i & 1]));
for(int j = 0; j <= i; j++){
for(int k = -(i - 1); k < i; k++){
int p = k + ADD;
if(s[i] == '-'){
(dp[i & 1][j][p - 1] += dp[(i - 1) & 1][j][p]) %= MOD;
}else if(s[i] == '+'){
(dp[i & 1][max(j, k + 1)][p + 1] += dp[(i - 1) & 1][j][p]) %= MOD;
}else if(s[i] == '0'){
(dp[i & 1][j][p] += dp[(i - 1) & 1][j][p]) %= MOD;
}else if(s[i] == '?'){
(dp[i & 1][j][p - 1] += dp[(i - 1) & 1][j][p]) %= MOD;
(dp[i & 1][max(j, k + 1)][p + 1] += dp[(i - 1) & 1][j][p]) %= MOD;
(dp[i & 1][j][p] += dp[(i - 1) & 1][j][p]) %= MOD;
}
}
}
}
for(int i = 0; i <= n; i++){
ll res = 0;
for(int j = -n; j <= n; j++) (res += dp[n & 1][i][j + ADD]) %= MOD;
printf("%lld\n",res);
}
}
考虑如何优化调一维。
我们可以考虑倒叙枚举 \(i\)。这样的好处在于,可以不用记录 \(k\)。因为是往当前得到的答案前面加上前一天的状态,所以可以直接得出对最大前缀和的影响,所以 \(k\) 那一维可以直接丢掉:
ll dp[5010][5010];
void main(){
dp[n + 1][0] = 1;
for(int i = n; i >= 1; i--){
for(int j = 0; j <= (n - i + 1); j++){
if(s[i] == '+'){
(dp[i][j + 1] += dp[i + 1][j]) %= MOD;
}else if(s[i] == '-'){
(dp[i][max(0, j - 1)] += dp[i + 1][j]) %= MOD;
}else if(s[i] == '0'){
(dp[i][j] += dp[i + 1][j]) %= MOD;
}else if(s[i] == '?'){
(dp[i][j + 1] += dp[i + 1][j]) %= MOD;
(dp[i][max(0, j - 1)] += dp[i + 1][j]) %= MOD;
(dp[i][j] += dp[i + 1][j]) %= MOD;
}
}
}
for(int i = 0; i <= n; i++) printf("%lld\n",dp[1][i]);
}
标签:阶段性,21,int,res,ll,tree,2022.7,now,data 来源: https://www.cnblogs.com/Elfin-Xiao/p/16508021.html