BZOJ1924: [Sdoi2010]所驼门王的宝藏【优化连边】

题目描述:

在这里插入图片描述
n<=100000,R、C<=1000000

题目分析:

如果暴力连边,之后就是tarjan缩点拓扑排序求最长路。
但是暴力连边是 O ( n 2 ) O(n^2) O(n2)的,考虑优化。
考虑同一行的横天门所能,它们可以相互到达,所以只需要让它们连成一个环就可以了,然后让第一个横天门向这一行的非横天门连边,这样是题意的等价表达,此行的任意横天门可以到达所有非横天门,并且这样的连边数是 O ( n ) O(n) O(n)的。
纵寰门同理,自由门用map存点,连接8个方向即可。

缩点之后似乎不用拓扑排序,直接按照强联通分量的编号由大到小计算就可以了。
Code:

#include<bits/stdc++.h>
#define maxn 100005
#define line(x,y) G[x].push_back(y)
using namespace std;
typedef pair<int,int> pii;
int n,R,C,t[maxn],X[maxn],Y[maxn],tx[maxn],ty[maxn],dis[maxn],deg[maxn],siz[maxn];
int dfn[maxn],low[maxn],tim,stk[maxn],top,scc[maxn],scnt;
vector<int>r[maxn][2],c[maxn][2],G[maxn],E[maxn];
map<pii,int>graph;
int dx[8]={1,1,1,-1,-1,-1,0,0},dy[8]={1,0,-1,1,0,-1,1,-1};
void tarjan(int u){
    dfn[u]=low[u]=++tim,stk[++top]=u;
    for(int i=G[u].size()-1,v;i>=0;i--)
        if(!dfn[v=G[u][i]]) tarjan(v),low[u]=min(low[u],low[v]);
        else if(!scc[v]) low[u]=min(low[u],dfn[v]);
    if(dfn[u]==low[u]){
        ++scnt;
        do siz[scc[stk[top]]=scnt]++; while(stk[top--]!=u);
    }
}
int main()
{
    scanf("%d%d%d",&n,&R,&C);
    for(int i=1;i<=n;i++) scanf("%d%d%d",&X[i],&Y[i],&t[i]),tx[i]=X[i],ty[i]=Y[i];
    sort(tx+1,tx+1+n),sort(ty+1,ty+1+n);
    tx[0]=unique(tx+1,tx+1+n)-tx-1, ty[0]=unique(ty+1,ty+1+n)-ty-1;
    for(int i=1;i<=n;i++){
        graph[pii(X[i],Y[i])]=i;
        X[i]=lower_bound(tx+1,tx+1+tx[0],X[i])-tx;
        Y[i]=lower_bound(ty+1,ty+1+ty[0],Y[i])-ty;
        r[X[i]][t[i]==1].push_back(i);
        c[Y[i]][t[i]==2].push_back(i);
    }
    for(int i=1;i<=tx[0];i++) if(r[i][1].size()){
        for(int j=r[i][1].size()-1;j;j--) line(r[i][1][j],r[i][1][j-1]);
        if(r[i][1].size()>1) line(r[i][1][0],r[i][1].back());
        for(int j=r[i][0].size()-1;j>=0;j--) line(r[i][1][0],r[i][0][j]);
    }
    for(int i=1;i<=ty[0];i++) if(c[i][1].size()){
        for(int j=c[i][1].size()-1;j;j--) line(c[i][1][j],c[i][1][j-1]);
        if(c[i][1].size()>1) line(c[i][1][0],c[i][1].back());
        for(int j=c[i][0].size()-1;j>=0;j--) line(c[i][1][0],c[i][0][j]);
    }
    for(int i=1,z;i<=n;i++) if(t[i]==3)
        for(int j=0;j<8;j++) if(z=graph[pii(tx[X[i]]+dx[j],ty[Y[i]]+dy[j])]) line(i,z);
    for(int i=1;i<=n;i++) if(!dfn[i]) tarjan(i);
    for(int i=1,x,y;i<=n;i++) 
        for(int j=G[i].size()-1;j>=0;j--) if((x=scc[i])!=(y=scc[G[i][j]]))
            E[x].push_back(y),deg[y]++;
    for(int i=1;i<=scnt;i++) if(!deg[i]) stk[++top]=i,dis[i]=siz[i];
    while(top){
        int u=stk[top--];
        for(int i=E[u].size()-1,v;i>=0;i--){
            v=E[u][i],dis[v]=max(dis[v],dis[u]+siz[v]);
            if(!--deg[v]) stk[++top]=v;
        }
    }
    printf("%d\n",*max_element(dis+1,dis+1+scnt));
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值