BZOJ1565【NOI2009】植物大战僵尸题解(tarjan+最大权闭合子图)

题目:BZOJ1565.
题目大意:给定一张 n ∗ m n*m nm的网格,坐标从 ( 0 , 0 ) (0,0) (0,0) ( n − 1 , m − 1 ) (n-1,m-1) (n1,m1),规定若一个网格 ( x , y ) (x,y) (x,y)要取,则 ( x , y + 1 ) , ( x , y + 2 ) , . . . ( x , m − 1 ) (x,y+1),(x,y+2),...(x,m-1) (x,y+1),(x,y+2),...(x,m1)都要取.现在给定每个网格 ( i , j ) (i,j) (i,j)的价值 v ( i , j ) v(i,j) v(i,j),以及每个网格 ( i , j ) (i,j) (i,j)是哪些网格 ( i ′ , j ′ ) (i',j') (i,j)的必选项(即要选择 ( i ′ , j ′ ) (i',j') (i,j),必须先选择 ( i , j ) (i,j) (i,j)),求可以获得的最大价值和.
1 ≤ n ≤ 20 , 1 ≤ m ≤ 30 , − 1 0 4 ≤ v ( i , j ) ≤ 1 0 4 1\leq n\leq 20,1\leq m\leq 30,-10^4\leq v(i,j)\leq 10^4 1n20,1m30,104v(i,j)104.

看到条件比较复杂而且数据范围比较奇怪的题,容易想到网络流来做.

看到条件是选了某个点就必须要选某些点的题,容易想到是最大权闭合子图.

然而不幸的是,这道题的限制条件可能形成一个环,而用最大权闭合子图做的题需要原图是个DAG.

考虑环怎么处理,显然一个环上的任意一个点都是不可能取的,而任意一个直接或间接以环作为前置条件的点也是不可取的,所以想到了SCC缩点,把所以包含点数大于 2 2 2的SCC以及所有能够通向它们的点都去掉,剩下的图就是一个DAG了.

之后跑个最大权闭合子图就好了.

代码如下:

#include<bits/stdc++.h>
using namespace std;
 
#define Abigail inline void
typedef long long LL;
 
const int N=600,M=N*(N+2),INF=(1<<30)-1;
 
int n,m,st,td,v[N+9],sum;
 
int H(int x,int y){return (x-1)*m+y;}
 
struct side{
  int y,next,f;
}e[M*4+9];
int lin[3][N+9],cs;
 
void Ins(int id,int x,int y,int v){e[++cs].y=y;e[cs].f=v;e[cs].next=lin[id][x];lin[id][x]=cs;}
 
int dfn[N+9],low[N+9],vis[N+9],co;
stack<int>sta;
int bel[N+9],cnt,cn[N+9];
vector<int>scc[N+9];
 
void Tarjan(int k){
  dfn[k]=low[k]=++co;
  vis[k]=1;sta.push(k);
  for (int i=lin[0][k];i;i=e[i].next)
    if (!dfn[e[i].y]) Tarjan(e[i].y),low[k]=min(low[k],low[e[i].y]);
    else if (vis[e[i].y]) low[k]=min(low[k],low[e[i].y]);
  if (dfn[k]^low[k]) return;
  ++cnt;
  while (vis[k]){
    int t=sta.top();sta.pop();
    vis[t]=0;
    scc[bel[t]=cnt].push_back(t);
    ++cn[cnt];
  }
}
 
int b[N+9];
 
void Contract(){
  for (int i=1;i<=n;++i)
    if (!dfn[i]) Tarjan(i);
  for (int i=1;i<=n;++i)
    for (int j=lin[0][i];j;j=e[j].next)
      if (bel[i]^bel[e[j].y]) Ins(1,bel[i],bel[e[j].y],0);
  for (int i=1;i<=cnt;++i)
    if (cn[i]>1) b[i]=1;
}
 
void Dfs_b(int k){
  if (vis[k]) return;
  vis[k]=1;
  for (int i=lin[1][k];i;i=e[i].next){
    Dfs_b(e[i].y);
    b[k]|=b[e[i].y];
  }
}
 
int dis[N+9],gap[N+9];
queue<int>q;
 
void Bfs_div(int st,int td){
  gap[dis[td]=1]=1;q.push(td);
  while (!q.empty()){
    int t=q.front();q.pop();
    for (int i=lin[2][t];i;i=e[i].next)
      if (!dis[e[i].y]){
        ++gap[dis[e[i].y]=dis[t]+1];
        q.push(e[i].y);
      }
  }
}
 
int cur[N+9];
 
int Dfs_path(int k,int st,int td,int flow){
  if (k==td) return flow;
  int res=0;
  for (int &i=cur[k];i;i=e[i].next)
    if (dis[k]==dis[e[i].y]+1){
      int t=Dfs_path(e[i].y,st,td,min(flow,e[i].f));
      flow-=t;res+=t;
      e[i].f-=t;e[i^1].f+=t;
      if (!flow) return res;
    }
  if (!(--gap[dis[k]])) dis[st]=n+1;
  ++gap[++dis[k]];
  cur[k]=lin[2][k];
  return res;
}
 
int Max_flow(int st,int td){
  int res=0;
  Bfs_div(st,td);
  for (int i=1;i<=n;++i) cur[i]=lin[2][i];
  for (;dis[st]<=n;res+=Dfs_path(st,st,td,INF));
  return res;
}
 
Abigail into(){
  scanf("%d%d",&n,&m);
  cs=1;
  for (int i=1;i<=n;++i)
    for (int j=1;j<=m;++j){
      for (int k=j+1;k<=m;++k) Ins(0,H(i,j),H(i,k),0);
      int sk,x,y;
      scanf("%d%d",&v[H(i,j)],&sk);
      for (int k=1;k<=sk;++k){
        scanf("%d%d",&x,&y);++x;++y;
        Ins(0,H(x,y),H(i,j),0);
      }
    }
  n*=m;
}
 
Abigail work(){
  Contract();
  for (int i=1;i<=cnt;++i){
    Dfs_b(i);
    if (!b[i]) continue;
    for (int j=0;j<cn[cnt];++j) v[scc[i][j]]=0;
  }
  cs|=1;      //注意这里一定要按位或上1,不然网络流中找反向边的异或1就炸了...
  for (int i=1;i<=n;++i)
    for (int j=lin[0][i];j;j=e[j].next)
      Ins(2,i,e[j].y,INF),Ins(2,e[j].y,i,0);
  st=n+1;td=n+2;
  for (int i=1;i<=n;++i)
    if (v[i]>=0) sum+=v[i],Ins(2,st,i,v[i]),Ins(2,i,st,0);
    else Ins(2,i,td,-v[i]),Ins(2,td,i,0);
  n+=2;
}
 
Abigail outo(){
  printf("%d\n",sum-Max_flow(st,td));
}
 
int main(){
  into();
  work();
  outo();
  return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值