题目: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. 2019-03-25-day018-面向对象

    re模块 字符串匹配 列表 = findall(正则表达式,待匹配的字符串) 找所有 结果集 = search 找第一个,结果集.group() 结果集 = match 从第一个字符开始匹配,结果集. ...

  2. php 安装过程 第二次探索

    由于第一次安装过程写的比较乱,再做整理 1.phpstudy 才是正确姿势. http://phpstudy.php.cn/ 官网下载.  phpstrom 中配置 php为 phpstudy目录下 ...

  3. Window下部署Maven Nexus

    Nexus下载地址:https://www.sonatype.com/download-oss-sonatype 选择相应的版本下载后,本人下载的是nexus-2.12.0-01-bundle.zip ...

  4. 【tomcat】启动报错:Failed to initialize end point associated with ProtocolHandler ["http-apr-8080"] java.lang.Exception: Socket bind failed 和java.net.BindException: Address already in use: JVM_Bind错误解决

    背景:[新手] 将开发机子上的Tomcat连同其中的项目,一起拷贝到服务器上,启动tomcat的start.bat,然后报错如下: 问题1: Failed to initialize end poin ...

  5. ORACLE函数、连接查询、约束

    *ORDER BY 子句在SELECT语句的结尾. 使用外连接可以查询不满足连接条件的数据 with字句 字符函数lower upper initcap concat substr length in ...

  6. 2.19 cookie相关操作

    2.19 cookie相关操作 前言虽然cookie相关操作在平常ui自动化中用得少,偶尔也会用到,比如登录有图形验证码,可以通过绕过验证码方式,添加cookie方法登录.登录后换账号登录时候,也可作 ...

  7. 防盗链技术终极解决方案(squid+cookie)

    防盗链技术现状:1.通过识别Referer确认请求来源页面2.Apache,squid等都能对Referer进行识别3.通过ActiveX显示的内容不向服务器提供Referer Header(例如,F ...

  8. ORA-15025 搭建DG环境,restore controlfile报错,提示oracle无法使用ASM存储

    环境说明: #主库RAC环境 #备库RAC环境,操作系统AIX 6.1 数据库版本11.2.0.3 报错说明: #主库备份控制文件,传输至备库,备库restore 报错 本篇文档,分为两大阶段:第一阶 ...

  9. 安装12C小问题及pdb表空间配置

    安装12C小问题及pdb表空间配置 一.安装 1.RPM包 #安装12C需要安装的rpm包,官网搜索,做个记录 bc binutils-2.23.52.0.1-12.el7(x86_64) compa ...

  10. Python中random模块在主函数中设置随机种子是否对于调用的函数中的随机值产生影响?

    一个问题,加入我有一个工程文件,在main函数里面调用random模块,设置随机种子,主函数中的随机种子的设置是否会影响主函数所调用的函数中的随机值? 实际上这个问题非常重要,比如你在跑网络的时候,初 ...