1. 程式人生 > 其它 >[題解]CF1534F1 Falling Sand (Easy Version)

[題解]CF1534F1 Falling Sand (Easy Version)

題目連結

#1.0 題目大意

一個網格圖,# 表示沙子,. 表示空,你可以選擇某些沙子使其掉落,沙子掉落過程中會擾動它下落路徑周圍四格(上下左右)的沙子,使他們一同掉落,問最少選擇幾塊沙子可以使全部沙子掉落。

#2.0 思路

#2.1 整體想法

考慮將 “擾動” 這一關係轉化成邊,“擾動” 這一關係是單向的,即若 \(A\) 能擾動 \(B\),不代表 \(B\) 能擾動 \(A\)

將圖中的 # 用 “擾動” 為邊連線後會得到一張有向圖,我們發現,在這張圖中存在許多強連通分量(SCC),很顯然,每一個 \(\text{SCC}\) 中的沙子擾動任意一個,該 \(\text{SCC}\) 中的所有沙子都會掉落,那麼我們便可以將每個 \(\text{SCC}\)

看做一個點,即用 \(\text{Tarjan}\) 演算法進行縮點。

縮點之後會得到一個有向無環圖(DAG),在這張圖上,無論如何都不會被擾動的點就是我們要選擇的點。那麼我們只需要知道這張 \(\text{DAG}\) 中有多少個入度為 \(0\) 的點即可。

下面來看每一步的細節。

#2.2 儲存

題目中只給了這樣一條對網格大小的限制:

\[n\cdot m\leq400000. \]

也就是說,\(n\)\(m\) 都有可能達到 \(4\cdot10^5\) 的級別,我們並不能直接開兩維大小都是 \(4\cdot10^5\) 的二維陣列進行儲存。但是,格子的數量範圍是確定的,我們可以直接開一維陣列 mp[400010]

來儲存格子的資訊。

轉換方法也很簡單,將格子 \((i,j)\) 編號為 \((i-1)\cdot m+j\) 即可,其實就是自左而右、自上而下地編號。

inline int get_ind(const int &i,const int &j){
    return (i - 1) * m + j;
}

#2.3 建圖

找到真正可能出現的 “擾動” 並轉化成邊是建圖的關鍵。我們來看一個沙子 \(A\) 下落時究竟可能會擾動哪些沙子(假設這些沙子存在)。

  • \(A\) 正上方一格的沙子;
  • \(A\) 正下方距離最近的沙子;
  • \(A\) 左邊一列,比 \(A\) 正下方距離最近的沙子高度更高的最高的沙子。
  • \(A\) 右邊一列,比 \(A\) 正下方距離最近的沙子高度更高的最高的沙子。

上圖中,\(A\) 被選中,只有 \(B,C,D,E\) 會被擾動,因為 \(F\) 會被 \(D\) 先擾動,\(G\) 會先被 \(C\) 擾動,正對應了我們上面的四種情況。

當然,假若 \(C\) 不存在,\(G\) 也不會被 \(A\) 擾動,顯然 \(B\) 會比 \(A\) 更早接觸 \(G.\)

#2.4 縮點 & 統計

縮點正常用 \(\text{Tarjan}\) 縮點即可。

因為我用的鏈式前向星存圖,記錄了邊的數量,在統計時直接列舉每條邊,判斷該邊兩端點是否在同一 \(\text{SCC}\) 中,如果不在,將終點所在的 \(\text{SCC}\) 的入度加一即可。

這裡不用去重邊,因為入度只要有,再多也是有,入度要沒有,咋整也沒有。

之後統計入度為 \(0\)\(\text{SCC}\) 的數量即可。

#3.0 程式碼實現

const int N = 500010;
const int INF = 0x3fffffff;

struct Edge{
    int u,v;
    int nxt;
};
Edge e[N << 2];

char mp[N];
int n,m,a[N],head[N],cnt = 1,ck[N];
int T,dfn[N],low[N],inst[N],st[N],frt;
int scc[N],scnt,icnt[N],ans;

/*獲取格子的編號*/
inline int get_ind(const int &i,const int &j){
    return (i - 1) * m + j;
}

/*加邊*/
inline void add(const int &u,const int &v){ 
    e[cnt].u = u;
    e[cnt].v = v;
    e[cnt].nxt = head[u];
    head[u] = cnt ++;
}

inline void tarjan(int x){ // Tarjan 演算法縮點
    dfn[x] = low[x] = ++ T;
    inst[x] = true;st[++ frt] = x;
    for (int i = head[x];i;i = e[i].nxt)
      if (!dfn[e[i].v]){
          tarjan(e[i].v);
          low[x] = min(low[x],low[e[i].v]);
      }
      else if (inst[e[i].v])
        low[x] = min(low[x],dfn[e[i].v]);
    if (dfn[x] == low[x]){
        int y = 0;++ scnt;
        do{
            y = st[frt --];
            scc[y] = scnt;
            inst[y] = false;
        }while (y != x);
    }
}

int main(){
    scanf("%d%d",&n,&m);
    for (int i = 1;i <= n;i ++)
      for (int j = 1;j <= m;j ++)
        cin >> mp[get_ind(i,j)];
    for (int i = 1;i <= n;i ++)
      scanf("%d",&a[i]);
    /*建圖*/
    for (int i = 1;i <= n;i ++)
      for (int j = 1;j <= m;j ++){
          if (mp[get_ind(i,j)] == '#'){
              ck[get_ind(i,j)] = true;//標記為沙子
              if (i > 1 && mp[get_ind(i - 1,j)] == '#')//如果正上方一格有沙子
                add(get_ind(i,j),get_ind(i - 1,j));
              /*找正下方最近的沙子*/
              for (int k = i + 1;k <= n;k ++)
                if (mp[get_ind(k,j)] == '#'){
                    add(get_ind(i,j),get_ind(k,j));
                    break;
                }
              /*左邊一列,比正下方距離最近的沙子高度更高的最高的沙子*/
              if (j > 1) for (int k = i;k <= n && (mp[get_ind(k,j)] != '#' || k ==  i);k ++)
                if (mp[get_ind(k,j - 1)] == '#'){
                    add(get_ind(i,j),get_ind(k,j - 1));
                    break;
                }
              /*右邊一列,比正下方距離最近的沙子高度更高的最高的沙子*/
              if (j < m) for (int k = i;k <= n && (mp[get_ind(k,j)] != '#' || k ==  i);k ++)
                if (mp[get_ind(k,j + 1)] == '#'){
                    add(get_ind(i,j),get_ind(k,j + 1));
                    break;
                }
          }
      }
    /*找 SCC, 縮點*/
    for (int i = 1;i <= n * m;i ++)
      if (ck[i] && !dfn[i]) tarjan(i);
    for (int i = 1;i < cnt;i ++)
      if (scc[e[i].u] != scc[e[i].v])
        icnt[scc[e[i].v]] ++;//統計入度
    for (int i = 1;i <= scnt;i ++)
      if (!icnt[i]) ans ++;//統計答案
    printf("%d",ans);
    return 0;
}

End

希望能給您帶來收穫。