[題解]CF1534F1 Falling Sand (Easy Version)
#1.0 題目大意
一個網格圖,#
表示沙子,.
表示空,你可以選擇某些沙子使其掉落,沙子掉落過程中會擾動它下落路徑周圍四格(上下左右)的沙子,使他們一同掉落,問最少選擇幾塊沙子可以使全部沙子掉落。
#2.0 思路
#2.1 整體想法
考慮將 “擾動” 這一關係轉化成邊,“擾動” 這一關係是單向的,即若 \(A\) 能擾動 \(B\),不代表 \(B\) 能擾動 \(A\)。
將圖中的 #
用 “擾動” 為邊連線後會得到一張有向圖,我們發現,在這張圖中存在許多強連通分量(SCC),很顯然,每一個 \(\text{SCC}\) 中的沙子擾動任意一個,該 \(\text{SCC}\) 中的所有沙子都會掉落,那麼我們便可以將每個 \(\text{SCC}\)
縮點之後會得到一個有向無環圖(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
希望能給您帶來收穫。