【题解】CF1534F2 Falling Sand (Hard Version)

Easy Version

考虑对于 (i,j) 向上下左右连出的 有向边 。缩点后答案就是 in[i]=0 的个数。

Hard Version

由于不需要全部消除,所以贪心地全部消完是不可行的。

trick1: 对于这个图,我们只需要让第 i 行第 a[i] 块掉下即可,我们称其为特殊点;

trick2: 观察到图的特征是仅向相邻的列连边,所以猜测选取某一个点消除的影响是 [l,r]

观察:

3 6
..#..#
......
#####.
1 1 2 1 1 1

如果特殊点 X 可以到 Y 的话,我们将 Y 删掉,因为只需要得到 X 即可。这样将剩下的点按从左到右的顺序重新编号,可以证明选取图中某一节点,会覆盖 新编号节点中的一段连续区间 。这样在缩点后的 DAGDP ,只需要记录最左和最右节点,做一次区间覆盖即可。

证明:假设能得到特殊点 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]);
//			cout<<l[x]<<" "<<r[x]<<endl;
		}
	}
	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;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值