2021牛客暑期多校訓練營第8場 F題(兩種做法)
阿新 • • 發佈:2021-08-10
2021牛客暑期多校訓練營8 F題(兩種做法)
2021牛客暑期多校訓練營8 F題(兩種做法)
連結:https://ac.nowcoder.com/acm/contest/11259/F
來源:牛客網
題意:
有一個 \(n*m\) 的貨運中心,每個格子為0或1,1代表有障礙不可通行。
有3種機器人,一種只能向右走,一種只能向下走,一種可以向下或向右走。
現在有 \(Q\) 個詢問,每個詢問包含了機器人型號,一個起點座標和一個終點座標,請問機器人能否將貨物從起點運送到終點
輸入
4 4
0000
0000
0001
0010
4
1 1 1 1 4
2 2 1 2 4
3 1 1 3 3
3 3 3 4 4
輸出
no
yes
yes
no
補題:
我首先花了一晚上去看某十字的程式碼,理解了一半
另一半感覺像是某種玄學的按64位分塊後再狀壓,不是很理解,不過這部分可以用bitset搞過去
從右下往左上列舉,設 \(f[i][j][k]\)表示列舉到當前位置時(注意 \(i\) 不是當前行,\(j\) 是當前列),能否走到 \((i,k)\) 這個位置
第三維 \(k\) 是可以被壓掉的,把 \(f[i][j]\) 設為bitset,那麼其實有 \(f[i][j] = f[i][j] \quad or\quad f[i][j+1]\)
這個狀態很玄妙。。。可以結合程式碼手動跑感受一下
看不懂也沒事,標答的分治做法更好理解(也更快)
bitset<502> f[505][505]; int mp[505][505], ans[500015]; int dw[505][505], rt[505][505]; struct ques{ int x2,y2,id; }; vector<ques> ot[505][505]; void solve(){ int n,m,Q; cin >> n >> m; for(int i=1;i<=n;i++){ string s; cin >> s; for(int j=0;j<m;j++){ mp[i][j+1] = s[j] - '0'; } } for(int i=1;i<=n;i++){ for(int j=1;j<=m;j++){ rt[i][j] = rt[i][j-1] + mp[i][j]; dw[i][j] = dw[i-1][j] + mp[i][j]; } } cin >> Q; for(int i=1;i<=Q;i++){ int tp, x1, y1 ,x2 ,y2; cin >> tp >> x1 >> y1 >> x2 >> y2; if(x2 < x1 || y2 < y1) ans[i] = 0; else if(tp==1){ ans[i] = (y1 == y2 && dw[x1][y1] == dw[x2][y2]); }else if(tp==2){ ans[i] = (x1 == x2 && rt[x1][y1] == rt[x2][y2]); }else{ ot[x1][y1].push_back({x2, y2, i}); } } for(int i=0;i<=n+2;i++) for(int j=0;j<=m+2;j++) f[i][j] = 0; for(int i=n;i>=1;i--){ for(int j=m;j>=1;j--){ if(mp[i][j]){ for(int k=n;k>=i;k--){ f[k][j] = 0; } }else{ f[i][j][j] = true; if(j!=m) for(int k=n;k>=i;k--){ f[k][j] |= f[k][j+1]; } } for(int k=0;k<ot[i][j].size();k++){ int xv = ot[i][j][k].x2, yv = ot[i][j][k].y2; ans[ot[i][j][k].id] = f[xv][j][yv]; } } } for(int i=1;i<=Q;i++){ if(ans[i]) cout << "yes\n"; else cout << "no\n"; } }
標答做法(分治):
對整個矩陣豎著來一刀,劃分成 \([l, mid]\), \([mid+1, r]\)
考慮處理橫跨左右的詢問,以 \(mid\) 這一列為樞紐
對所有左邊的點,處理出從它開始能走到中線上哪些位置
對所有右邊的點,處理出中線上哪些點開始能走到這個它
顯然任何橫跨兩邊的詢問,如果可行那麼它們必然存在一個樞紐
其他詢問分治做就可以啦
int n,m,Q; bitset<502> f[505][505]; int mp[505][505], ans[500015]; int dw[505][505], rt[505][505]; struct ques{ int x2,y2,id; }; vector<ques> ot[505][505]; void CDQ(int l, int r){ if(l==r){//邊界處理 for(int i=n;i>=1;i--){ if(mp[i][l]) f[i][l] = 0; else{ f[i][l][i] = 1; f[i][l] |= f[i+1][l]; } for(int k=0;k<ot[i][l].size();k++){ int xv = ot[i][l][k].x2, yv = ot[i][l][k].y2; if(l == yv && xv >= i){ ans[ot[i][l][k].id] = f[i][l][xv]; } } } for(int i=1;i<=n;i++) f[i][l] = 0; return; } int mid = (l+r)/2; CDQ(l, mid); CDQ(mid+1, r); for(int i=1;i<=n;i++){//計算右半邊每個點能由哪些中點到達 if(mp[i][mid]) f[i][mid] = 0; else{ f[i][mid][i] = 1; f[i][mid] |= f[i-1][mid]; } } for(int i=1;i<=n;i++){ for(int j=mid+1;j<=r;j++){ if(mp[i][j]) f[i][j] = 0; else{ f[i][j] |= f[i-1][j]; f[i][j] |= f[i][j-1]; } } } for(int i=1;i<=n;i++) f[i][mid] = 0; for(int i=n;i>=1;i--){//計算左半邊每個點能到達哪些中點 if(mp[i][mid]) f[i][mid] = 0; else{ f[i][mid][i] = 1; f[i][mid] |= f[i+1][mid]; } } for(int i=n;i>=1;i--){ for(int j=mid-1;j>=l;j--){ if(mp[i][j]) f[i][j] = 0; else{ f[i][j] |= f[i+1][j]; f[i][j] |= f[i][j+1]; } } } for(int i=1;i<=n;i++){//處理答案 for(int j=l;j<=mid;j++){ for(int k=0;k<ot[i][j].size();k++){ int xv = ot[i][j][k].x2, yv = ot[i][j][k].y2; if(mid < yv && yv <= r){ ans[ot[i][j][k].id] = (f[i][j] & f[xv][yv]).count(); } } } } for(int i=1;i<=n;i++) for(int j=l;j<=r;j++) f[i][j] = 0;//撤銷 } void solve(){ cin >> n >> m; for(int i=1;i<=n;i++){ string s; cin >> s; for(int j=0;j<m;j++){ mp[i][j+1] = s[j] - '0'; } } for(int i=1;i<=n;i++){ for(int j=1;j<=m;j++){ rt[i][j] = rt[i][j-1] + mp[i][j]; dw[i][j] = dw[i-1][j] + mp[i][j]; } } cin >> Q; for(int i=1;i<=Q;i++){ int tp, x1, y1 ,x2 ,y2; cin >> tp >> x1 >> y1 >> x2 >> y2; if(x2 < x1 || y2 < y1) ans[i] = 0; else if(tp==1){ ans[i] = (y1 == y2 && dw[x1][y1] == dw[x2][y2]); }else if(tp==2){ ans[i] = (x1 == x2 && rt[x1][y1] == rt[x2][y2]); }else{ ot[x1][y1].push_back({x2, y2, i}); } } CDQ(1, m); for(int i=1;i<=Q;i++){ if(ans[i]) cout << "yes\n"; else cout << "no\n"; } }