P2805

前置知识:\(\text{tarjan}\) 缩点或拓扑排序判环

至少要有一种方法判环。

前置知识:最大权闭合子图

可以看第一篇题解的讲述或者先写一下模板题

题意:

P2805 [NOI2009] 植物大战僵尸

在大小为 \(N\times M\) 的网格图上,每个点都有一株植物。
每株植物都有一个能源值,以及其防御位置集合,能源不保证非负。
可以从地图右侧开始攻击,不能经过未被消灭的植物的防御集合。
求问攻击最大收益。

\(1\le N\le 20,1\le M\le 30,\left|\text{Score}\right| \le 4000\)

题解:

最大权闭合子图的最小割建图可以引申到此题上。
设点 \(i\) 权值为 \(v_i\),源点和汇点 \(s,t\)

  • 对于每个点 \(u\)
    • \(v_u>0\),从源点向 \(u\) 连一条容量为 \(s_u\) 的边。\(s \xrightarrow{v_u} u\)
    • \(v_u<0\),从 \(u\) 向汇点连一条容量为 \(s_u\) 的边。\(u \xrightarrow{v_u} t\)
  • 对于图中原有的边 \(u \to v\),两点之间连一条容量无限大的边。\(u \xrightarrow{\text{inf}} v\)

由此题的条件:攻击一株植物要先消灭其右边和保护此植物的所有植物,可以发现这和最大闭合子图类似。
所以,先将所有植物按其能源值正负分别向源点或汇点连容量为能源值绝对值的边。
再将每株植物向保护其的所有植物连容量无限大的边,跑最大权闭合子图即可。
最后的答案就是所有正能源值之和减去最大流

但是可能会出现植物无敌的情况,比如两株植物互相保护。
所以要先在建出的图的反图上跑拓扑排序,或是 \(\text{tarjan}\) 找环后从环上点出发跑 \(\text{BFS}\),找到无敌的点。
在网络流建图时,除去无敌的点,而且正能源值之和中也不包括无敌点的能源值。

代码:

#include <bits/stdc++.h>
#define id(x,y) ((x-1)*m+y)
#define V e[i].v
using namespace std;
int rd(){
  int w=0,v=1;
  char c=getchar();
  while(c<'0'||c>'9'){
    if(c=='-')
      v=-1;
    c=getchar();
  }while(c>='0'&&c<='9'){
    w=(w<<1)+(w<<3)+(c&15);
    c=getchar();
  }return w*v;
}const int N=2e5,INF=2e9;
int n,m,st,ed,ans;
int a[1001][1001];
struct E{int v,w,nt;}e[N];
int fir[N],c=1;
void I(int u,int v,int w){
  e[++c]=(E){v,w,fir[u]};
  fir[u]=c;
  e[++c]=(E){u,0,fir[v]};
  fir[v]=c;
}int cur[N],d[N];
queue <int>q;
vector <int>g[N];
bool bfs(){
  for(int i=0;i<=ed;i++){
    cur[i]=fir[i];
    d[i]=0;
  }q.push(st);
  d[st]=1;
  while(!q.empty()){
    int u=q.front();
    q.pop();
    for(int i=fir[u];i;i=e[i].nt)
      if(e[i].w&&!d[V]){
        q.push(V);
        d[V]=d[u]+1;
      }
  }return d[ed];
}int dfs(int u,int fl){
  if(u==ed) 
    return fl;
  int f,s=0;
  for(int i=cur[u];i;i=e[i].nt){
    cur[u]=i;
    if(e[i].w&&d[V]==d[u]+1){
      f=dfs(V,min(e[i].w,fl));
      e[i].w-=f;
      e[i^1].w+=f;
      fl-=f;s+=f;
      if(!fl) break;
    }
  }if(!s) d[u]=0;
  return s;
}int dinic(){
  int ans=0;
  while(bfs())
    ans+=dfs(st,INF);
  return ans;
}int dfn[N],low[N];
int tim,xs,si[N];
bool in[N],tag[N];
int sta[N],tp,now;
void tarjan(int u){
  dfn[u]=low[u]=++tim;
  in[sta[++tp]=u]=1;
  for(auto v:g[u])
    if(!dfn[v]){
      tarjan(v);
      low[u]=min(low[u],low[v]);
    }else if(in[v])
      low[u]=min(low[u],dfn[v]);
  if(dfn[u]==low[u]){
    xs++;now=tp;
    while(sta[tp+1]!=u){
      in[sta[tp--]]=0;
      si[xs]++;
    }if(si[xs]<=1) return ;
    for(int i=tp+1;i<=now;i++){
      tag[sta[i]]=1;
      q.push(sta[i]);
    }
  }
}int main(){
  freopen("test.in","r",stdin);
  n=rd(),m=rd();
  ed=n*m*2;
  for(int i=1;i<=n;i++)
    for(int j=2;j<=m;j++)
      g[id(i,j)].push_back(id(i,j-1));
  for(int i=1;i<=n;i++)
    for(int j=1,k,x,y;j<=m;j++){
      a[i][j]=rd();k=rd();
      while(k--){
        x=rd()+1,y=rd()+1;
        g[id(i,j)].push_back(id(x,y));
      }
    }
  for(int i=1;i<=n;i++)
    for(int j=1;j<=m;j++)
      if(!dfn[id(i,j)])
        tarjan(id(i,j));
  while(!q.empty()){
    int u=q.front();
    q.pop();
    for(auto i:g[u]){
      if(!tag[i]){
        tag[i]=1;
        q.push(i);
      }
    }
  }for(int i=1;i<=n;i++)
    for(int j=1;j<=m;j++){
      if(tag[id(i,j)])
        continue;
      if(a[i][j]>0)
        I(st,id(i,j),a[i][j]),ans+=a[i][j];
      if(a[i][j]<0)
        I(id(i,j),ed,-a[i][j]);
    }
  for(int i=1;i<=n;i++)
    for(int j=1;j<=m;j++)
      if(!tag[id(i,j)])
        for(int k:g[id(i,j)])
          if(!tag[k])
            I(k,id(i,j),INF);
  cout<<ans-dinic();
  return 0;
}
posted @ 2022-07-20 07:33  AIskeleton  阅读(11)  评论(0编辑  收藏  举报