1. 程式人生 > 其它 >NOIP 2022 遊記(題解)

NOIP 2022 遊記(題解)

NOIP 2022 遊記(題解)

目錄

更好的閱讀體驗戳此進入

週五的時候通知考試取消了,改為三月份春測。。就挺突然的。

於是來(假裝)VP 一下,看看能做上多少分。

T1 [NOIP2022] 種花

題面

大概就是在有障礙的網格圖裡分別問能填充多少 C

F 形狀的圖形。嚴謹地敘述太複雜了,這裡就不多敘述,直接去看 洛谷題面 吧。

然後順便吐槽一下,這不愧是經典的 NOIP T1,感覺和上次的報數差不多,難度有點低了,比 CSP 的 T1 T2 都要簡單,可惜考試取消了沒考上。

Solution

做完之後翻了一下討論區,似乎還有一些高妙的懸線法之類的解法,可以很簡短地切掉這題。不過我不會我感覺不是很好像,所以這裡提供一個思維難度極低,程式碼略長的做法,比較無腦,但是是妥妥的 $ O(n^2) $。

大概就是手畫一下找性質,然後發現,對於 C 型來說,當我們固定其上下兩行之後,若最左側列從上至下都是 $ 0 $,那麼這樣的方案書就是上端橫行向右最大延申的 $ 0 $ 乘上下端延伸的。換句話說,我們就是要列舉確定每個 C

的左側的豎直的部分,然後把上下兩側可以延伸的乘起來加到方案裡。

然後這東西是 $ O(n^3) $ 的,也就是列舉每個點然後再列舉豎直能延申多少。

然後我們發現這東西實際上是可以優化的,也就是說當我們確定一個豎直部分上端點所在行為 $ i $ 的時候,如果這個點豎直向下最長可以延申 $ \xi $,那麼我們要的就是行數為 $ [i + 2, i + \xi] $ 之間的所有可能水平延伸的長度之和。

這樣的話我們就隨便做一個豎直方向上的字首和即可優化 $ O(n^3) \longrightarrow O(n^2) $,則對於 C 型的就過了。

對於維護最長能延申的距離,隨便寫一個 deque 即可,也就是雙端佇列,裡面存下標,對於每個值如果為 $ 0 $ 那麼直接插進去,如果為 $ 1 $ 那麼更新佇列裡所有的下標的值即可,具體可看程式碼,很好理解。

然後考慮 F 型,這個需要想一下,發現對於一個確定的 C 型,變成 F 型就是乘上其下端點能夠延伸的距離。這個正常做還是 $ O(n^3) $ 的,優化方式也類似,類比之前的方式,維護水平延申長度的時候直接乘上豎直延申長度,然後再做個字首和即可,具體實現可以參考程式碼。

Tips:有一些迴圈能壓在一起,會讓程式碼可讀性變差這裡就不壓了。然後因為是 VP 也沒打對拍,交上去最後兩個點 WA 了,隨便寫了個滿的資料才發現是最後加法沒有取模。。。不過這種錯誤隨便拍一下就能調出來,然後這題本身也很好調,思路非常直觀,一步一步檢查即可。

Code

#define _USE_MATH_DEFINES
#include <bits/stdc++.h>

#define PI M_PI
#define E M_E
#define npt nullptr
#define SON i->to
#define OPNEW void* operator new(size_t)
#define ROPNEW(arr) void* Edge::operator new(size_t){static Edge* P = arr; return P++;}

using namespace std;

mt19937 rnd(random_device{}());
int rndd(int l, int r){return rnd() % (r - l + 1) + l;}
bool rnddd(int x){return rndd(1, 100) <= x;}

typedef unsigned int uint;
typedef unsigned long long unll;
typedef long long ll;
typedef long double ld;

#define MOD (ll)(998244353)

template < typename T = int >
inline T read(void);

int N, M, C, F;
bool mp[1100][1100];
int cline[1100][1100];
int srow[1100][1100];
int crow[1100][1100];
int line_x_row[1100][1100];
ll srowF[1100][1100];
ll cntC(0), cntF(0);

int main(){
    int T = read(); (void)read();
    while(T--){
        memset(mp, 0, sizeof mp);
        memset(cline, 0, sizeof cline);
        memset(srow, 0, sizeof srow);
        memset(crow, 0, sizeof crow);
        memset(line_x_row, 0, sizeof line_x_row);
        memset(srowF, 0, sizeof srowF);
        cntC = cntF = 0;
        N = read(), M = read(), C = read(), F = read();
        for(int i = 1; i <= N; ++i)
            for(int j = 1; j <= M; ++j){
                char c = getchar();
                while(c != '1' && c != '0')c = getchar();
                mp[i][j] = c - '0';
            }
        for(int i = 1; i <= N; ++i){//i = line, j - row
            deque < int > cur;
            for(int j = 1; j <= M; ++j)
                if(!mp[i][j])cur.emplace_back(j);
                else while(!cur.empty())cline[i][cur.front()] = cur.size() - 1, cur.pop_front();
            while(!cur.empty())cline[i][cur.front()] = cur.size() - 1, cur.pop_front();
        }
        // for(int i = 1; i <= N; ++i)for(int j = 1; j <= M; ++j)printf("%d%c", cline[i][j], j == M ? '\n' : ' ');
        for(int i = 1; i <= M; ++i){//i - row, j - line
            deque < int > cur;
            for(int j = 1; j <= N; ++j)
                if(!mp[j][i])cur.emplace_back(j);
                else while(!cur.empty())crow[i][cur.front()] = cur.size() - 1, cur.pop_front();
            while(!cur.empty())crow[i][cur.front()] = cur.size() - 1, cur.pop_front();
        }
        // for(int i = 1; i <= N; ++i)for(int j = 1; j <= M; ++j)printf("%d%c", crow[j][i], j == M ? '\n' : ' ');
        for(int i = 1; i <= M; ++i)//i - row, j - line
            for(int j = 1; j <= N; ++j)
                srow[i][j] = srow[i][j - 1] + cline[j][i];
        for(int i = 1; i <= M; ++i)
            for(int j = 1; j <= N; ++j)
                line_x_row[i][j] = crow[i][j] * cline[j][i] % MOD;
        for(int i = 1; i <= M; ++i)
            for(int j = 1; j <= N; ++j)
                srowF[i][j] = (srowF[i][j - 1] + line_x_row[i][j]) % MOD;
        for(int i = 1; i <= M; ++i)
            for(int j = 1; j <= N - 2; ++j){
                if(crow[i][j] < 2)continue;
                (cntC += (ll)cline[j][i] * (srow[i][j + crow[i][j]] - srow[i][j + 1]) % MOD) %= MOD;
                if(!crow[i][j + 2])continue;
                (cntF += (ll)cline[j][i] * (srowF[i][j + crow[i][j]] - srowF[i][j + 1]) % MOD) %= MOD;
            }
        printf("%lld %lld\n", cntC * C, cntF * F);
    }
    fprintf(stderr, "Time: %.6lf\n", (double)clock() / CLOCKS_PER_SEC);
    return 0;
}

template < typename T >
inline T read(void){
    T ret(0);
    int flag(1);
    char c = getchar();
    while(c != '-' && !isdigit(c))c = getchar();
    if(c == '-')flag = -1, c = getchar();
    while(isdigit(c)){
        ret *= 10;
        ret += int(c - '0');
        c = getchar();
    }
    ret *= flag;
    return ret;
}

T2 [NOIP2022] 喵了個喵

題面

存在 $ n $ 個棧,給定 $ m $ 張卡牌,每張卡牌有圖案 $ a_i $,給定 $ k $,有 $ a_i \in [1, k] $。保證每種圖案的卡牌數均為偶數。初始所有棧為空。

你可以進行兩種操作:

  • 將剩餘卡牌中最先的一張插入某個棧,如果此時該棧最上方兩張卡牌的圖案相同則會被消除。
  • 選定兩個非空棧,如果這兩個棧底卡牌圖案相同則這兩張卡牌會被消除。

輸入保證有解,求一個合法操作方案。

Solution

這道題實際上是可以通過每個部分分來慢慢推出方案的。

首先第一檔部分分,對於 $ k = 2n - 2 $,不難想到,我們將一個棧空出來作為備用棧,並保證其它棧裡永遠最多隻有兩個卡牌,也就是一個在棧頂一個在棧底,實現上可以把所有可以放卡牌的棧頂和棧底存下來並動態維護,注意要先用棧底再插棧頂。然後順序遍歷所有卡牌。如果當前所有棧中不存在這個顏色,那麼找個棧內卡牌數不足 $ 2 $ 的丟進去。如果存在的話,考慮如果其在棧頂,那就把這個卡牌放在對應的棧頂來消除,然後重新將這個位置置為可以放置卡牌。如果在棧底那麼將這張卡牌放在備用棧裡,然後用操作二將這一對刪除即可。

然後考慮後面的 $ k = 2n - 1 $ 的部分分。我們還是按照剛才的思路,保留一個備用棧,然後老樣子處理卡牌。顯然處理一段時間之後就會出現一個狀態,即已經有 $ 2n - 2 $ 個顏色的卡牌被放到了棧裡,然後此時又多了個新的棧中不存在的顏色的卡牌。

這時不難想到有如下方案:我們先將這張卡牌擱置,令其顏色為 $ a $,然後繼續向後遍歷,如果再碰到了顏色為 $ a $ 的直接結束,這裡還要注意如果迴圈結束之後 $ a $ 的兩個位置的答案沒有被更新,那麼需要額外更新一下,即把兩個 $ a $ 都塞進備用棧裡消除一下。然後考慮過程中碰到的其它顏色的卡牌,顯然第一次遇到的時候其一定是在前面的棧中存在的,因為顏色一共就有 $ 2n - 1 $ 個。如果碰到在棧頂的顏色,那麼直接將其消除,並且標記這個該顏色的位置為這個位置,如果本次遍歷過程中再次碰到這個顏色還要再次把這個顏色放到這個位置上。如果碰到在棧底的顏色,那麼這時候考慮當前其對應的棧頂是否已經在本次遍歷過程中被刪除。我們令這個棧底顏色為 $ b $,棧頂顏色為 $ c $。

如果 $ c $ 被刪除的話,那麼我們將 $ a $ 放在備用棧裡,然後將新的 $ b $ 放到原來的 $ b $ 棧,將其消除,則此時備用棧的位置會多一個棧頂的空位,$ b $ 棧會變空,則我們這時直接將 $ b $ 棧作為新的備用棧即可讓狀態又變成原來的樣子。

如果 $ c $ 依然存在,那麼先將 $ a $ 放在 $ b $ 棧裡,然後再將新的 $ b $ 放到備用棧裡,這樣可以將 $ b $ 棧和備用棧棧底的卡牌對應消除掉,此時則 $ b $ 棧又變回兩個元素,備用棧又為空。

然後這個時候我們可能會發現一點問題,比如如果有一個實際的結果:$ (2, 1, 3, 1, 1, 1, 1) $(這裡記左側為棧底,右側為棧頂),但是按照我們的寫法實際是變成了 $ (2, 1, 1, 1, 1, 1, 3) $,表面上這些是不同的,但是結果上其都為 $ (2, 1, 3) $。對於剩下的 $ 1 $ 為奇數個的,寫一下會發現也是等效的。

思路看起來還是比較簡單容易想的,但是這東西實現起來就能感受到噁心了,這裡為了方便理解,大概說明一下程式碼中的部分關鍵變數名的作用。

對於每個 pair 前者表示棧的索引,後者為棧頂或棧底,$ 1 $ 表示棧底,$ 0 $ 表示棧頂,ans 存的是答案,mp 指的是某個顏色對應的在棧中的位置,stk 儲存的是對應位置中存的是什麼顏色,mark 是指對應顏色被固定的位置(在遇到 $ a $ 之後遍歷的時候使用),unfix 儲存的是所有空著的可以使用的棧的位置。

寫在後面

建議先做 T3 T4 再做這題

這個應該算是我寫過最噁心的題之一了,調程式碼已經人調麻了。如果是在考場上我絕對部分分一拿就跑路。最開始有個思路然後寫了一百多行寫完了,除錯的過程中發現假掉了。。。然後是參考的 @sssmzy 的思路,上面說的也是這個做法,然後做完就開始陰間除錯的過程。最後本地對拍全過然後交上去就 WA,各種找樣例各種手動模擬,不知道調了多久才找到問題:備用棧切換的時候 unfix 中可能有剩餘的新棧中的空位,需要刪除。

然後改來改去的程式碼可能變得很難看,敬請諒解,並且我的實現也可能不夠精細,或許會有更簡便的方法和更簡便的實現,這裡只是提供一個思路。還有個問題就是這個 erase 理論上最壞可能是 $ O(n) $ 的,理論上似乎最壞情況下 $ O(S) $ 次,$ O(nS) $ 似乎理論上能被卡,但是這也是理論上,實際上完全卡不滿,基本上是無法被卡掉的,並且本身 $ O(nS) $ 也超的不多,再加上 O2 那基本上就是穩穩過。

Code

#define _USE_MATH_DEFINES
#include <bits/stdc++.h>

#define PI M_PI
#define E M_E
#define npt nullptr
#define SON i->to
#define OPNEW void* operator new(size_t)
#define ROPNEW(arr) void* Edge::operator new(size_t){static Edge* P = arr; return P++;}

using namespace std;

mt19937 rnd(random_device{}());
int rndd(int l, int r){return rnd() % (r - l + 1) + l;}
bool rnddd(int x){return rndd(1, 100) <= x;}

typedef unsigned int uint;
typedef unsigned long long unll;
typedef long long ll;
typedef long double ld;

template < typename T = int >
inline T read(void);

int N, M, K;
int a[2100000];

namespace Sub1{
    struct Ans{int flag; int a, b;};
    pair < int, bool > mp[610];
    int stk[310][2];
    basic_string < pair < int, bool > > unfix;
    basic_string < Ans > ans[2100000];
    void Make(void){
        memset(mp, 0, sizeof mp);
        memset(stk, 0, sizeof stk);
        for(int i = 1; i <= M; ++i)ans[i].clear(), ans[i].shrink_to_fit();
        unfix.clear();
        for(int i = N - 1; i >= 1; --i)unfix += {{i, 0}, {i, 1}};
        for(int i = 1; i <= M; ++i){
            int col(a[i]), cur(N);
            if(mp[col].first){
                if(mp[col].second){
                    ans[i] += {Ans{1, cur}, Ans{2, mp[col].first, cur}};
                    if(stk[mp[col].first][0])
                        unfix += {mp[col].first, 0},
                        mp[stk[mp[col].first][0]].second = 1;
                    else
                        unfix += {mp[col].first, 1};
                    stk[mp[col].first][1] = stk[mp[col].first][0];
                    stk[mp[col].first][0] = 0;
                    mp[col] = {0, 0};
                }
                else{
                    ans[i] += {Ans{1, mp[col].first}};
                    unfix += mp[col];
                    stk[mp[col].first][mp[col].second] = 0;
                    mp[col] = {0, 0};
                }
            }else{
                if(!unfix.empty()){
                    ans[i] += Ans{1, unfix.back().first};
                    mp[a[i]] = unfix.back();
                    stk[mp[col].first][mp[col].second] = col;
                    unfix.pop_back();
                }else{
                    printf("Error!\n"), exit(1);
                }
            }
            // printf("Finish %d\n", i);
            // printf("unfix: ");
            // for(auto tmpp : unfix)printf("(%d, %d) ", tmpp.first, tmpp.second ? 1 : 0);
            // printf("\n");
            // for(int i = 1; i <= 4; ++i)
            //     printf("mp[%d] = %d, %d\n", i, mp[i].first, mp[i].second ? 1 : 0);
        }
        ll tot(0);
        for(int i = 1; i <= M; ++i)tot += ans[i].size();
        printf("%lld\n", tot);
        for(int i = 1; i <= M; ++i)
            for(auto opt : ans[i])
                if(opt.flag == 1)printf("1 %d\n", opt.a);
                else printf("2 %d %d\n", opt.a, opt.b);
    }
}
namespace Sub2{//1 - down, 0 - up
    struct Ans{int flag; int a, b;};
    basic_string < Ans > ans[2100000];
    pair < int, bool > mp[610];
    int cur(N);
    basic_string < pair < int, bool > > unfix;
    int stk[310][2];
    pair < int, bool > mark[610];
    void Make(void){
        for(int i = 1; i <= M; ++i)ans[i].clear(), ans[i].shrink_to_fit();
        memset(mp, 0, sizeof mp);
        memset(mark, 0, sizeof mark);
        memset(stk, 0, sizeof stk);
        cur = N, unfix.clear();
        for(int i = N - 1; i >= 1; --i)unfix += {{i, 0}, {i, 1}};
        for(int i = 1; i <= M; ++i){
            int col = a[i];
            if(mp[col].first){
                if(mp[col].second){
                    ans[i] += {Ans{1, cur}, Ans{2, mp[col].first, cur}};
                    if(stk[mp[col].first][0])
                        unfix += {mp[col].first, 0},
                        mp[stk[mp[col].first][0]].second = 1;
                    else
                        unfix += {mp[col].first, 1};
                    stk[mp[col].first][1] = stk[mp[col].first][0];
                    stk[mp[col].first][0] = 0;
                    mp[col] = {0, 0};
                }
                else{
                    ans[i] += {Ans{1, mp[col].first}};
                    unfix += mp[col];
                    stk[mp[col].first][mp[col].second] = 0;
                    mp[col] = {0, 0};
                }
            }else{
                if(!unfix.empty()){
                    ans[i] += Ans{1, unfix.back().first};
                    mp[col] = unfix.back();
                    stk[mp[col].first][mp[col].second] = col;
                    unfix.pop_back();
                }else{
                    int nxt;
                    for(nxt = i + 1; nxt <= M; ++nxt){
                        if(a[nxt] == col)break;
                        // if(!ans[nxt].empty())continue;
                        int ccol(a[nxt]);
                        if(!mp[ccol].first){
                            ans[nxt] += Ans{1, mark[ccol].first};
                            unfix.erase(find(unfix.begin(), unfix.end(), mark[ccol]));
                            mp[ccol] = mark[ccol];
                            stk[mp[ccol].first][mp[ccol].second] = ccol;
                            mark[ccol] = {0, 0};
                            // if(!unfix.empty()){
                            //     ans[nxt] += Ans{1, unfix.back().first};
                            //     mp[ccol] = unfix.back();
                            //     stk[mp[ccol].first][mp[ccol].second] = ccol;
                            //     unfix.pop_back();
                            // }else{
                            //     printf("Error\n"), exit(0);
                            // }
                        }else{
                            if(!mp[ccol].second){
                                mark[ccol] = mp[ccol];
                                ans[nxt] += Ans{1, mp[ccol].first};
                                unfix += mp[ccol];
                                stk[mp[ccol].first][mp[ccol].second] = 0;
                                mp[ccol] = {0, 0};
                            }else{
                                if(stk[mp[ccol].first][0]){
                                    ans[i] += Ans{1, mp[ccol].first};
                                    ans[nxt] += {Ans{1, cur}, Ans{2, mp[ccol].first, cur}};
                                    int stkpos = mp[ccol].first;
                                    mp[stk[mp[ccol].first][0]].second = 1;
                                    mp[col] = {mp[ccol].first, 0};
                                    mp[ccol] = {0, 0};
                                    stk[stkpos][1] = stk[stkpos][0];
                                    stk[stkpos][0] = col;
                                    break;
                                }else{
                                    stk[mp[ccol].first][0] = 0;
                                    ans[i] += Ans{1, cur};
                                    ans[nxt] += Ans{1, mp[ccol].first};
                                    mp[col] = {cur, 1};
                                    unfix += {cur, 0};
                                    cur = mp[ccol].first;
                                    for(auto it = unfix.begin(); it != unfix.end();)
                                        if(it->first == cur)it = unfix.erase(it);
                                        else advance(it, 1);
                                    stk[mp[ccol].first][1] = 0;
                                    mp[ccol] = {0, 0};
                                    break;
                                }
                            }
                        }
                    }
                    if(ans[i].empty()){
                        ans[i] += Ans{1, cur};
                        ans[nxt] += Ans{1, cur};
                    }i = nxt;
                }
            }
            // printf("Finish %d\n", i);
            // printf("unfix: ");
            // for(auto tmpp : unfix)printf("(%d, %d) ", tmpp.first, tmpp.second ? 1 : 0);
            // printf("\n");
            // for(int i = 1; i <= 3; ++i)
            //     printf("mp[%d] = %d, %d\n", i, mp[i].first, mp[i].second ? 1 : 0);
        }
        ll tot(0);
        for(int i = 1; i <= M; ++i)tot += ans[i].size();
        printf("%lld\n", tot);
        for(int i = 1; i <= M; ++i)
            for(auto opt : ans[i])
                if(opt.flag == 1)printf("1 %d\n", opt.a);
                else printf("2 %d %d\n", opt.a, opt.b);
    }
}

int main(){
    int T = read();
    while(T--){
        N = read(), M = read(), K = read();
        for(int i = 1; i <= M; ++i)a[i] = read();
        if(K == N * 2 - 2)Sub1::Make();
        else Sub2::Make();
    }

    fprintf(stderr, "Time: %.6lf\n", (double)clock() / CLOCKS_PER_SEC);
    return 0;
}

template < typename T >
inline T read(void){
    T ret(0);
    int flag(1);
    char c = getchar();
    while(c != '-' && !isdigit(c))c = getchar();
    if(c == '-')flag = -1, c = getchar();
    while(isdigit(c)){
        ret *= 10;
        ret += int(c - '0');
        c = getchar();
    }
    ret *= flag;
    return ret;
}

T3 [NOIP2022] 建造軍營

題面

給定連通圖,需要選定至少一個關鍵點,然後保護至少一條邊,被保護的邊不會被破壞,需要保證無論破壞哪條邊都不會影響關鍵點之間的連通性。求方案數。

Solution

這題我感覺還是有點難度的,主要是細節很多,如果不是我寫的太麻煩了的話,感覺評個紫應該比較合理(不過似乎洛谷上西西弗的題難度評級都偏低一些

首先這個題意讀完應該就能想到 Tarjan 邊雙縮點吧,這個還是很顯然的。如果在同一個邊雙裡,那麼無論刪去這裡面的任意哪條邊都不會使方案不合法,所以可以直接進行 e-BCC 縮點,這樣對於每一個 e-BCC 裡,如果設其中的邊數為 $ \xi $,點數為 $ \epsilon $,那麼只考慮這個 e-BCC 的話,如果不在其中設定關鍵點(軍營)其方案數就是 $ 2^{\xi} $,設定的話就是 $ 2^\xi \times (2^\epsilon - 1) $。

然後同時不難發現因為圖本身是連通的,所以進行 e-BCC 縮點之後原圖就會變成一棵樹,所以此時這個東西就很顯然變成了一個 樹形DP。或者說就是變成了 $ 12 - 14 $ 的部分分,如果想通了樹形 DP,再縮個點之後就直接可以切了。

考慮 DP,不難想到一個很顯然的狀態,設 $ dp(p, 0/1) $ 表示考慮到點 $ p $ 所在子樹,且其子樹中是否設定關鍵點(軍營)的合法方案數,且此時必須滿足 $ p $ 子樹的所有關鍵節點都和 $ p $ 連通,這個繼續討論下去就會發現如果不這樣設定就無法轉移了。

考慮轉移,對於任意一個 $ p $ 的子節點 $ son $,首先顯然有:

\[dp(p, 0) \longleftarrow dp(p, 0) \times dp(son, 0) \times 2 \]

然後考慮 $ dp(p, 1) $ 的轉移:

  1. 如果從 $ dp(son, 0) $ 轉移,那麼這條邊(橋)就是可保護也可不保護,且 $ p $ 點必須設為關鍵點。
\[dp(p, 1) \longleftarrow dp(p, 1) \times dp(son, 0) \times 2 \]
  1. 如果從 $ dp(son, 1) $ 轉移,那麼則需要考慮,對於 $ p $ 為關鍵點的,則橋必須被保護。
\[dp(p, 1) \longleftarrow dp(p, 1) \times dp(son, 1) \]
  1. 同2,對於 $ p $ 不為關鍵點的,顯然要從前面所有子樹轉移後的 $ dp(p, 0) $ 轉移而來,且此時這個橋也必須被保護,因為我們要保證子樹的關鍵節點和 $ p $ 連通。
\[dp(p, 1) \longleftarrow dp(p, 1) + dp(p, 0) \times dp(son, 1) \]

同時注意對於這裡的轉移 $ dp(p, 0/1) $,我們用到的都是上一次的 $ dp(p, 0/1) $,這個東西可以直接類似滾動陣列思想寫一下,也就是先轉移 $ dp(p, 1) $ 然後 $ dp(p, 0) $。

於是我們就發現,這東西假了。。

因為我們漏下了一些情況,再回頭考慮我們設定的時候為必須滿足 $ p $ 子節點的關鍵節點都和 $ p $ 連通,但是如果不連通,且除 $ p $ 子樹之外的所有節點都非關鍵節點,那麼此時我們這幾條從 $ p $ 子樹中關鍵節點到 $ p $ 的邊無論是否保護都是合法的,但是我們卻忽略了,所以此時要考慮維護一下這個東西。

這個東西我們可以考慮將狀態改為 $ dp(p, 0/1/2) $,分別表示考慮 $ p $ 所在子樹,子樹內沒有關鍵點,或有關鍵點且要求子樹外沒有關鍵點,或有關鍵點且不要求子樹外沒有關鍵點。當然這個東西寫起來要多考慮一些東西,所以我們也可以換個思路,直接嘗試去補上漏下的。

我們實際上可以在 e-BCC 縮點之後再跑一遍 dfs,維護一下對應子樹中的包括被縮掉的所有邊的數量,設其為 $ s_p $,(或者給 樹形DP 加個返回值記錄一下好像也行,不過細節會更多)不失一般性,設根為 $ 1 $,那麼當我們維護完 $ dp(p, 1) $ 的時候,我們算漏的方案數似乎就是:$ dp(p, 1) \times 2^{s_1 - s_p} $,也就是子樹內設定關鍵點,子樹外不設定關鍵點,則子樹外的所有邊都是任意的。然後你會發現這個東西又假了。。

考慮發現如果 $ p \longleftrightarrow fa $ 的邊被連上了,那麼這個東西已經被 $ dp(fa, 1) $ 給計算過了,這樣會重,所以我們欽定 $ p \longleftrightarrow fa $ 的邊不選擇,即可做到不重不漏,這樣最終此處的貢獻就是 $ dp(p, 1) \times 2^{s_1 - s_p - 1} $。

然後注意對於根節點時,其不存在我們剛才說的漏下的那些方案,但需要加上一般的答案,也就是 $ dp(1, 1) $。

至此,這道細節巨多的題就做完了。

Code

#define _USE_MATH_DEFINES
#include <bits/stdc++.h>

#define PI M_PI
#define E M_E
#define npt nullptr
#define SON i->to
#define OPNEW void* operator new(size_t)
#define ROPNEW(arr) void* Edge::operator new(size_t){static Edge* P = arr; return P++;}

using namespace std;

mt19937 rnd(random_device{}());
int rndd(int l, int r){return rnd() % (r - l + 1) + l;}
bool rnddd(int x){return rndd(1, 100) <= x;}

typedef unsigned int uint;
typedef unsigned long long unll;
typedef long long ll;
typedef long double ld;

#define MOD (ll)(1e9 + 7)

template < typename T = int >
inline T read(void);

struct Edge{
    Edge* nxt;
    Edge* rev;
    int to;
    bool vis;
    OPNEW;
}ed[4100000];
ROPNEW(ed);
Edge* head[1100000];
Edge* ehead[1100000];

int N, M;
int dfn[1100000], low[1100000], eBCC(0), belong[1100000];
int cnte[1100000], cntv[1100000];
int sume[1100000];
bool inS[1100000];
ll dp[1100000][2];
ll pow2[2100000];
ll ans(0);

void Tarjan(int p = 1){
    static stack < int > cur;
    static int cdfn(0);
    cur.push(p); inS[p] = true;
    low[p] = dfn[p] = ++cdfn;
    for(auto i = head[p]; i; i = i->nxt){
        if(i->vis)continue;
        i->vis = i->rev->vis = true;
        if(!dfn[SON]){
            Tarjan(SON);
            low[p] = min(low[p], low[SON]);
            if(low[SON] > dfn[p]){
                ++eBCC;
                while(true){
                    int tp = cur.top(); cur.pop();
                    belong[tp] = eBCC;
                    inS[tp] = false;
                    ++cntv[eBCC];
                    if(tp == SON)break;
                }
            }
        }else if(inS[SON])
            low[p] = min(low[p], dfn[SON]);
    }
    if(p == 1 && !cur.empty()){
        ++eBCC;
        while(true){
            int tp = cur.top(); cur.pop();
            belong[tp] = eBCC;
            inS[tp] = false;
            ++cntv[eBCC];
            if(tp == p)break;
        }
    }
}
void dfs_edge(int p = 1, int fa = 0){
    sume[p] = cnte[p];
    for(auto i = ehead[p]; i; i = i->nxt){
        if(SON == fa)continue;
        dfs_edge(SON, p);
        sume[p] += sume[SON] + 1;
    }
}
void MakeDP(int p = 1, int fa = 0){
    dp[p][0] = pow2[cnte[p]];
    dp[p][1] = (pow2[cntv[p]] - 1) * pow2[cnte[p]] % MOD;
    for(auto i = ehead[p]; i; i = i->nxt){
        if(SON == fa)continue;
        MakeDP(SON, p);
        dp[p][1] = (dp[p][1] * dp[SON][0] * 2 % MOD + dp[p][1] * dp[SON][1] % MOD + dp[p][0] * dp[SON][1] % MOD) % MOD;
        dp[p][0] = dp[p][0] * dp[SON][0] * 2 % MOD;
    }(ans += (p == 1 ? dp[p][1] : dp[p][1] * pow2[sume[1] - sume[p] - 1] % MOD)) %= MOD;
}

int main(){
    N = read(), M = read();
    pow2[0] = 1;
    for(int i = 1; i <= 2010000; ++i)pow2[i] = pow2[i - 1] * 2 % MOD;
    for(int i = 1; i <= M; ++i){
        int s = read(), t = read();
        head[s] = new Edge{head[s], npt, t};
        head[t] = new Edge{head[t], npt, s};
        head[s]->rev = head[t], head[t]->rev = head[s];
    }Tarjan();
    for(int p = 1; p <= N; ++p)
        for(auto i = head[p]; i; i = i->nxt)
            if(belong[p] != belong[SON])
                ehead[belong[p]] = new Edge{ehead[belong[p]], npt, belong[SON]};
            else ++cnte[belong[p]];
    for(int i = 1; i <= eBCC; ++i)cnte[i] >>= 1;
    dfs_edge();
    MakeDP();
    printf("%lld\n", ans);
    fprintf(stderr, "Time: %.6lf\n", (double)clock() / CLOCKS_PER_SEC);
    return 0;
}

template < typename T >
inline T read(void){
    T ret(0);
    int flag(1);
    char c = getchar();
    while(c != '-' && !isdigit(c))c = getchar();
    if(c == '-')flag = -1, c = getchar();
    while(isdigit(c)){
        ret *= 10;
        ret += int(c - '0');
        c = getchar();
    }
    ret *= flag;
    return ret;
}

ABC補完再繼續回來寫

T4

題面

Solution

Code

UPD

update-2022__ 初稿