Easy Version
考虑对于 (i,j)
向上下左右连出的 有向边
。缩点后答案就是 in[i]=0
的个数。
Hard Version
由于不需要全部消除,所以贪心地全部消完是不可行的。
trick1:
对于这个图,我们只需要让第 i
行第 a[i]
块掉下即可,我们称其为特殊点;
trick2:
观察到图的特征是仅向相邻的列连边,所以猜测选取某一个点消除的影响是 [l,r]
。
观察:
如果特殊点 X
可以到 Y
的话,我们将 Y
删掉,因为只需要得到 X
即可。这样将剩下的点按从左到右的顺序重新编号,可以证明选取图中某一节点,会覆盖 新编号节点中的一段连续区间
。这样在缩点后的 DAG
上 DP
,只需要记录最左和最右节点,做一次区间覆盖即可。
证明:假设能得到特殊点 i<j<k
,其中 j
不可达,那么一定和 j
有交点。如果是在下方,则从特殊点 j
能到达特殊点 i,k
,要么是缩点后在同一节点上,不难证明缩点后的节点代表的特殊点也是一段连续区间;否则与上文矛盾。如果是在上方,那么可以到达特殊点 j
,证毕。
#include<bits/stdc++.h>
#define fi first
#define se second
#define ll long long
#define PII pair<int,int>
#define All(x) x.begin(),x.end()
#define INF 0x3f3f3f3f
using namespace std;
const int mx=4e5+5;
int n,m,cnt,low[mx],dfn[mx],num,bl,c[mx],vis[mx],in[mx],a[mx],b[mx],l[mx],r[mx],siz,marked[mx],idx;
PII d[mx];
queue<int> q;
stack<int> st;
set<PII> s[mx];
vector<PII> s2[mx];
vector<PII> v,e;
vector<int> sp;
vector<int> g[mx],g2[mx];
void tarjan(int x) {
low[x]=dfn[x]=++num; vis[x]=1,st.push(x);
for(auto y:g[x]) {
if(!dfn[y]) {
tarjan(y),low[x]=min(low[x],low[y]);
}
else if(vis[y]){
low[x]=min(low[x],dfn[y]);
}
}
if(dfn[x]==low[x]) {
int tmp=0; bl++;
do{
tmp=st.top(); st.pop();
c[tmp]=bl,vis[tmp]=0;
}while(tmp!=x);
}
}
void dfs(int x) {
if(marked[x]) return; marked[x]=1;
for(auto y:g2[x]) {
dfs(y);
}
}
int main() {
cin>>n>>m; memset(l,INF,sizeof(l));
for(int i=1;i<=n;i++) {
for(int j=1;j<=m;j++) {
char c; cin>>c;
if(c=='#') {
s2[j].push_back(make_pair(i,cnt));
s[j].insert(make_pair(i,cnt++));
v.push_back(make_pair(i,j));
}
}
}
for(int i=1;i<=m;i++) {
scanf("%d",&a[i]);
}
for(int i=1;i<=m;i++) {
if(!a[i]) continue;
b[i]=s2[i][s2[i].size()-a[i]].second;
sp.push_back(b[i]);
}
for(int i=0;i<cnt;i++) {
int x=v[i].fi,y=v[i].se;
auto it=s[y].upper_bound(make_pair(x,INF));
if(it!=s[y].end()) {
e.push_back(make_pair(i,it->second));
g[i].push_back(it->second);
}
it=s[y-1].lower_bound(make_pair(x,0));
if(it!=s[y-1].end()) {
e.push_back(make_pair(i,it->second));
g[i].push_back(it->second);
}
it=s[y+1].lower_bound(make_pair(x,0));
if(it!=s[y+1].end()) {
e.push_back(make_pair(i,it->second));
g[i].push_back(it->second);
}
it=s[y].lower_bound(make_pair(x,0));
if(it--!=s[y].begin() && it->first+1==x) {
e.push_back(make_pair(i,it->second));
g[i].push_back(it->second);
}
}
for(int i=0;i<cnt;i++) {
if(!dfn[i]) tarjan(i);
}
for(int i=0;i<e.size();i++) {
int x=e[i].fi,y=e[i].se;
x=c[x],y=c[y];
if(x!=y) {
g2[x].push_back(y);
}
}
for(int i=0;i<sp.size();i++) {
int x=sp[i]; x=c[x];
for(auto y:g2[x]) {
dfs(y);
}
}
for(int i=1;i<=bl;i++) g2[i].clear();
for(int i=0;i<sp.size();i++) {
int x=sp[i]; x=c[x];
if(marked[x]) continue;
idx++;
l[x]=min(l[x],idx);
r[x]=max(r[x],idx);
}
for(int i=0;i<e.size();i++) {
int x=e[i].fi,y=e[i].se;
x=c[x],y=c[y];
if(x!=y) {
g2[y].push_back(x);
in[x]++;
}
}
for(int i=1;i<=bl;i++) {
if(!in[i]) {
q.push(i);
}
}
while(q.size()) {
int x=q.front(); q.pop();
for(auto y:g2[x]) {
l[y]=min(l[y],l[x]);
r[y]=max(r[y],r[x]);
if(--in[y] == 0) q.push(y);
}
if(!g2[x].size() && l[x] <= r[x]) {
d[++siz]=make_pair(l[x],r[x]);
}
}
sort(d+1,d+1+siz);
int res(0),Max(1),p(1);
while(Max<=idx) {
int R(0);
while(p<=siz&&d[p].fi<=Max) R=max(R,d[p].se),p++;
res++,Max=R+1;
}
cout<<res;
}
__EOF__
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· DeepSeek 开源周回顾「GitHub 热点速览」