LOJ 2587 「APIO2018」铁人两项——圆方树

题目:https://loj.ac/problem/2587

先写了 47 分暴力。

对于 n<=50 的部分, n3 枚举三个点,把图的圆方树建出来,合法条件是 c 是 s -> f 路径上的方点连出去的某个圆点。像找 LCA 那样走一遍 s -> f 路径即可。

对于树的部分,考虑一条路径对答案的贡献是其边数减 1 ,所以对于每条边求一下它在多少路径中,就是 siz[ v ] * ( n-siz[ v ] ) ( v 是它指向的点),然后答案再减去 \( C_n^2 \) 即可。

  注意答案还要乘 2 ,因为一条路径的贡献其实是两倍的 (边数 - 1),因为 s 和 f 位置可以互换。

对于每个点度数最多是 2 的部分,是一些链和环。链就枚举路径的长度,可以算出有多少该长度路径以及贡献;环就考虑固定 s 的位置,对答案的贡献是一个等差数列,算一番即可。

以为子任务 6 是基环树。写了个 n2 的。然后发现不是基环树而是仙人掌。(并且忘记考虑环的另一方向组成的路径了。懒得改了。)

一定要好好判断什么情况是树的部分。因为是森林,所以不能写 if( m == n-1 ) 。

#include<cstdio>
#include<cstring>
#include<algorithm>
#define ll long long
using namespace std;
int rdn()
{
  int ret=0;bool fx=1;char ch=getchar();
  while(ch>'9'||ch<'0'){if(ch=='-')fx=0;ch=getchar();}
  while(ch>='0'&&ch<='9')ret=ret*10+ch-'0',ch=getchar();
  return fx?ret:-ret;
}
int Mn(int a,int b){return a<b?a:b;}
int Mx(int a,int b){return a>b?a:b;}
const int N=1e5+5,M=4e5+5;
int n,m,hd[N],xnt,to[M],nxt[M],rd[N];
void add(int x,int y){to[++xnt]=y;nxt[xnt]=hd[x];hd[x]=xnt;rd[y]++;}
namespace S1{
  int siz[N],tot;ll ans;
  bool vis[N];
  void ini_dfs(int cr,int fa)
  {
    siz[cr]=1; vis[cr]=1;
    for(int i=hd[cr],v;i;i=nxt[i])
      if((v=to[i])!=fa)
    ini_dfs(v,cr), siz[cr]+=siz[v];
  }
  void dfs(int cr,int fa)
  {
    for(int i=hd[cr],v;i;i=nxt[i])
      if((v=to[i])!=fa)
    {
      dfs(v,cr);
      ans+=(ll)siz[v]*(tot-siz[v]);
    }
  }
  void solve()
  {
    for(int i=1;i<=n;i++)
      if(!vis[i])
    {
      tot=0; ini_dfs(i,0); tot=siz[i];
      dfs(i,0); ans-=(ll)tot*(tot-1)/2;
    }
    printf("%lld\n",ans*2);
  }
}
namespace S2{
  bool vis[N],flag; int cnt;
  void dfs(int cr,int fa)
  {
    vis[cr]=1; cnt++;
    for(int i=hd[cr],v;i;i=nxt[i])
      if((v=to[i])!=fa)
    {
      if(vis[v]){flag=1;return;}
      dfs(v,cr);
    }
  }
  void solve()
  {
    ll ans=0;
    for(int i=1;i<=n;i++)
      if(!vis[i])
    {
      flag=0;cnt=0;dfs(i,0);
      if(cnt<=2)continue;
      if(!flag)
        {
          for(int j=2;j<cnt;j++)
        {
          int ct=cnt-j;
          ans+=(ll)ct*(j-1)*2;//*2
        }
        }
      else ans+=(ll)(cnt-2)*(cnt-1)*cnt;
    }
    printf("%lld\n",ans);
  }
}
namespace S3{
  const int N2=N<<1;
  int h2[N2],t2[M],nt2[M],dep[N2],cnt,col[N2],pre[N2];
  int dfn[N],low[N],tim,sta[N],top,tot;
  bool ins[N],vis[N2];
  void add(int x,int y)
  {
    t2[++xnt]=y;nt2[xnt]=h2[x];h2[x]=xnt;
  }
  void tarjan(int cr,int fa)
  {
    dfn[cr]=low[cr]=++tim;
    sta[++top]=cr; ins[cr]=1;
    for(int i=hd[cr],v;i;i=nxt[i])
      if((v=to[i])!=fa)
    {
      if(ins[v])low[cr]=Mn(low[cr],dfn[v]);
      else if(!dfn[v])
        {
          tarjan(v,cr);low[cr]=Mn(low[cr],low[v]);
          if(low[v]>=dfn[cr])
        {
          tot++; add(tot,cr); add(cr,tot);
          do{
            int tp=sta[top]; ins[tp]=0;
            add(tot,tp); add(tp,tot);
          }while(sta[top--]!=v);
        }
        }
    }
  }
  void dfs(int cr,int fa)
  {
    col[cr]=cnt;dep[cr]=dep[fa]+1;pre[cr]=fa;
    for(int i=h2[cr],v;i;i=nt2[i])
      if((v=t2[i])!=fa) dfs(v,cr);
  }
  bool chk(int s,int t,int c)
  {
    int x=s, y=t; if(dep[x]<dep[y])swap(x,y);
    while(dep[x]!=dep[y])
      {
    x=pre[x];if(x<=n)continue;
    for(int i=h2[x];i;i=nt2[i])
      if(t2[i]==c)return true;
      }
    while(x!=y)
      {
    x=pre[x]; y=pre[y];
    if(x>n)
      {
        for(int i=h2[x];i;i=nt2[i])
          if(t2[i]==c)return true;
      }
    if(y>n&&y!=x)
      {
        for(int i=h2[y];i;i=nt2[i])
          if(t2[i]==c)return true;
      }
      }
    return false;
  }
  void solve()
  {
    tot=n; xnt=0;
    for(int i=1;i<=n;i++)
      if(!dfn[i])top=0,tarjan(i,0);
    for(int i=1;i<=tot;i++)
      if(!col[i])cnt++,dfs(i,0);
    int ans=0;
    for(int s=1;s<=n;s++)
      for(int c=1;c<=n;c++)
    if(s!=c&&col[s]==col[c])
      for(int t=1;t<=n;t++)
        {
          if(t==s||t==c||col[t]!=col[s])continue;
          if(chk(s,t,c)) ans++;
        }
    printf("%d\n",ans);
  }
}
namespace S4{
  const int N=1005;
  int tim,dfn[N],low[N],sta[N],top;
  bool vis[N],ins[N];
  int a[N],tot,dep[N]; ll ans,dp[N][N];
  void tarjan(int cr,int fa)
  {
    dfn[cr]=low[cr]=++tim;
    sta[++top]=cr; ins[cr]=1;
    for(int i=hd[cr],v;i;i=nxt[i])
      if((v=to[i])!=fa)
    {
      if(ins[v=to[i]])low[cr]=Mn(low[cr],dfn[v]);
      else if(!dfn[v])tarjan(v,cr),low[cr]=Mn(low[cr],low[v]);
    }
    if(dfn[cr]==low[cr])
      {
    if(sta[top]==cr){ins[cr]=0;top--;return;}
    do{
      int tp=sta[top]; a[++tot]=tp; vis[tp]=1; ins[tp]=0;
    }while(sta[top--]!=cr);
      }
  }
  void dfs(int cr,int fa)
  {
    dp[cr][0]=1;
    for(int i=hd[cr],v;i;i=nxt[i])
      if(!vis[v=to[i]]&&v!=fa)
    {
      dfs(v,cr);dep[cr]=Mx(dep[cr],dep[v]+1);
      for(int j=1;j<=dep[cr];j++)
        for(int k=0;k<=dep[v];k++)
          {
        ll tp=(ll)dp[cr][j]*dp[v][k];
        ans+=tp*(j+k);
          }
      for(int k=0;k<=dep[v];k++)
        dp[cr][k+1]+=dp[v][k];
    }
  }
  void solve()
  {
    for(int i=1;i<=n;i++)
      {
    if(dfn[i])continue;
        tot=tim=0;tarjan(i,0);
    if(!tot)
      {
        dfs(i,0);
        for(int j=2;j<=dep[i];j++)
          ans+=(ll)dp[i][j]*(j-1);
        continue;
      }
    for(int j=1;j<=tot;j++)dfs(a[j],0);
    for(int s=1;s<=tot;s++)
      for(int t=s+1;t<=tot;t++)
        for(int j=0;j<=dep[s];j++)
          for(int k=0;k<=dep[t];k++)
        {
          ll tp=(ll)dp[a[s]][j]*dp[a[t]][k];
          ans+=tp*(j+k+t-s-1);
        }
      }
    printf("%lld\n",ans);
  }
}
bool vis[N],fg;
void chk_dfs(int cr,int fa)
{
  vis[cr]=1;
  for(int i=hd[cr],v;i;i=nxt[i])
    if((v=to[i])!=fa)
      {
    if(vis[v]){fg=1;return;}
    chk_dfs(v,cr); if(fg)return;
      }
}
int main()
{
  n=rdn();m=rdn();
  for(int i=1,u,v;i<=m;i++)
    {
      u=rdn();v=rdn();add(u,v);add(v,u);
    }
  for(int i=1;i<=n;i++)
    if(!vis[i]){chk_dfs(i,0);if(fg)break;}
  if(!fg){S1::solve();return 0;}
  fg=0;
  for(int i=1;i<=n;i++)if(rd[i]>2){fg=1;break;}
  if(!fg){S2::solve();return 0;}
  if(n<=50){S3::solve();return 0;}
  if(n<=1000){S4::solve();return 0;}
  return 0;
}
View Code

 既然有了那个判断的想法,即一个 c 可行当且仅当它是 s -> f 路径上的方点连出去的某个圆点,那么就可以考虑怎样快速计算!

比如对于一对 s , f ,贡献就是路径上方点连出去的圆点个数 - 2 。

考虑每个点的贡献是多少,就能通过算该点在多少路径里而算出答案了。

令方点权值是连出去的圆点个数,圆点权值是 -1 即可。考虑如果是端点的圆点,只和一个方点相邻,被算了一遍又自己减去一遍;如果是路径中的圆点,和两个方点相邻,被算了两遍又自己减去一遍,就正好。

路径应该是两端是圆点的路径。计算方法见代码即可。

#include<cstdio>
#include<cstring>
#include<algorithm>
#define ll long long
using namespace std;
int rdn()
{
  int ret=0;bool fx=1;char ch=getchar();
  while(ch>'9'||ch<'0'){if(ch=='-')fx=0;ch=getchar();}
  while(ch>='0'&&ch<='9')ret=ret*10+ch-'0',ch=getchar();
  return fx?ret:-ret;
}
int Mx(int a,int b){return a>b?a:b;}
int Mn(int a,int b){return a<b?a:b;}
const int N=2e5+5,M=4e5+5;
int n,m,hd[N],xnt,to[M],nxt[M];
int tim,dfn[N],low[N],sta[N],top; bool ins[N];
int tot,tn,h2[N],t2[M],nt2[M],c[N],siz[N]; ll ans;
void add(int x,int y){to[++xnt]=y;nxt[xnt]=hd[x];hd[x]=xnt;}
void ad2(int x,int y){t2[++xnt]=y;nt2[xnt]=h2[x];h2[x]=xnt;}
void tarjan(int cr,int fa)
{
  dfn[cr]=low[cr]=++tim;
  sta[++top]=cr; ins[cr]=1; tot++;
  for(int i=hd[cr],v;i;i=nxt[i])
    if((v=to[i])!=fa)
      {
    if(ins[v])low[cr]=Mn(low[cr],dfn[v]);
    else if(!dfn[v])
      {
        tarjan(v,cr);low[cr]=Mn(low[cr],low[v]);
        if(low[v]>=dfn[cr])
          {
        tn++; ad2(cr,tn);ad2(tn,cr); c[tn]=1;
        do{
          int tp=sta[top]; ins[tp]=0;
          ad2(tp,tn); ad2(tn,tp); c[tn]++;
        }while(sta[top--]!=v);
          }
      }
      }
}
void dfs(int cr,int fa)
{
  bool fg=(cr<=n); siz[cr]=fg; ll tp=0;
  for(int i=h2[cr],v;i;i=nt2[i])
    if((v=t2[i])!=fa)
      {
    dfs(v,cr); siz[cr]+=siz[v];
    tp+=(ll)siz[v]*(tot-siz[cr]);
      }
  if(fg)ans-=tp+tot-1; else ans+=c[cr]*tp;
}
int main()
{
  n=rdn();m=rdn();
  for(int i=1,u,v;i<=m;i++)
    {
      u=rdn();v=rdn();add(u,v);add(v,u);
    }
  tn=n; xnt=0;
  for(int i=1;i<=n;i++)
    if(!dfn[i])
      {
    tim=tot=0;tarjan(i,0); dfs(i,0);
      }
  printf("%lld\n",ans*2);
  return 0;
}

 

posted on 2019-04-10 18:17  Narh  阅读(169)  评论(0编辑  收藏  举报

导航