題目: CF New Year and Old Subsequence
題意:給定一個字符串,詢問某一個區間內至少需要刪除多少個字母,使得剩下的數字含有2017,但不含有2016。
思路
定義5個狀態
0:空狀態,連2都沒有
1:只有2,沒有0連在後面
2: 只有20,沒有1在後面
3: 只有201,沒有7在後面
4:完整的2017
狀態轉移
對於任意兩個相鄰的連續的區間之間的狀態的轉移,我們可以藉助一個狀態作爲中間變量進行轉移,對於狀態a->b,我們可以藉助中間狀態K進行轉移,a->k->b, k的取值範圍在[0,4]。最後取最小的一個值
下面的轉移過程和矩陣乘法的過程很像,於是考慮用矩陣乘法去維護,*變+,取最小
node operator+(node A) {
node ans=node();
for (int i = 0; i < 5; ++i) {
for (int j = 0; j < 5; ++j) {
for (int k = 0; k < 5; ++k) {
ans.a[i][j] = min(ans.a[i][j], a[i][k] + A.a[k][j]);;
}
}
}
return ans;
}
初始化
對於每一數字單獨形成的葉子節點來說。
node() { memset(a, 0x3f, sizeof(a)); }
void init(int x) {
for (int i = 0; i < 5; i++)a[i][i] = 0;
switch(x)
{
case 2: {a[0][0] = 1, a[0][1] = 0; break; }
case 0: {a[1][1] = 1, a[1][2] = 0; break; }
case 1: {a[2][2] = 1, a[2][3] = 0; break; }
case 7: {a[3][3] = 1; a[3][4] = 0; break; }
case 6: {a[3][3] = 1; a[4][4] = 1; break; }
}
}
如果出現2,那麼0到0的轉移就是1(0是空狀態,需要刪去2),0到1爲0(從無到2,剛好需要一個,正好有,因此不用刪除)。
其它的轉移類似。
但如果出現的是數字6,這個是根據題目要求6是不能出現在201的後面的,故3到3(201)的轉移需要減去這個6,4到4(2017)也需要減去。
接下就可以敲代碼了,用線段樹去維護這個合併過程,每個節點維護這個區間內的轉移狀態
AC—CODE
#include<bits/stdc++.h>
using namespace std;
const int N = 200 * 1000 + 10;
int n, q;
char s[N];
struct node {
int a[5][5];
node() { memset(a, 0x3f, sizeof(a)); }
void init(int x) {
for (int i = 0; i < 5; i++)a[i][i] = 0;
switch(x)
{
case 2: {a[0][0] = 1, a[0][1] = 0; break; }
case 0: {a[1][1] = 1, a[1][2] = 0; break; }
case 1: {a[2][2] = 1, a[2][3] = 0; break; }
case 7: {a[3][3] = 1; a[3][4] = 0; break; }
case 6: {a[3][3] = 1; a[4][4] = 1; break; }
}
}
node operator+(node A) {
node ans=node();
for (int i = 0; i < 5; ++i) {
for (int j = 0; j < 5; ++j) {
for (int k = 0; k < 5; ++k) {
ans.a[i][j] = min(ans.a[i][j], a[i][k] + A.a[k][j]);;
}
}
}
return ans;
}
}T[N<<2];
void build(int rt, int l, int r) {
if (l == r) {
T[rt].init(s[l]-'0');
return;
}
int mid = l + r >> 1;
build(rt<<1,l,mid);
build(rt<<1|1,mid+1,r);
T[rt] = T[rt << 1] + T[rt << 1 | 1];
}
node query(int rt,int l,int r,int ql,int qr){
if (l>=ql && r<=qr) return T[rt];
int mid = l + r >> 1;
if (qr <= mid)return query(rt<<1,l,mid,ql,qr);
if (ql > mid)return query(rt << 1 | 1, mid + 1, r, ql, qr);
return query(rt << 1, l, mid, ql, qr) + query(rt << 1 | 1, mid + 1, r, ql, qr);
}
int main() {
scanf("%d%d",&n,&q);
scanf("%s",s+1);
build(1, 1, n);
while (q--) {
int l, r;
scanf("%d%d",&l,&r);
int ans = query(1, 1, n, l, r).a[0][4];
printf("%d\n",ans==0x3f3f3f3f?-1:ans);
}
return 0;
}
另一個題目:2019 南昌網絡賽 C. Hello 2019
這題其實和上面是一樣的,就是把變成了2019和2018,並且反過來了。
那麼我們只需要把所給的字符串反轉一下,查詢的區間也反轉一下就可以了。
#include<bits/stdc++.h>
using namespace std;
const int N = 200 * 1000 + 10;
int n, q;
char s[N];
struct node {
int a[5][5];
node() { memset(a, 0x3f, sizeof(a)); }
void init(int x) {
for (int i = 0; i < 5; i++)a[i][i] = 0;
switch(x)
{
case 2: {a[0][0] = 1, a[0][1] = 0; break; }
case 0: {a[1][1] = 1, a[1][2] = 0; break; }
case 1: {a[2][2] = 1, a[2][3] = 0; break; }
case 9: {a[3][3] = 1; a[3][4] = 0; break; }
case 8: {a[3][3] = 1; a[4][4] = 1; break; }
}
}
node operator+(node A) {
node ans=node();
for (int i = 0; i < 5; ++i) {
for (int j = 0; j < 5; ++j) {
for (int k = 0; k < 5; ++k) {
ans.a[i][j] = min(ans.a[i][j], a[i][k] + A.a[k][j]);;
}
}
}
return ans;
}
}T[N<<2];
void build(int rt, int l, int r) {
if (l == r) {
T[rt].init(s[l]-'0');
return;
}
int mid = l + r >> 1;
build(rt<<1,l,mid);
build(rt<<1|1,mid+1,r);
T[rt] = T[rt << 1] + T[rt << 1 | 1];
}
node query(int rt,int l,int r,int ql,int qr){
if (l>=ql && r<=qr) return T[rt];
int mid = l + r >> 1;
if (qr <= mid)return query(rt<<1,l,mid,ql,qr);
if (ql > mid)return query(rt << 1 | 1, mid + 1, r, ql, qr);
return query(rt << 1, l, mid, ql, qr) + query(rt << 1 | 1, mid + 1, r, ql, qr);
}
int main() {
scanf("%d%d",&n,&q);
scanf("%s",s+1);
reverse(s+1,s+n+1);
build(1, 1, n);
while (q--) {
int l, r;
scanf("%d%d",&l,&r);
int ans = query(1, 1, n, n-r+1, n-l+1).a[0][4];
printf("%d\n",ans==0x3f3f3f3f?-1:ans);
}
return 0;
}