题目: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=;bool fx=;char ch=getchar();
while(ch>''||ch<''){if(ch=='-')fx=;ch=getchar();}
while(ch>=''&&ch<='')ret=ret*+ch-'',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+,M=4e5+;
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]=; vis[cr]=;
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=;i<=n;i++)
if(!vis[i])
{
tot=; ini_dfs(i,); tot=siz[i];
dfs(i,); ans-=(ll)tot*(tot-)/;
}
printf("%lld\n",ans*);
}
}
namespace S2{
bool vis[N],flag; int cnt;
void dfs(int cr,int fa)
{
vis[cr]=; cnt++;
for(int i=hd[cr],v;i;i=nxt[i])
if((v=to[i])!=fa)
{
if(vis[v]){flag=;return;}
dfs(v,cr);
}
}
void solve()
{
ll ans=;
for(int i=;i<=n;i++)
if(!vis[i])
{
flag=;cnt=;dfs(i,);
if(cnt<=)continue;
if(!flag)
{
for(int j=;j<cnt;j++)
{
int ct=cnt-j;
ans+=(ll)ct*(j-)*;//*2
}
}
else ans+=(ll)(cnt-)*(cnt-)*cnt;
}
printf("%lld\n",ans);
}
}
namespace S3{
const int N2=N<<;
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]=;
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]=;
add(tot,tp); add(tp,tot);
}while(sta[top--]!=v);
}
}
}
}
void dfs(int cr,int fa)
{
col[cr]=cnt;dep[cr]=dep[fa]+;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=;
for(int i=;i<=n;i++)
if(!dfn[i])top=,tarjan(i,);
for(int i=;i<=tot;i++)
if(!col[i])cnt++,dfs(i,);
int ans=;
for(int s=;s<=n;s++)
for(int c=;c<=n;c++)
if(s!=c&&col[s]==col[c])
for(int t=;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=;
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]=;
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]=;top--;return;}
do{
int tp=sta[top]; a[++tot]=tp; vis[tp]=; ins[tp]=;
}while(sta[top--]!=cr);
}
}
void dfs(int cr,int fa)
{
dp[cr][]=;
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]+);
for(int j=;j<=dep[cr];j++)
for(int k=;k<=dep[v];k++)
{
ll tp=(ll)dp[cr][j]*dp[v][k];
ans+=tp*(j+k);
}
for(int k=;k<=dep[v];k++)
dp[cr][k+]+=dp[v][k];
}
}
void solve()
{
for(int i=;i<=n;i++)
{
if(dfn[i])continue;
tot=tim=;tarjan(i,);
if(!tot)
{
dfs(i,);
for(int j=;j<=dep[i];j++)
ans+=(ll)dp[i][j]*(j-);
continue;
}
for(int j=;j<=tot;j++)dfs(a[j],);
for(int s=;s<=tot;s++)
for(int t=s+;t<=tot;t++)
for(int j=;j<=dep[s];j++)
for(int k=;k<=dep[t];k++)
{
ll tp=(ll)dp[a[s]][j]*dp[a[t]][k];
ans+=tp*(j+k+t-s-);
}
}
printf("%lld\n",ans);
}
}
bool vis[N],fg;
void chk_dfs(int cr,int fa)
{
vis[cr]=;
for(int i=hd[cr],v;i;i=nxt[i])
if((v=to[i])!=fa)
{
if(vis[v]){fg=;return;}
chk_dfs(v,cr); if(fg)return;
}
}
int main()
{
n=rdn();m=rdn();
for(int i=,u,v;i<=m;i++)
{
u=rdn();v=rdn();add(u,v);add(v,u);
}
for(int i=;i<=n;i++)
if(!vis[i]){chk_dfs(i,);if(fg)break;}
if(!fg){S1::solve();return ;}
fg=;
for(int i=;i<=n;i++)if(rd[i]>){fg=;break;}
if(!fg){S2::solve();return ;}
if(n<=){S3::solve();return ;}
if(n<=){S4::solve();return ;}
return ;
}

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

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

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

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

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

#include<cstdio>
#include<cstring>
#include<algorithm>
#define ll long long
using namespace std;
int rdn()
{
int ret=;bool fx=;char ch=getchar();
while(ch>''||ch<''){if(ch=='-')fx=;ch=getchar();}
while(ch>=''&&ch<='')ret=ret*+ch-'',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+,M=4e5+;
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]=; 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]=;
do{
int tp=sta[top]; ins[tp]=;
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=;
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-; else ans+=c[cr]*tp;
}
int main()
{
n=rdn();m=rdn();
for(int i=,u,v;i<=m;i++)
{
u=rdn();v=rdn();add(u,v);add(v,u);
}
tn=n; xnt=;
for(int i=;i<=n;i++)
if(!dfn[i])
{
tim=tot=;tarjan(i,); dfs(i,);
}
printf("%lld\n",ans*);
return ;
}

LOJ 2587 「APIO2018」铁人两项——圆方树的更多相关文章

  1. loj2587 「APIO2018」铁人两项[圆方树+树形DP]

    主要卡在一个结论上..关于点双有一个常用结论,也经常作为在圆方树/简单路径上的良好性质,对于任意点双内互不相同的三点$s,c,t$,都存在简单路径$s\to c\to t$,证明不会.可以参见clz博 ...

  2. LOJ #2587「APIO2018」铁人两项

    是不是$ vector$存图非常慢啊...... 题意:求数对$(x,y,z)$的数量使得存在一条$x$到$z$的路径上经过$y$,要求$x,y,z$两两不同  LOJ #2587 $ Solutio ...

  3. 【刷题】LOJ 2587 「APIO2018」铁人两项

    题目描述 比特镇的路网由 \(m\) 条双向道路连接的 \(n\) 个交叉路口组成. 最近,比特镇获得了一场铁人两项锦标赛的主办权.这场比赛共有两段赛程:选手先完成一段长跑赛程,然后骑自行车完成第二段 ...

  4. [APIO2018] Duathlon 铁人两项 圆方树,DP

    [APIO2018] Duathlon 铁人两项 LG传送门 圆方树+简单DP. 不会圆方树的话可以看看我的另一篇文章. 考虑暴力怎么写,枚举两个点,答案加上两个点之间的点的个数. 看到题面中的一句话 ...

  5. 【LOJ】#2587. 「APIO2018」铁人两项

    题解 学习了圆方树!(其实是复习了Tarjan求点双) 我又双叒叕忘记了tarjan点双一个最重要,最重要的事情! 就是--假如low[v] >= dfn[u],我们就找到了一个点双,开始建立方 ...

  6. [APIO2018]铁人两项 --- 圆方树

     [APIO2018] 铁人两项 题目大意: 给定一张图,问有多少三元组(a,b,c)(a,b,c 互不相等)满足存在一条点不重复的以a为起点,经过b,终点为c的路径 如果你不会圆方树 ------- ...

  7. [APIO2018]铁人两项——圆方树+树形DP

    题目链接: [APIO2018]铁人两项 对于点双连通分量有一个性质:在同一个点双里的三个点$a,b,c$,一定存在一条从$a$到$c$的路径经过$b$且经过的点只被经过一次. 那么我们建出原图的圆方 ...

  8. [APIO2018]铁人两项 [圆方树模板]

    把这个图缩成圆方树,把方点的权值设成-1,圆点的权值设成点双的size,算 经过这个点的路径的数量*这个点的点权 的和即是答案. #include <iostream> #include ...

  9. 【Luogu4630】【APIO2018】 Duathlon 铁人两项 (圆方树)

    Description ​ 给你一张\(~n~\)个点\(~m~\)条边的无向图,求有多少个三元组\(~(x, ~y, ~z)~\)满足存在一条从\(~x~\)到\(~z~\)并且经过\(~y~\)的 ...

随机推荐

  1. Android程序设计-圆形图片的实现

    在android中,google只提供了对图形的圆形操作,而没有实现对图片的圆形操作,所以我们无法实现上述操作,在此我们将使用框架进行设计(下述框架为as编写): https://github.com ...

  2. Hibernate联合主键映射

    1.联合主键的映射规则 1) 类中的每个主键属性都对应到数据表中的每个主键列. Hibernate要求具有联合主键的实体类实现Serializable接口,并且重写hashCode与equals方法, ...

  3. C#的垃圾回收机制及弱引用

    在上一篇中,讨论了字符串常量的拘留池和不可变性:对于字符串变量,没有这个特性(或其他DotNet的非托管资源),当我们使用完后就要手动回收,即将变量的值指向null(p=null),然而堆内存中,那个 ...

  4. PHP fpm优化【转】

    在优化PHP的进程数的时候我们首先要了解我们服务器执行一个php使用的内存 1: 查询一个php占用的内存方法 pmap $(pgrep php-fpm | head -1) 我这里查询到的是 000 ...

  5. HDU4341-Gold miner-分组DP

    模拟黄金矿工这个游戏,给出每一个金子的位置和所需时间,计算在给定时间内最大收益. 刚看这道题以为金子的位置没什么用,直接DP就行,WA了一发终于明白如果金子和人共线的话只能按顺序抓. 这就是需要考虑先 ...

  6. 个人作业Week3

    个人作业week3 一.  调研,评测 1.我的使用体验 版本:IOS版   BUG_1: 点击单词本中的“同步”后,会提示登录Microsoft账户.登录成功立即开始同步单词本.在单词本同步过程中, ...

  7. python中from __future__ import division

    ppython2.7版本中整数相除得出的结果不显示小数 a = 9 / 2 print(a) 输出结果: 4 此时就需要调用from __future__ import division 1 from ...

  8. 介绍用C#和VS2015开发基于Unity架构的2D、3D游戏的技术

    [Unity]13.3 Realtime GI示例 摘要: 分类:Unity.C#.VS2015 创建日期:2016-04-19 一.简介 使用简单示例而不是使用实际示例的好处是能让你快速理解光照贴图 ...

  9. androidStudio 打包与混淆

    在gradle中通过makeJar打包 不同模块的gradle都支持打包功能,application module的build.gradle中引入的是com.android.application插件 ...

  10. C# list.ForEach用法

    list.ForEach(delegate(T model) { ... });