【JSOI2018】防御网络

题面

https://www.luogu.org/problemnew/show/P4517

题解

这道题对我来说意义重大,是我花了很长时间独立完成的dp。

分析题目

给出一个仙人掌,对于一个点集$S$,把原图中使他联通的最小的边集记做$H(S)$,

求$sum_{S subseteq V} |H(S)|$

从考虑边的贡献的思路开始。

对于非环边,假设它把$V$分成了$S$和$T$两个区域,如果它被使用,当且仅当其中存在两个点$a,b$ 使得 $a in S b in T$,统计$S$和$T$集合中元素的个数即可。

对于环边,我们把它放到环中考虑,环上如果有边,则边一定是连续的(若不连续,子图不能联通)

那么对于一个环点的子集,这个连续的边的区间该怎么选?

找到其中最大的空缺,把环的长度去掉空缺的长度,即为答案区间。

所以贡献与最大的空缺有关。

设$f[x][y]$为从起点到$x$点,起点一定选,并且$x$点一定选,最大的空缺长度为$y$ 的集合个数是多少。

显然可以得到 $F[x][y] = (sum_{dis(i,x) le y} F[i][y] + sum_{j<y} F[x-y][j]) imes v[x]$

观察到转移可以写成一个前缀和的形式。

所以我们就可以把$F[x][y]$都算出来了。

考虑怎么统计答案。

设$ans$为所有的$|H(S)|$之和。

本来我的想法是 当$F[x][y]$满足$dis(start,x) le cirlen-y$ 把$F[x][y]$按长度为权加到$ans$里 把环上的每个点作为起点做一次$dp$

发现这样是假的,因为当取到等号时,一个集合可能被算多次。

所有我们考虑怎么统计。

其实解决方法也很简单,当我却想了很久。

今天$gyfan$给我们做的题中,一个很简单的操作我竟然不会实现,写的代码巨烦无比,并且逻辑错误,还说题目出错了,现在看来,是太浮躁了吧,如果让$AYSN$写的话,他肯定很快能想到。

不要把$F[x][y]$看成有向的,直接把他看成集合的集合。

对于每个起点$dp$到一样的终点即可。

这样一定是不重不漏的。

不重:对于起点$x$和$y$的$F$,他们代表的集合中的起点一定是$x$和$y$,如果$x ot=y$,则集合的每个元素的起点都和另一个元素的起点不同,自然不会重复,对于起点相同的$F[x]$和$F[y]$来说,他们的终点不同。综上,对于一个元素,一定不会被重复分到两个不同的集合中。

不漏:对于一个长度(这里的长度指点到点的有向长度)大于$1$(长度为$1$时,对答案无贡献,不考虑),一定有最左边的点和最右边的点,所以一定会出现在$start=s$的$F[t]$中,所以一定不漏。

然后就可以统计答案了。

  • 前缀和优化$dp$时,宽不能少算,即使$F[i][j]=0$也要算$sum[i][j]$,并且$sum[0][i]$和$sum[i][0]$也要算。这个地方已经被坑了两次了。
  • $tarjan(x)$时,注意判树边时,记得$s.pop()$ (可以理解成,脱下裤子后,发现...,再重新把裤子传上)
  • $tarjan(x)$时,对于$pop$出的$t$,暴力找环边,复杂度是真的,因为均摊。
  • $tarjan(x)$时,勿忘$low[x]=min(low[x],low[y])$,$low[x]=min(dfn[y],low[x])$不容易忘。。。。
  • $tarjan(x)$时,其他细节见上一题。
  • 注意取模的减法,要加上$mod$,防止减了之后小于零(虽然这种可能性很小)
#include<cstdio>
#include<cstring>
#include<iostream>
#include<stack>
#include<vector>
#define INF 1000000007
#define ri register int
#define LL long long
#define N 205
#define M 500
using namespace std;

LL ans=0;
int n,m,tot=0,a[M],b[M],vis[N],cir[M],pp[M];
vector<int> c[N];
int v[N],myn;

inline int read() {
  int ret=0,f=0; char ch=getchar();
  while (ch<'0' || ch>'9') f|=(ch=='-'),ch=getchar();
  while (ch>='0'&&ch<='9') ret*=10,ret+=ch-'0',ch=getchar();
  return f?-ret:ret;
}

stack<int> s;
struct graph {
  vector<int> to[N],id[N];
  int dfn[N],low[N],cnt;
  void add_edge(int a,int b,int c) {
    to[a].push_back(b); id[a].push_back(c);
    to[b].push_back(a); id[b].push_back(c);
  }
  void tarjan(int x) {
    dfn[x]=low[x]=++cnt; 
    s.push(x);
    for (ri i=0,l=to[x].size();i<l;i++) {
      int y=to[x][i];
      if (dfn[y]) {
        low[x]=min(low[x],dfn[y]);
      }
      else {
        tarjan(y);
        low[x]=min(low[x],low[y]);
        if (low[y]>=dfn[x]) {
          if (s.top()==y) {
            s.pop(); continue;
          }
          ++tot; int t,pret=x;
          do {
            t=s.top(); c[tot].push_back(t); s.pop();
            for (ri j=0,l2=id[t].size();j<l2;j++) if (to[t][j]==pret) {cir[id[t][j]]=tot;break;}
            pret=t;
          }
          while (t!=y);
          c[tot].push_back(x); cir[id[x][i]]=tot;
        }
      }
    }
  }

void tonji(int x,int ff1,int ff2,int &cnt) { vis[x]=1; ++cnt; for (ri i=0,l=to[x].size();i<l;i++) { int y=to[x][i]; if (y==ff1 || vis[y] || y==ff2) continue; tonji(y,ff1,ff2,cnt); } } } G; LL f[N][N],sumh[N][N],sums[N][N]; LL sh(int x,int y) { if (x>=0) return sumh[x][y]; else return 0; } void dp(int lent,int cur,int start) { memset(f,0,sizeof(f)); memset(sumh,0,sizeof(sumh)); memset(sums,0,sizeof(sums)); sumh[start][0]=sums[start][0]=f[start][0]=(pp[v[c[cur][start]]]-1+INF)%INF; for (ri i=1;i<=lent;i++) sumh[start+i][0]=sumh[start+i-1][0]; for (ri i=1;i<=lent;i++) sums[start][i]=sums[start][i-1]; for (ri l=1;start+l<lent;l++) { for (ri k=1;k<=lent;k++) { f[start+l][k]=((sumh[start+l-1][k]-sh(start+l-k-1,k)+INF+sums[start+l-k][k-1])%INF*1LL*(pp[v[c[cur][(start+l)%lent]]]-1+INF)%INF)%INF; sumh[start+l][k]=(sumh[start+l-1][k]+f[start+l][k])%INF; sums[start+l][k]=(sums[start+l][k-1]+f[start+l][k])%INF; int ml=max(k,lent-l); ans+=(lent-ml)*1LL*f[start+l][k],ans%=INF; } } } LL pow(LL a,int n) { LL ret=1; for (;n;n>>=1,a=(a*a)%INF) if (n&1) ret=(ret*a)%INF; return ret; } int main() { pp[0]=1; for (ri i=1;i<N;i++) pp[i]=(pp[i-1]+pp[i-1])%INF; scanf("%d %d",&n,&m);
for (ri i=1;i<=m;i++) { int u,v; scanf("%d %d",&u,&v); a[i]=u; b[i]=v; G.add_edge(u,v,i); } G.cnt=0; G.tarjan(1); for (ri i=1;i<=m;i++) if (cir[i]==0) { memset(vis,0,sizeof(vis)); int t1=0; G.tonji(a[i],b[i],-1,t1); memset(vis,0,sizeof(vis)); int t2=0; G.tonji(b[i],a[i],-1,t2); ans+=(pp[t1]-1+INF)*1LL*(pp[t2]-1+INF); ans%=INF; } for (ri i=1;i<=tot;i++) { myn=c[i].size(); for (ri j=0;j<myn;j++) c[i].push_back(c[i][j]); for (ri j=1;j<=myn;j++) { memset(vis,0,sizeof(vis)); v[c[i][j]]=0; G.tonji(c[i][j],c[i][j-1],c[i][j+1],v[c[i][j]]); } for (ri j=0;j<myn;j++) dp(myn,i,j); } printf("%lld",ans*1LL*pow(pp[n],INF-2)%INF); return 0; }
原文地址:https://www.cnblogs.com/shxnb666/p/11089255.html