20231121
2023/11/21
树状数组
t[i],为树状数组;a[i],为原数组
t[i]代表的区间为a(i-lowbit(i)+1)~a(i)这个区间。
所以求前缀的时候,每次-=lowbit(x),区间是连续接起来的
修改操作,a[x]+val,原数组单点加,那么我们要去树状数组上找哪些节点包含a[x],所以是一个+=lowbit(x)的过程
#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
#define Acode ios::sync_with_stdio(false), cin.tie(0), cout.tie(0)
#define int long long
const int N = 1e6 + 10;
int t[N];
int n, m;
int a[N];
int lowbit(int x)
{
return x & (-x);
}
void add(int x, int val) //对a[x]+val,同时修改t数组,t是真实意义上的树状数组
{
while (x <= n)
{
t[x] += val;
x += lowbit(x);
}
}
int getsum(int x) // 计算sum[1,x] 1到x的前缀和 ,a[1]..a[x]的和
{
int sum = 0;
while (x)
{
sum += t[x];
x -= lowbit(x);
}
return sum;
}
int query(int l, int r) //区间求和 , 前缀相减
{
return getsum(r) - getsum(l - 1);
}
void solve()
{
cin >> n >> m;
for (int i = 1; i <= n; i++)
{
cin >> a[i];
add(i, a[i]);
}
for (int i = 1; i <= m; i++)
{
int op, x, y;
cin >> op >> x >> y;
if (op == 1)
{
add(x, y);
}
else
{
cout << query(x, y) << endl;
}
}
}
树状数组上二分
因为2的幂次的t[i]数组(即i为2的幂次),他表示的区间就是1~i,所以可以利用位运算的贪心策略从高到底的求最大的区间(第一次的区间表示)
t[i]代表的区间为a(i-lowbit(i)+1)~a(i)这个区间。(关键)
O(n*logn)查询小于x时,前缀的最大下标
const int N = 2e6 + 10;
int t[N];
int n, m;
int a[N];
int lowbit(int x)
{
return x & (-x);
}
void add(int x, int val)
{
while (x <= n)
{
t[x] += val;
x += lowbit(x);
}
}
int getsum(int x)
{
int sum = 0;
while (x)
{
sum += t[x];
x -= lowbit(x);
}
return sum;
}
int query(int s)
{
int c = 0; // 记录当前的前缀和
int pos = 0; // 记录位置
for (int j = 20; j >= 0; j--)
{
if (pos + (1LL << j) <= n && c + t[pos + (1LL << j)] <= s)
{
pos += (1LL << j);
c += t[pos];
}
}
return pos;
}
void solve()
{
int q;
cin >> n >> q;
for (int i = 1; i <= n; i++)
{
cin >> a[i];
add(i, a[i]);
}
while (q--)
{
int op;
cin >> op;
if (op == 1)
{
int x, d;
cin >> x >> d;
add(x, d - a[x]); // 修改a[x]为d
a[x] = d;
}
else
{
int s;
cin >> s;
cout << query(s) << endl;
}
}
}
二维树状数组
对每一个一位的树状数组节点都看成一个树状数组,这样就形成了二维的树状数组,高位同理
二维树状数组 1:单点修改,区间查询
const int N = 4100;
long long t[N][N];
int n, m;
int lowbit(int x)
{
return x & (-x);
}
void add(int x, int y, int val)
{
for (int p = x; p <= n; p += p & (-p))
{
for (int q = y; q <= m; q += q & (-q))
{
t[p][q] += val;
}
}
}
int query(int x, int y)
{
int sum = 0;
for (int p = x; p; p -= p & (-p))
{
for (int q = y; q; q -= q & (-q))
{
sum += t[p][q];
}
}
return sum;
}
void solve()
{
cin >> n >> m;
int op;
while (cin >> op)
{
if (op == 1)
{
int x, y, k;
cin >> x >> y >> k;
add(x, y, k);
}
else
{
int a, b, c, d;
cin >> a >> b >> c >> d;
cout << query(c, d) - query(c, b - 1) - query(a - 1, d) + query(a - 1, b - 1) << endl;
}
}
}
CF1800F Dasha and Nightmares
思路:首先抓住25个字母的关键,少一个是什么意思,不就是给我枚举少了哪一个用的嘛
我们枚举缺哪个字母,对每一个字符串处理一下它少了哪些字母,放进集合里面。然后我们可以把奇数次出现的字母看作1,偶数次或没有出现的看作0.等价处理,这样我们就可以得到一个字符串的二进制值,最多为2的26次,int存的下。然后我们对缺少的字母的集合枚举。遍历里面的元素,然后开个桶记录一下这样我们可以O(1)的求出符合条件的对数。
具体做法为,新遍历到的一个元素,然后我们可以知道和他能匹配的二进制是多少,由于我们之前桶记录过,所以ans+=mp[id]即可。总复杂度为O(26*n);
#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
#define Acode ios::sync_with_stdio(false), cin.tie(0), cout.tie(0)
// #define int long long
const int N = 2e5 + 10;
int n, m;
int vis[30];
vector<int> g[30];
int c[N];
// map<int, int> mp;
int mp[(1 << 26) + 10];
void solve()
{
int k = (1LL << 26) - 1;
int n;
cin >> n;
for (int i = 1; i <= n; i++)
{
for (int j = 0; j <= 26; j++)
vis[j] = 0;
string s;
cin >> s;
int len = s.size();
s = " " + s;
for (int j = 1; j <= len; j++)
{
vis[s[j] - 'a']++;
}
for (int j = 0; j < 26; j++)
{
if (!vis[j])
g[j].push_back(i);
}
int x = 0;
for (int j = 0; j < 26; j++)
{
if (vis[j] % 2)
x += (1LL << j);
}
c[i] = x;
}
// cerr << "TTTTT\n";
long long ans = 0;
for (int i = 0; i < 26; i++)
{
for (auto x : g[i])
{
int id = c[x] ^ k ^ (1LL << i);
ans += mp[id];
mp[c[x]]++;
}
for (auto x : g[i])
mp[c[x]]--;
// mp.clear();
}
cout << ans << endl;
}
signed main()
{
Acode;
int T = 1;
while (T--)
{
solve();
}
return 0;
}

浙公网安备 33010602011771号