1. 程式人生 > >hihor學習日記:hiho一下 第六十一週

hihor學習日記:hiho一下 第六十一週

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;
        }
    }
            
           

相關推薦

hihor學習日記hiho一下

http://hihocoder.com/contest/hiho61/problem/1 題意分析 給定一個字串s,以及對該字串s的 m 個操作。 字串s包含n個字元,下標為1…n。字元由’A’到’Z’構成,字元增加1表示該字元變為後續字元,比如’A’增加1是’B’,‘C’增加1是

hihor學習日記hiho一下

http://hihocoder.com/contest/hiho62/problem/1 題意分析 在瀏覽網頁的時候,快取技術能夠迅速地顯示頁面。這裡我們對瀏覽器的快取技術進行簡化:我們認為瀏覽器的快取大小為M,表示快取可以儲存M個頁面。 當用戶訪問URL時,瀏覽器會先到快取中查詢

hihor 學習日記hiho一下 (尤拉圖)

http://hihocoder.com/contest/hiho51/problem/1 思路: 可以把輪盤的轉換成成一張圖,圖由 2

hihor學習日記hiho一下

http://www.hihocoder.com/contest/hiho60/problem/1 主要內容來源於:http://www.hihocoder.com/discuss/question/2111 題意分析 給定只包含字母的兩個字串A,B,求A,B兩個字串的最長公共子

hihor學習日記hiho一下 (高斯消元)

http://hihocoder.com/contest/hiho57/problem/1 高斯消元的變種,因為圖很小所以而且每一個小格子都得為1,那麼就把圖中對某個小格子有作用的點標記起來,而他們的共同作用次數為奇數的話,小格子的狀態變化,反之不變, 注意這裡在處理矩陣時,對於第

hihor學習日記hiho一下 (點的雙連通分量)

http://hihocoder.com/contest/hiho55/problem/1 與邊的雙聯通分量類似,這個是求的割點 虛擬碼: void dfs(int u) { //記錄dfs遍歷次序 static int counter = 0; //記錄節點u

hihor 學習日記hiho一下 (割邊與割點)

http://hihocoder.com/contest/hiho52/problem/1 題意: 這道題就是求割邊與割點, 割邊與割點 思路: 大致就是用DFS樹來得到low,與dfn比較來判斷當前點的子樹上的點是否與當前點的父點相連,如果不聯,那麼去掉當前點

hihor 學習日記hiho一下 (快速冪+狀壓)

http://hihocoder.com/contest/hiho42/problems 題意: 求一個3xN的矩形有1x2的骨牌覆蓋有多少種方法 思路: 咋一看有點像輪廓線dp,但是因為N的範圍太大,若果使用O(n)的演算法會超時,所以可以觀察矩形, 與輪廓線類似,假設使用

hihor學習日記hiho一下 (高斯消元)

http://hihocoder.com/contest/hiho56/problem/1 高斯消元就是用多元一次方程求解 小Ho:<吧唧><吧唧><吧唧> 小Hi:小Ho,你還吃呢。想好了麼? 小Ho:腫搶著呢(正想著呢)…<吞嚥>

hihor 學習日記hiho一下 (SG函式)

http://hihocoder.com/contest/hiho46/problem/1 這個SG函式算是講的比較易懂了, AC程式碼: #include <bits/stdc++.h> using namespace std; #define LL long l

hihor 學習日記hiho一下 (尤拉路, DFS)

http://hihocoder.com/contest/hiho50/problem/1 題意: 一張圖,求每條邊經過一次走完整個圖, 思路: 因為前提已經是圖是一個尤拉圖,所以先DFS一條路徑,然後回溯是對於每個節點進行DFS,這樣就可以了 AC程式碼: #inc

hohor學習日記hiho一下

http://hihocoder.com/contest/hiho58/problem/1 題意分析 給定字串S,判定S是否存在子串S’滿足"aa…abb…bcc…c"的形式。其中abc為連續的三個字母,且a,b,c的數量相同。 原題目中數量相等的連續n(n>3)個字母也是

hihor 學習日記hiho一下 五十三(邊的雙連通分量)

http://hihocoder.com/contest/hiho53/problem/1 求出邊的雙聯通分量 與求割點與割邊類似,求邊的雙聯通分量找的是橋,有m橋,那麼就有m+1的分組。 而分組有點類似割邊,如果一個分組中存在割邊那麼這個分組就不是分組,所以這張圖的割邊就是分

hihor 學習日記hiho一下 四十三 (拓展)

http://hihocoder.com/contest/hiho43 前面的可以理解,後面的還需要摸索 AC程式碼: #include <bits/stdc++.h> using namespace std; #define LL long long cons

hohor學習日記 hiho一下 九十三(尤拉篩)

http://hihocoder.com/contest/hiho93/problem/1 存一下尤拉篩模板 #include <bits/stdc++.h> using namespace std; #define LL long long const int Mod

hihor日記hiho一下 九周

http://hihocoder.com/contest/hiho59/problem/1 題意分析 給定一個單執行緒程式執行的記錄,包含有每個函式啟動和結束的時間。判定該份記錄是否錯誤,主要的錯誤包含: 記錄中的時間不是嚴格遞增的 一個函式的結束時間比啟動時間更早 記錄中一個函

hihor學習日記:hiho一下

http://hihocoder.com/contest/hiho92/problem/1 小Hi:這種質數演算法是基於費馬小定理的一個擴充套件,首先我們要知道什麼是費馬小定理: 費馬小定理:對於質數p和任意整數a,有a^p ≡ a(mod p)(同餘)。反之,若滿足a^p ≡ a(mo

hihoCoder之hiho一下 九周 解題

題目1 : HIHODrinking Game 時間限制:10000ms 單點時限:1000ms 記憶體限制:256MB         Little Hi and Little Ho are playing adrinking game called HIHO. The

hihocoder hiho一下

先挖個坑 我看完這道題第一感覺是不用拓展歐幾里得,直接每走一步迴圈一次就行了,迴圈一個很大的次數(比如9999999,不會超時的次數),要是能找到就能找到了,找不到就找不到了。(小技巧:用這行程式碼就可以取消iostream的同步繫結,可以讓cin 的輸入速度和scanf一

hihor 學習日記hihor一下 四周(縮點+dfs)

http://hihocoder.com/contest/hiho54/problem/1 這次是強連通分量 因為強連通分量是一個迴路,所以一張圖的一個強連通分量可以看成一個點, 虛擬碼: AC程式碼: #include <bits/stdc++.h> usin