題目
題目描述
I used to believe
We were burning on the edge of something beautiful
Something beautiful
Selling a dream
Smoke and mirrors keep us waiting on a miracle
On a miracle
Say go through the darkest of days
Heaven's a heartbreak away
Never let you go
Never let me down
OH it's been a hell of a ride
Driving the edge of a knife
Never let you go
Never let me down
Bieber擁有一個長度為n的01 串,他每次會選出這個串的一個子串作為曲譜唱歌,考慮該子串從左往右讀所組成的二進位制數P。 Bieber每一秒歌唱可以讓P增加或減少 2 的 k次方(k由Bieber選定),但必須保證任意時刻其P大於等於0。
Bieber 是一位追求效率的人 每次Bieber都想知道他歌唱的最少時間將這個數P變成0。
Bieber 正和 一位DJ合作,他隨時可能修改串上的一個字元。
輸入描述
第一行一個數n
第二行一個長度為n的字串s
第三行一個數 t 表示 詢問 + 修改總次數
以下 t 行, 每行格式如下
第一個數 1 <= type <= 2 表示 型別
Type = 1 表示是一次 詢問 接下來兩個數 l , r 表示詢問的區間。
否則 表示一次修改 接下來兩個數x,y 表示把 s[x] 改為y.
n <= 3e5, t <= 3e5
輸出描述
對於每個詢問輸出一個數表示最少次數。
示例1
輸入
4
1101
1
1 1 4
輸出
3
題解
知識點:動態dp,區間dp,線段樹。
先考慮不帶修改操作只有詢問,那就是一道簡單的區間dp題,複雜度是 \(O(n^3) \sim O(1)\) 。當然如果只詢問 \([1,n]\) ,那就是線性dp了,複雜度 \(O(n)\)。
設 \(f_{l,r,i,j}\) 代表考慮區間 \([l,r]\) ,左端點狀態為 \(i(0/1)\) (是否向高位進位),右端點狀態為 \(j(0/1)\) (是否從低位進位)時的操作次數最小值。
顯然,兩個區間合併成一個區間時,分割點位置是無後效性的。因為根據現有的狀態,一個子區間狀態的答案以及方案和包含它的區間的狀態沒有任何關係,可以推得無論分割點在哪,答案是固定的。
因此,狀態轉移方程為:
其中 \(mid\) 從 \([l,r]\) 任選一點即可。
但是現在要求能夠修改,那就必須使用線段樹維護區間的dp資訊了,也就是動態dp,每個線段樹區間 \([l,r]\) 維護一個矩陣 \(f[0/1][0/1]\) 即可。維護資訊的過程是十分顯然的,因為此區間dp和分割點無關,就直接按照線段樹的修改合併查詢就做完了。
另外,區間資訊的單位元值不好找,可以直接合並時特判,或者合併前排除無效區間,都可以的。
時間複雜度 \(O((n+m)\log n)\)
空間複雜度 \(O(n)\)
程式碼
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
struct T {
array<array<int, 2>, 2> f = { (int)1e9,(int)1e9,(int)1e9,(int)1e9 };
friend T operator+(const T &a, const T &b) {
// 這裡用特殊值判斷無效區間
// 當然,也可以在遞迴的時候直接避免掉無效區間,而不是在合併時才判斷
if (a.f[0][0] == 1e9) return b;
if (b.f[0][0] == 1e9) return a;
auto x = T();
//* 廣義矩陣乘法(乘改加,加改取最大值)
for (auto i : { 0,1 })
for (auto j : { 0,1 })
for (auto k : { 0,1 })
x.f[i][j] = min(x.f[i][j], a.f[i][k] + b.f[k][j]);
return x;
}
};
struct F {
bool upd;
T operator()(const T &x) {
return{
upd,1,
1,!upd
};
}
};
template<class T, class F>
class SegmentTree {
int n;
vector<T> node;
void update(int rt, int l, int r, int x, F f) {
if (r < x || x < l) return;
if (l == r) return node[rt] = f(node[rt]), void();
int mid = l + r >> 1;
update(rt << 1, l, mid, x, f);
update(rt << 1 | 1, mid + 1, r, x, f);
node[rt] = node[rt << 1] + node[rt << 1 | 1];
}
T query(int rt, int l, int r, int x, int y) {
if (r < x || y < l) return T();
if (x <= l && r <= y) return node[rt];
int mid = l + r >> 1;
return query(rt << 1, l, mid, x, y) + query(rt << 1 | 1, mid + 1, r, x, y);
}
public:
SegmentTree(int _n = 0) { init(_n); }
SegmentTree(const vector<T> &src) { init(src); }
void init(int _n) {
n = _n;
node.assign(n << 2, T());
}
void init(const vector<T> &src) {
assert(src.size() >= 2);
init(src.size() - 1);
function<void(int, int, int)> build = [&](int rt, int l, int r) {
if (l == r) return node[rt] = src[l], void();
int mid = l + r >> 1;
build(rt << 1, l, mid);
build(rt << 1 | 1, mid + 1, r);
node[rt] = node[rt << 1] + node[rt << 1 | 1];
};
build(1, 1, n);
}
void update(int x, F f) { update(1, 1, n, x, f); }
T query(int x, int y) { return query(1, 1, n, x, y); }
};
int main() {
std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
int n;
cin >> n;
vector<T> a(n + 1);
for (int i = 1;i <= n;i++) {
char x;
cin >> x;
a[i] = {
x == '1',1,
1,x != '1'
};
}
SegmentTree<T, F> sgt(a);
int m;
cin >> m;
while (m--) {
int op;
cin >> op;
if (op == 1) {
int l, r;
cin >> l >> r;
cout << sgt.query(l, r).f[0][0] << '\n';
}
else {
int x;
bool val;
cin >> x >> val;
sgt.update(x, { val });
}
}
return 0;
}