教学训练:线段树
如题,已知一个数列,你需要进行下面两种操作:
输入格式
第一行包含两个整数
第二行包含
接下来
1 x y k
:将区间 2 x y
:输出区间 输出格式
输出包含若干行整数,即为所有操作 2 的结果。
数据范围
对于
对于
对于
保证任意时刻数列中所有元素的绝对值之和
线段树基础模板题,维护区间加以及区间和。
首先明确,线段树结点维护的是节点所代表区间的一个可递归下传以及递归合并后上传的信息,实现以区间为单位修改来节约大面积查询时间的目的。
对于一套线段树而言,其需要维护两个数组:一个记录区间维护信息的树节点数组
线段树核心的两个操作就是标记下传和迭代上传。对于一个区间
同理,涉及一次标记下传就需要对应一次标记上传,通过子节点的当前更新信息来上传父节点以便统一该区间所影响到的全部上级结点的全部信息。
查询不需要进行标记上传更新,没有影响。
void push_down(long long p, int len)//必须是当前区间懒惰标记对子区间造成的影响修改,下传不修改当前区间
{
if (len <= 1)
return;
tree[p << 1] += lazy_tag[p] * (len - len / 2); // 左侧偏多,是建树的时候决定的
lazy_tag[p << 1] += lazy_tag[p];
tree[p << 1 | 1] += lazy_tag[p] * (len / 2);
lazy_tag[p << 1 | 1] += lazy_tag[p];
lazy_tag[p] = 0;//传递后归零
return;
}
void push_up(int x,int len)//递归下传后必须对应修改上传
{
tree[p]=tree[p<<1]+tree[p<<1|1];//上传信息依据树节点维护信息决定
return;
}
关于建树、修改和查询,如果结点区间不在范围内直接返回(0),节点区间完整的包含在目标区间内则直接返回对应信息,否则递归拆分处理。
void rangemodify_(int l, int r, T d, long long p, int cl, int cr)
{
if (cl > cr || cl > r || cr < l)//不合法以及越界行为,把不准就画画区间。注意区间开闭问题
return;
if (cl >= l && cr <= r)
{
tree[p] += d * (cr - cl + 1);
lazy_tag[p] += d;
return;
}
push_down(p, cr - cl + 1);
int mid = (cl + cr) >> 1;
rangemodify_(l, r, d, p << 1, cl, mid);
rangemodify_(l, r, d, p << 1 | 1, mid + 1, cr);//闭区间mid+1,开区间mid,维护离散区间信息。
//像后文的涂色、扫描线区间连续的就要使用[l,r)形式区间维护连续信息
push_up(p,cr-cl+1);
return;
}
T query(int l, int r, long long p, int cl, int cr)
{
if (cl > cr || cl > r || cr < l)
return 0;
if (cl >= l && cr <= r)
return tree[p];
push_down(p, cr - cl + 1);
int mid = (cl + cr) >> 1;
return query(l, r, p << 1, cl, mid) + query(l, r, p << 1 | 1, mid + 1, cr);
}
#include <bits/stdc++.h>
using namespace std;
template <class T>
class SegmentTree_for_Sum
{
private:
int n;
vector<T> tree, lazy_tag;
void build(T *a, long long p, int cl, int cr)
{
if (cl > cr)
return;
if (cl == cr)
return void(tree[p] = a[cl]);
int mid = (cl + cr) >> 1;
build(a, p << 1, cl, mid);
build(a, p << 1 | 1, mid + 1, cr);
tree[p] = tree[p << 1] + tree[p << 1 | 1];
return;
}
void push_down(long long p, int len)
{
if (len <= 1)
return;
tree[p << 1] += lazy_tag[p] * (len - len / 2); // 左侧偏多,是建树的时候决定的
lazy_tag[p << 1] += lazy_tag[p];
tree[p << 1 | 1] += lazy_tag[p] * (len / 2);
lazy_tag[p << 1 | 1] += lazy_tag[p];
lazy_tag[p] = 0;
return;
}
void rangemodify_(int l, int r, T d, long long p, int cl, int cr)
{
if (cl > cr || r < cl || l > cr)
return;
if (cl >= l && cr <= r)
{
tree[p] += d * (cr - cl + 1);
lazy_tag[p] += d;
return;
}
push_down(p, cr - cl + 1);
int mid = (cl + cr) >> 1;
rangemodify_(l, r, d, p << 1, cl, mid);
rangemodify_(l, r, d, p << 1 | 1, mid + 1, cr);
tree[p] = tree[p << 1] + tree[p << 1 | 1];
return;
}
T query(int l, int r, long long p, int cl, int cr)
{
if (cl > cr || r < cl || l > cr)
return 0;
if (cl >= l && cr <= r)
return tree[p];
push_down(p, cr - cl + 1);
int mid = (cl + cr) >> 1;
return query(l, r, p << 1, cl, mid) + query(l, r, p << 1 | 1, mid + 1, cr);
}
public:
SegmentTree_for_Sum(T *a, int n) : n(n)
{
tree = vector<T>(n << 2 + 1);
lazy_tag = vector<T>(n << 2 + 1);
build(a, 1, 1, n);
return;
}
// 区间增加
void rangemodify(int l, int r, T d)
{
return rangemodify_(l, r, d, 1, 1, n);
}
// 区间查询
T rangequery(int l, int r)
{
return query(l, r, 1, 1, n);
}
~SegmentTree_for_Sum()
{
tree.clear();
lazy_tag.clear();
}
};
#define int long long
int a[200001];
signed main()
{
int n, q;
cin >> n >> q;
for (int i = 1; i <= n; i++)
{
cin >> a[i];
}
SegmentTree_for_Sum<int> S(a, n);
while (q--)
{
int op, x, y;
cin >> op >> x >> y;
if (op == 1)
{
int d;
cin >> d;
S.rangemodify(x, y, d);
}
else
{
cout << S.rangequery(x, y) << endl;
}
}
system("pause");
return 0;
}
给定多个矩形的左下角和右上角坐标,求这些矩形面积的并。
输入格式
输入包括多组数据。
对于每一组数据,第一行一个整数
接下来
输入若干组数据后,以
输出格式
对于第
Test case #(the number of the testcase,normally i)
Total explored area:(the answer of testcase i,exact to two digits to the right of the decimal point.)
样例输入
2
10 10 20 20
15 15 25 25.5
0
样例输出
Test case #1
Total explored area: 180.00
扫描线是基于线段树的一个实现,其前置内容为线段树求线段的并。
先解决上述问题。
给定长度为
操作
操作
通过线段树维护区间
对于区间
修改操作:
修改完整区间
修改不完整区间,线段树二分划分修改。
下传操作:
由于
所以num不下传,没有
举个例子,现在集合里面有
上传操作:
如果区间
否则,区间
查询操作:
直接返回
因为查询线段的并必定是查询区间
然后就不用查询函数了,只考虑建树的复杂度
需要注意的一点事带开区间的线段树要开
不保证
离线下来后离散化,然后再按顺序加入、删除、查询。
(强制在线先打问号?)
扫描线就是在上述内容基础之上。
这个动图已经足够说明意思了。扫描线从低向上扫,每两次修改之间计算当前线段并扫过部分的面积。扫到底边添加线段,扫到顶边不添加线段。
需要注意的是涉及到离散化的操作,这里有个问题就是离散化的时候没必要离散化
离散化操作细节详见代码,这样操作后相当于变成了
#include <bits/stdc++.h>
using namespace std;
#define int long long
#define double long double
int cnum = 0;
vector<int> fx;
struct node
{
double x, y, x2;
int flag;
bool operator<(const node &b) const
{
return (y < b.y);
}
};
int n;
const int maxn = 2e5 + 1;
typedef struct treenode
{
int num = 0;
double sum = 0;
} treenode;
treenode *tree;
vector<node> rem;
vector<vector<pair<pair<int, int>, int>>> operate;
int xsize;
void init()
{
tree = new treenode[maxn << 3];
for (int i = 1; i <= n; i++)
{
double x_1, y_1, x_2, y_2;
scanf("%Lf%Lf%Lf%Lf", &x_1, &y_1, &x_2, &y_2);
rem.push_back({x_1, y_1, x_2, 1}), rem.push_back({x_1, y_2, x_2, -1});
fx.push_back(x_1), fx.push_back(x_2);
}
sort(fx.begin(), fx.end());
xsize = unique(fx.begin(), fx.end()) - fx.begin();
sort(rem.begin(), rem.end());
}
void push_up(int p, int cl, int cr)
{
if (tree[p].num)
{
tree[p].sum = fx[cr] - fx[cl];
return;
}
tree[p].sum = tree[p << 1LL].sum + tree[p << 1LL | 1LL].sum;
return;
}
void modify(int l, int r, int d, int p = 1, int cl = 0, int cr = xsize - 1)
{
if (cr <= cl || r <= fx[cl] || fx[cr] <= l)
return;
if (l <= fx[cl] && fx[cr] <= r)
{
tree[p].num += d;
push_up(p, cl, cr);
return;
}
int mid = (cl + cr) >> 1;
modify(l, r, d, p << 1, cl, mid);
modify(l, r, d, p << 1 | 1, mid, cr);
push_up(p, cl, cr);
return;
}
void solve()
{
init();
double ans = 0;
for (int i = 0; i < rem.size() - 1; i++)
{
modify(rem[i].x, rem[i].x2, rem[i].flag);
ans += tree[1].sum * (rem[i + 1].y - rem[i].y);
}
printf("Test case #%d\nTotal explored area: %.2Lf\n\n", cnum, ans);
// cout << ans << '\n';
}
signed main()
{
while (scanf("%lld", &n) && n != 0)
{
cnum++;
solve();
}
// system("pause");
return 0;
}
给你一棵有
给出一个长度为
求出
不难想到随便找一个点将整棵树挂起来之后先算出来一个值,然后在节点之间
问题是如何实现
举个例子,你有以下情况的树存在(样例2):
然后把结点
如何快速查询总和?显然将子树的结点全部编号成连续号就可以扔到前缀和或者树状数组里面解决了。
那么,重链剖分(
重链剖分可以将树上的任意一条路径划分成不超过
剖分实现过程
给出一些定义:
定义 重子节点 表示其子节点中子树最大的子结点。如果有多个子树最大的子结点,取其一。如果没有子节点,就无重子节点。
定义 轻子节点 表示剩余的所有子结点。
从这个结点到重子节点的边为 重边。
到其他轻子节点的边为 轻边。
若干条首尾衔接的重边构成 重链。
把落单的结点也当作重链,那么整棵树就被剖分成若干条重链。
很明显,这样进行划分后,树具有以下的性质:
树上每个节点都属于且仅属于一条重链。
重链开头的结点不一定是重子节点(因为重边是对于每一个结点都有定义的)。
所有的重链将整棵树 完全剖分。
在剖分时 重边优先遍历,最后树的 DFS 序上,重链内的 DFS 序是连续的。按 DFN 排序后的序列即为剖分后的链。
一颗子树内的 DFS 序是连续的。
当我们向下经过一条 轻边 时,所在子树的大小至少会除以二。因此,对于树上的任意一条路径,把它拆分成从
剖分实现方法:
进行两次
第一次扫出来每个节点的父节点
int dfs1(int u, int dep)
{
depth[u] = dep;
Size[u] = 1;
for (auto p : connects[u])
{
if (p == father[u])
continue;
father[p] = u;
Size[u] += dfs1(p, dep + 1);
if (Size[p] > Size[hson[u]])
hson[u] = p;
}
return Size[u];
}
第二次扫出来每条链链顶
void dfs2(int u, int t)
{
top[u] = t;
dfn[u] = ++tot;
invdfn[tot] = u;
if (hson[u])
{
dfs2(hson[u], t);
for (auto p : connects[u])
if (father[u] != p && hson[u] != p)
dfs2(p, p);
}
return;
}
有长度为
长度为
求满足
数据范围
暴力显然是非常时间吃紧的,时间复杂度
注意到奇偶的问题,考虑奇数解答案情况和位运算的异或有关系(异或运算可以将偶数次情况消除,因为
考虑
考虑暴力的情况,暴力模拟第
注意到奇数才为
更进一步的,对于去掉
那么对于一个
但是还是
因为运算全程只涉及了
那么根据下列伪代码思考,将哪一个维度压缩到
for(int i=0;i<n;i++)
for(int j=i+1;j<n;j++)
for(int k=0;k<m;k++)
cnt[i][j]^=tag[i][j][k];
for(int i=0;i<n;i++)
for(int j=i+1;j<n;j++)
ans+=cnt[i][j];
注意到最后求和的时候,我们连续的加了
这样就变成了
ans+=cnt[i].count();
那么此时,
更进一步的,为了实现
考虑cnt[i][j]^=tag[i][j][k]
的优化,那么将行信息
cnt[i]^=tag[i][k]
和上面同理,为去掉
注意到
for(int i=0;i<n;i++)
for(int k=0;k<m;k++)
{
cin>>a[i][k];
tag[k][a[i][k]].set(i);//初始化bitset tag[k][a[i][k]][j]对应位信息
}
int ans=0;
for(int i=0;i<n;i++)
{
for(int k=0;k<m;j++)
{
bitset<2001>cnt={};
cnt^=tag[k][a[i][k]];
}
cnt.unset(i);//要求i!=j
ans+=cnt.count();
}
ans>>=1;//重复计算,count从头开始点的。
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int mod = 998244353;
const int INF = 1e18;
using p = pair<int, int>;
#define IOS \
ios::sync_with_stdio(false); \
cin.tie(nullptr); \
cout.tie(nullptr)
bitset<2001> bit[2001][1001];
int a[2001][2001];
signed main()
{
IOS;
int n, m;
cin >> n >> m;
int ans = 0;
for (int i = 0; i < n; i++)
for (int j = 0; j < m; j++)
{
cin >> a[i][j];
bit[j][a[i][j]].set(i);//将行信息压缩到了bitset中,充当了判断a[i][k]==a[j][k]的成分。
}
for (int i = 0; i < n; i++)
{
bitset<2001> s = {};
for (int j = 0; j < m; j++)
{
s ^= bit[j][a[i][j]];//逐行鉴定是否有对应下标数相等。如果有,异或计数加一。
}
s[i] = 0;//除去自己。
ans += s.count();
}
cout << ans / 2 << endl;
system("pause");
return 0;
}
有一个网格,网格中有
.
:空单元格。#
:一个障碍物。S
:空单元格和起点。T
:空单元格和目标点。高桥可以通过消耗
网格中有
高桥以
数据范围
.
、 #
、 S
和 T
中的一个。S
和 T
都恰好存在一次。#
.朴素的搜索思想,问题是这个数据范围是否有点过大了,显然
但是
注意到因为不一定在当前点吃药,但是最优情况下一定是低体力吃药,保持在该点时能够保持最大体力。
发现,我们只关注在对应点的最大体力。
回忆
考虑维护对应点的最大体力值。如果能更新目标点的最大体力,则该点重新入队列,重新松弛其相关的所有边。否则不需要更新。
需要注意的一个问题是,不能直接把有药的结点直接初始化为药的数值作为最大体力,因为你有吃药的操作,如果你直接把药初始化了,可能会出现低体力访问有药的格子时发现体力不足以更新,这时候不会把有药的格子入队列进行检查,样例1和3就会WA掉。
或者加一个特判,第一次路过有药的结点可以低体力入队列。
这里是
void spfa()
{
for (int i = 1; i <= n; ++i)
dist[i] = INF;
dist[s] = 0;
queue<int> q;
q.push(s);
vis[s] = 1;
while (!q.empty())
{
int u = q.front();
q.pop();
vis[u] = 0;
for (int i = head[u]; i != 0; i = edges[i].next)
{
int v = edges[i].to, w = edges[i].weight;
if (dist[v] > dist[u] + w)
{
dist[v] = dist[u] + w;
if (!vis[v])
{
q.push(v);
vis[v] = 1;
}
}
}
}
}
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int mod = 998244353;
const int INF = numeric_limits<int>::max();
using p = pair<int, int>;
#define IOS \
ios::sync_with_stdio(false); \
cin.tie(nullptr); \
cout.tie(nullptr)
int n, m, q;
int c[301][301];
pair<int, int> st, ed;
inline void read(int &ans)
{
int x = 0, f = 1;
char ch = getchar();
while (ch < '0' || ch > '9')
{
if (ch == '-')
f = -1;
ch = getchar();
}
while (ch >= '0' && ch <= '9')
x = x * 10 + ch - '0', ch = getchar();
return void(ans = x * f);
}
int rem[301][301];
bool vis[301][301];
int dp[301][301];
void get()
{
read(n);
read(m);
for (int i = 1; i <= n; i++)
{
for (int j = 1; j <= m; j++)
{
rem[i][j] = dp[i][j] = -INF;
char ch = getchar();
if (ch == '#')
c[i][j] = -1;
else
{
c[i][j] = 0;
if (ch == 'S')
st = {i, j};
if (ch == 'T')
ed = {i, j};
}
}
getchar();
}
read(q);
for (int i = 1; i <= q; i++)
{
int u, v, w;
read(u), read(v), read(w);
rem[u][v] = w;
}
}
int dir[4][2] = { {-1, 0}, {1, 0}, {0, -1}, {0, 1} };
void bfs()
{
queue<p> q;
q.push(st);
dp[st.first][st.second] = rem[st.first][st.second];
while (!q.empty())
{
p f = q.front();
q.pop();
vis[f.first][f.second] = 0;
if (dp[f.first][f.second] <= 0)
continue;
for (int i = 0; i <= 3; i++)
{
int nx = f.first + dir[i][0];
int ny = f.second + dir[i][1];
if (nx >= 1 && nx <= n && ny >= 1 && ny <= m)
{
if (c[nx][ny] != -1)
{
if (dp[nx][ny] < dp[f.first][f.second] - 1)
{
dp[nx][ny] = max(dp[f.first][f.second] - 1, rem[nx][ny]);//考虑吃药的问题
if (!vis[nx][ny])
q.push({nx, ny}), vis[nx][ny] = 1;
}
}
}
}
}
if (dp[ed.first][ed.second] >= 0)
cout << "Yes" << endl;
else
cout << "No" << endl;
return;
}
signed main()
{
// IOS;
get();
bfs();
system("pause");
return 0;
}
给你非负整数
这里,
如果多对
什么是 popcount?
对于非负整数
例如,
纯暴力模拟,使用
假设
注意到
显然最大可能差值为
贪心的从高到底对换到目标差值后,计算当前
需要明确的边界条件是,目标差值对换和目标值填充
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int mod = 998244353;
const int INF = 1e18;
using p = pair<int, int>;
#define IOS \
ios::sync_with_stdio(false); \
cin.tie(nullptr); \
cout.tie(nullptr)
int getlength(int x)
{
if (!x)
return 1;
int count = 0;
while (x)
{
count++;
x >>= (1LL);
}
return count;
}
signed main()
{
IOS;
int a, b, c;
cin >> a >> b >> c;
bitset<60> A = bitset<60>(c);
bitset<60> B = bitset<60>(0);
// cout << A.count() << endl;
if (abs(a - b) > A.count() || (A.count() - abs(a - b)) & 1)
{
cout << -1 << endl;
system("pause");
return 0;
}
int more = (A.count() - abs(a - b)) / 2;
int pos = getlength(c) - 1;
for (pos; pos >= 0; pos--)
{
if (!more)
break;
if (A.test(pos))
{
A.flip(pos);
B.flip(pos);
more--;
}
}
int size = max(a, b) - A.count();
for (pos = 0; pos < 60; pos++)
{
if (!size)
break;
if (!(A.test(pos) || B.test(pos)))
{
A.set(pos);
B.set(pos);
size--;
}
}
if (size)
{
cout << -1 << endl;
system("pause");
return 0;
}
if (a < b)
swap(A, B);
cout << A.to_ullong() << " " << B.to_ullong() << endl;
system("pause");
return 0;
}
有一个长度为
依次执行以下
这里,
只要
这不就一区间求和?
说实话,写线段树貌似没啥必要。
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int mod = 998244353;
const int INF = 1e18;
using p = pair<int, int>;
#define IOS \
ios::sync_with_stdio(false); \
cin.tie(nullptr); \
cout.tie(nullptr)
const int maxn = 3e5 + 1;
int num[maxn], qu[maxn], tree[maxn << 2LL], a[maxn];
void push_up(int p)
{
tree[p] = tree[p << 1LL] + tree[p << 1LL | 1LL];
}
int n, q;
void build(int p = 1, int cl = 1, int cr = q)
{
if (cl > cr)
return;
if (cl == cr)
{
tree[p] = num[cl];
return;
}
int mid = (cl + cr) >> 1LL;
build(p << 1LL, cl, mid);
build(p << 1LL | 1LL, mid + 1, cr);
push_up(p);
return;
}
int query(int l, int r, int p = 1, int cl = 1, int cr = q)
{
if (cl > cr || cl > r || cr < l)
return 0;
if (cl >= l && cr <= r)
{
return tree[p];
}
int mid = (cl + cr) >> 1LL;
return query(l, r, p << 1LL, cl, mid) + query(l, r, p << 1LL | 1LL, mid + 1, cr);
}
signed main()
{
IOS;
cin >> n >> q;
set<int> s;
for (int i = 1; i <= q; i++)
{
cin >> qu[i];
if (!s.count(qu[i]))
s.insert(qu[i]);
else
s.erase(qu[i]);
num[i] = s.size();
}
build();
map<int, int> mp;
for (int i = 1; i <= q; i++)
{
if (!mp[qu[i]])
{
mp[qu[i]] = i;
}
else
{
a[qu[i]] += query(mp[qu[i]], i - 1);
mp.erase(qu[i]);
}
}
for (auto p : mp)
{
a[p.first] += query(p.second, q);
}
for (int i = 1; i <= n; i++)
cout << a[i] << " ";
cout << endl;
system("pause");
return 0;
}
有一个无限长的钢琴键盘。在这个键盘中,是否存在一个由
个白键和 个黑键组成的连续部分?
假设 wbwbwwbwbwbw
构成的字符串。
在 w
和出现了 b
组成的?
什么是
数据范围
纯sb题,不用分类讨论,枚举
分类讨论丢了
给你一个长度为 0
和 1
组成。
长度为 0
和 1
组成,当且仅当它满足以下条件时,它是一个**好的字符串:
对于每个
求使
最后要求只有一组两个相同的字符挨着。
那么这个目标串一定是由
对于偶数长度目标串,其基串一定是
对于奇数长度目标串,其基串一定是
在第
奇偶数分开讨论(涉及前后缀开头不一样),
注意位运算挂括号。
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int mod = 998244353;
const int INF = 1e18;
const int maxn = 2e5+3;
int pre1[maxn], suf1[maxn];
int pre0[maxn], suf0[maxn];
int a[maxn];
using p = pair<int, int>;
#define IOS \
ios::sync_with_stdio(false); \
cin.tie(nullptr); \
cout.tie(nullptr)
signed main()
{
IOS;
int n;
cin >> n;
string s;
cin >> s;
s = " " + s;
for (int i = 1; i <= n; i++)
{
cin >> a[i];
if (s[i] & 15)
{
pre1[i] += pre1[i - 1] + ((i + 1) & 1) * a[i];
pre0[i] += pre0[i - 1] + (i & 1) * a[i];
}
else
{
pre1[i] += pre1[i - 1] + (i & 1) * a[i];
pre0[i] += pre0[i - 1] + ((i + 1) & 1) * a[i];
}
}
for (int i = n, j = 1; i >= 1; i--, j++)
{
if (s[i] & 15)
{
suf1[i] += suf1[i + 1] + ((j + 1) & 1) * a[i];
suf0[i] += suf0[i + 1] + (j & 1) * a[i];
}
else
{
suf1[i] += suf1[i + 1] + (j & 1) * a[i];
suf0[i] += suf0[i + 1] + ((j + 1) & 1) * a[i];
}
}
if (n == 2)
{
if (s[1] != s[2])
{
cout << min(a[1], a[2]) << endl;
}
else
cout << 0 << endl;
system("pause");
return 0;
}
int ans = INF;
if (n & 1)
{
for (int i = 2; i <= n; i++)
{
int t = pre1[i - 1] + suf0[i + 1];
if (((i - 1) & 1) != (s[i] & 15))
t += a[i];
ans = min(ans, t);
}
for (int i = 2; i <= n; i++)
{
int t = pre0[i - 1] + suf1[i + 1];
if ((i & 1) != (s[i] & 15))
t += a[i];
ans = min(ans, t);
}
}
else
{
for (int i = 2; i <= n; i++)
{
int t = pre1[i - 1] + suf1[i + 1];
if (((i - 1) & 1) != (s[i] & 15))
t += a[i];
ans = min(ans, t);
}
for (int i = 2; i <= n; i++)
{
int t = pre0[i - 1] + suf0[i + 1];
if ((i & 1) != (s[i] & 15))
t += a[i];
ans = min(ans, t);
}
}
cout << ans << endl;
system("pause");
return 0;
}
对于长度为 abc
, 那么 abcabc
, abcabcabcabc
,以及 aaabbbccc
。另外,对于任意字符串
给你一个正整数
什么是子序列?字符串 ac
、atcoder
和(空字符串)都是atcoder
的子序列,但ta
不是。
如果
判断
记录每一个字母开始计数时指针所在的位置,按倍数筛去字符串。如果剩余的目标字母数量比一个完整
为方便搜索,按顺序记录
注意二分起点设大一点。
// freopen("data.in", "r", stdin);
// freopen("data.out", "w", stdout);
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int mod = 998244353;
const int INF = numeric_limits<int>::max();
using p = pair<int, int>;
#define IOS \
ios::sync_with_stdio(false); \
cin.tie(nullptr); \
cout.tie(nullptr)
string s, t;
int n;
vector<vector<int>> calc;
vector<vector<int>> place;
bool judge(int k)
{
if (k == 0)
return true;
int pt = 0, rem = 0, nowpos = 0;
for (int i = 0; i < t.size(); i++)
{
if (calc[0][t[i] - 'a'] == 0)
return false;
int len = (k / calc[0][t[i] - 'a']);
rem = k % calc[0][t[i] - 'a'];
if (!rem)
{
len--, rem = calc[0][t[i] - 'a'];
}
pt += len * s.size();
int cnum = lower_bound(place[t[i] - 'a'].begin(), place[t[i] - 'a'].end(), nowpos) - place[t[i] - 'a'].begin();
pt += (place[t[i] - 'a'][cnum + rem - 1] - nowpos + 1);
nowpos = (place[t[i] - 'a'][cnum + rem - 1] + 1) % s.size();
}
if (pt > n * s.size())
return false;
return true;
}
signed main()
{
// freopen("data.in", "r", stdin);
// freopen("data.out", "w", stdout);
IOS;
cin >> n;
cin >> s >> t;
place = vector<vector<int>>(26);
for (int i = 0; i < 2 * s.size(); i++)
{
place[(s[i % s.size()] - 'a')].push_back(i);
}
calc = vector<vector<int>>(s.size() + 1, vector<int>(26));
for (int i = s.size() - 1; i >= 0; i--)
{
for (int j = 0; j < 26; j++)
{
calc[i][j] += (calc[i + 1][j]);
}
calc[i][(s[i] - 'a')]++;
}
int l = 0, r = 2e18;
while (l < r)
{
if (r - l == 1)
{
cout << l << endl;
break;
}
int mid = (l + r) >> 1;
if (judge(mid))
l = mid;
else
r = mid;
}
system("pause");
return 0;
}
RM-S-1 ABC348G (max,+)卷积
RM-S-2 ABC347G 网络流拆点
RM-S-3 ABC347F 巧妙分割+二维线段树
请我喝杯咖啡吧~