http://hihocoder.com/contest/hiho61/problem/1
題意分析
給定一個字符串s,以及對該字符串s的 m 個操作。
字符串s包含n個字符,下標爲1…n。字符由’A’到’Z’構成,字符增加1表示該字符變爲後續字符,比如’A’增加1是’B’,‘C’增加1是’D’。需要注意的是’Z’增加1是’A’。
m個操作包含以下四種類型:
將字符串第i位到第j位設定爲C。
比如當i=2,j=3,C='Z’時:“ABCDEFG"變成"AZZDEFG”
將字符串第i位到第j位增加K。
比如i=2,j=3,K=1時:“ABCDEFG"變成"ACDDEFG”
將字符串左邊K位移至右邊。
比如K=3時:“ABCDEFG"變成"DEFGABC”
從字符串第i位到第j位,依次增加1,2,…,j-i+1。
比如當i=2,j=3時:“ABCDEFG"變成"ACEDEFG”
輸出m個操作結束後的字符串s。
算法分析
本題需要根據每一次的操作去修改現在的s。若採用樸素的做法,每一次修改其最大代價爲O(n),故總的時間複雜度爲O(nm)。對於n=50000,m=50000的數據量來說,這樣時間複雜度顯然是不能夠接受的。
仔細觀察我們每一次的操作,其中CMD3是對整體進行了平移,CMD1,CMD2,CMD4都是針對i到j的一個區間進行操作。
首先我們來解決看似比較簡單的CMD3操作:
若將整個字符串s看作環形,則線型的字符串是從起點指針SP開始順時針將n個元素進行展開得到的。那麼CMD3操作爲順時針移動該環的頭指針。舉個例子來說:
最開始頭指針在1時,我們展開字符串爲[1,2,3,4,5]。當執行CMD3 K=2操作後,起點指針SP移動到3的位置,此時展開的字符串爲[3,4,5,1,2]。
符合CMD3操作的規則,並且起點指針SP的改變就是增加了K。
其中新字符串的第ij位,對應的是原字符串第i+SPj+SP位。
所以我們只需要維護一個SP指針,當執行CMD3操作時,改變SP的值。而對於其他操作的區間,只需要將區間從[i…j]變化到[i+SP…j+SP]即可。
需要注意的是,SP,i+SP,j+SP有可能會超過n。當超過n時,需要將其值減去n。
至此執行CMD3操作的時間複雜度降至O(1)。
接下來考慮CMD1,CMD2,CMD4。這三個操作均爲區間上的操作,因此我們可以使用線段樹來進行模擬。(在我們的Hiho一下第19期和第20期可以找到線段樹的教程)
在那之前,我們需要對字符進行處理。從題目中我們知道當一個字符超過’Z’時,會直接變成’A’。所以我們可以直接考慮將’A’'Z’與025對應起來。當一個字符增加了很多次K後,其實際表示的字符也就等於該值 mod 26。
構造線段樹
構造線段樹,主要是構造每個節點的數據域,使其能夠記錄我們需要的信息,同時在父節點和子節點之間能夠進行信息的傳遞。根據本題的題意,我們構造的線段樹其節點包含以下三個數據:
same: 表示當前區間的字符是否相同,若相同則same等於該字符,否則same=-1
add: 表示當前區間的增量,對應CMD2操作所增加的K
delta和 inc : 這兩個變量是一組,其表示CMD4的操作。其含義爲,該區間最左起第1個元素值增量爲delta,此後每一個元素的增量比前一個多inc。即第2個元素的增量爲delta+inc,第3個元素的增量爲delta+inc+inc,…,第i個元素的增量爲delta+inc*(i-1)。舉個例子:
若我們對區間[1,3]進行了CMD4操作,實際的意義爲s1+1,s[2]+2,s[3]+3。對於表示區間[1,3]的節點,其Delta=1,inc=1。
若我們對區間[1,3]進行了2次CMD4操作,實際意義爲s1+2,s[2]+4,s[3]+6。則此時Delta=2,inc=2。而對於表示區間[2,3]的節點,其Delta=4,inc=2。因爲該區間左起第1個元素爲s[2]+4,故delta=4。
在本題中我們一開始便讀入了字符串,該字符串的每一個字符對應了樹的一個葉子節點。故我們一開始就需要建出整顆樹,其代碼:
// 該段代碼我們採用的是數組模擬線段樹
const int MAXN = 50001;
struct sTreeNode {
int left, right;
int same, add;
int delta, inc;
int lch, rch;
} tree[ MAXN << 2 ];
void createTree(int rt, int left, int right) {
tree[rt].left = left, tree[rt].right = right;
tree[rt].delta = tree[rt].step = 0;
tree[rt].add = 0;
if (left == right) { // 葉子節點
tree[rt].base = str[ left ] - 'A';
tree[rt].lch = tree[rt].rch = 0;
return ;
}
// 非葉子節點
tree[rt].base = -1;
tree[rt].lch = rt * 2, tree[rt].rch = rt * 2 + 1;
int mid = (tree[rt].left + tree[rt].right) >> 1;
createTree(tree[rt].lch, left, mid);
createTree(tree[rt].rch, mid + 1, right);
return ;
}
更新線段樹
在更新線段樹時,需要注意更新區間可能會出現i+SP <= n並且j+SP大於n時,此時要將區間分爲[i+SP…n]和[1…j+SP-n]兩個部分單獨處理。
更新線段樹信息的update函數:
// rt表示當前節點
// left,right表示此次操作的區間
// key表示此次操作K或Delta
// type表示此次操作的類型
void update(int rt, int left, int right, int key, int type) {
if (!rt) return ;
if (tree[rt].right < left || tree[rt].left > right) return ;
if (left <= tree[rt].left && tree[rt].right <= right) {
// 當前節點區間完全包含於[left,right]
// 更新當前區間信息
...
} else {
// 當前節點區間不完全包含於[left,right],則需要讓子區間來處理
// 傳遞當前區間的信息
...
// 更新當前區間信息
...
// 迭代處理
update(tree[rt].lch, left, right, key, type);
update(tree[rt].rch, left, right, key, type);
}
return ;
}
若當前區間包含於[left,right],根據操作的不同我們進行如下的處理:
CMD1: 直接更新區間的same值,同時將add,delta和inc置爲0
if (type == 1) {
tree[rt].same = key;
tree[rt].delta = 0, tree[rt].inc = 0;
tree[rt].add = 0;
}
CMD2: 累加到當前區間的add上
if (type == 2) {
tree[rt].add += key;
}
CMD4: 將新的delta和inc累加到當前區間的delta和inc上
if (type == 4) {
tree[rt].delta += key + (tree[rt].left - left);
tree[rt].inc ++;
}
當需要對子區間進行處理時,我們需要將當前區間的信息傳遞下去,此時需要判斷當前區間的same值:
// 傳遞當前區間的信息
int mid = (tree[rt].left + tree[rt].right) / 2;
if (tree[rt].base == -1) {
// lch
tree[ tree[rt].lch ].delta += tree[rt].delta;
tree[ tree[rt].lch ].step += tree[rt].step;
tree[ tree[rt].lch ].add += tree[rt].add;
// rch
tree[ tree[rt].rch ].delta += tree[rt].delta + (mid - tree[rt].left + 1) * tree[rt].step;
tree[ tree[rt].rch ].step += tree[rt].step;
tree[ tree[rt].rch ].add += tree[rt].add;
} else {
tree[ tree[rt].lch ].base = tree[ tree[rt].rch ].base = tree[rt].base;
tree[ tree[rt].lch ].delta = tree[rt].delta;
tree[ tree[rt].rch ].delta = tree[rt].delta + (mid - tree[rt].left + 1) * tree[rt].step;
tree[ tree[rt].lch ].step = tree[ tree[rt].rch ].step = tree[rt].step;
tree[ tree[rt].lch ].add = tree[ tree[rt].rch ].add = tree[rt].add;
}
當我們把當前區間的信息傳遞下去後,可以知道當前區間內的字符一定會發生改變,所以設置其same=1。同時由於當前區間的add,delta和inc信息已經傳遞下去,其本身的add,delta和inc設置爲0:
// 更新當前區間信息
tree[rt].base = -1;
tree[rt].delta = tree[rt].step = 0;
tree[rt].add = 0;
產生新的字符串
在這一步我們需要對整個線段樹進行一次遍歷,將所有的信息傳遞到葉子節點,再根據葉子節點的值產生我們新的字符串。
int f[ MAXN ]; // 記錄每個葉子節點的數值
void getResult(int rt) {
if (!rt) return ;
if (tree[rt].base != -1) {
int delta = tree[rt].delta;
for (int i = tree[rt].left; i <= tree[rt].right; ++i)
f[i] = tree[rt].base + tree[rt].add + delta, delta += tree[rt].step;
} else {
int mid = (tree[rt].left + tree[rt].right) / 2;
// lch
tree[ tree[rt].lch ].delta += tree[rt].delta;
tree[ tree[rt].lch ].step += tree[rt].step;
tree[ tree[rt].lch ].add += tree[rt].add;
// rch
tree[ tree[rt].rch ].delta += tree[rt].delta + (mid - tree[rt].left + 1) * tree[rt].step;
tree[ tree[rt].rch ].step += tree[rt].step;
tree[ tree[rt].rch ].add += tree[rt].add;
getResult(tree[rt].lch);
getResult(tree[rt].rch);
}
return ;
}
此時得到的s並不是我們最後的結果,還需要根據SP的值來輸出
void typeAns() {
for (int i = 0; i < n; ++i)
printf("%c", (char) (f[(SP + i) % n] + 'A'));
printf("\n");
return ;
}
結果分析
本題現場的通過率爲2%,一個有9名選手。
本題的思維複雜度並沒有第三題高,但是代碼量比較大。因此在前三題花費了較多精力的選手很難有足夠的時間來解決此題。
不過也有少數選手選擇放棄了第三題,直接從第四題入手並得到了滿分。
對於實際比賽來說,當拿到第四題這樣的題目,而時間又不足夠充裕時,直接採用樸素算法獲得部分分也是一個較好的選擇。
AC代碼:
#include <bits/stdc++.h>
using namespace std;
#define LL long long
const int Mod = 1e9 + 7;
const int maxn = 1e5 + 5;
const double eps = 0.00000001;
const int INF = 0x3f3f3f3f;
struct Tree{
LL same, add;
LL delte, inc;
int l, r;
int w;
}tree[maxn << 2];
string ss;
void creatTree(int k, int l, int r) {
tree[k].l = l; tree[k].r = r;
tree[k].delte = tree[k].add = tree[k].inc = 0;
if(l == r) {
tree[k].w = ss[l - 1] - 'A';
return ;
}
tree[k].w = -1;
int mid = (l + r) >> 1;
creatTree(k << 1, l, mid);
creatTree(k << 1 | 1, mid + 1, r);
}
void down(int rt) {
int mid = (tree[rt].l + tree[rt].r) >> 1;
if(tree[rt].w == -1) {
tree[rt << 1].delte += tree[rt].delte;
tree[rt << 1].inc += tree[rt].inc;
tree[rt << 1].add += tree[rt].add;
tree[rt << 1 | 1].delte += tree[rt].delte + (mid - tree[rt].l + 1) * tree[rt].inc;
tree[rt << 1 | 1].inc += tree[rt].inc;
tree[rt << 1 | 1].add += tree[rt].add;
}else {
tree[rt << 1].w = tree[rt << 1 | 1].w = tree[rt].w;
tree[rt << 1].delte = tree[rt].delte;
tree[rt << 1 | 1].delte = tree[rt].delte + (mid - tree[rt].l + 1) * tree[rt].inc;
tree[rt << 1].inc = tree[rt << 1 | 1].inc = tree[rt].inc;
tree[rt << 1].add = tree[rt << 1 | 1].add = tree[rt].add;
}
tree[rt].w = -1;
tree[rt].delte = tree[rt].inc = 0;
tree[rt].add = 0;
}
void update(int rt, int l, int r, LL key, int type) {
//cout << tree[rt].l << " " << tree[rt].r << " " << l << " " << r << endl;
if(!rt)return ;
if(tree[rt].r < l || tree[rt].l > r) return ;
if(tree[rt].l >= l && tree[rt].r <= r) {
if(type == 1) {
tree[rt].w = key;
tree[rt].delte = 0;
tree[rt].inc = 0;
tree[rt].add = 0;
}else if(type == 2) {
tree[rt].add += key;
}else if(type == 4) {
tree[rt].delte += key + (tree[rt].l - l);
tree[rt].inc ++;
}
}else {
int mid = (tree[rt].l + tree[rt].r) >> 1;
down(rt);
//cout << tree[rt].l << " " << tree[rt].r << " " << mid << endl;
update(rt << 1, l, r, key, type);
update(rt << 1 | 1, l, r, key, type);
}
}
int f[maxn];
void getResult(int rt) {
if(!rt) return ;
if(tree[rt].w != -1) {
int delte = tree[rt].delte;
for (int i = tree[rt].l; i <= tree[rt].r; i ++) {
f[i] = tree[rt].w + tree[rt].add + delte;
f[i] %= 26;
delte += tree[rt].inc;
}
}else {
down(rt);
getResult(rt << 1);
getResult(rt << 1 | 1);
}
return ;
}
int SP;
int n, op;
void typeAns() {
for (int i = 1; i <= n; i ++) {
char m;
if((SP + i) > n) m = f[SP + i - n] + 'A';
else m = f[SP + i] + 'A';
cout << m;
}
cout << endl;
return ;
}
int main()
{
while(scanf("%d %d", &n, &op) != EOF) {
cin >> ss;
string st;
creatTree(1, 1, n);
while(op --) {
cin >> st;
int type;
cin >> type;
if(type == 3) {
int k;
cin >> k;
SP = (SP + k) % n;
}else if(type == 1) {
int l, r;
char m;
cin >> l >> r >> m;
int key = m - 'A';
l += SP;
while(l > n) l -= n;
r += SP;
while(r > n) r -= n;
//cout << l << " " << r << endl;
if(l <= r) {
update(1, l, r, key, type);
}else {
update(1, l, n, key, type);
update(1, 1, r, key, type);
}
}else if(type == 2) {
int l, r;
LL key;
cin >> l >> r >> key;
l += SP; r += SP;
while(l > n) l -= n;
while(r > n) r -= n;
//cout << l << " " << r << endl;
if(l <= r) {
update(1, l, r, key, type);
}else {
update(1, l, n, key, type);
update(1, 1, r, key, type);
}
}else if(type == 4) {
int l, r;
cin >> l >> r;
LL key = 1;
l += SP; r += SP;
while(l > n) l -= n;
while(r > n) r -= n;
//cout << l << " " << r << endl;
if(l <= r) {
update(1, l, r, key, type);
}else {
update(1, l, n, key, type);
update(1, 1, r, key + n - l + 1, type);
}
}
//cout << SP << endl;
}
getResult(1);
typeAns();
}
return 0;
}