题目: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. table 表头固定 thead固定. 1) 使用jquery.freezeheader.js

    方法一: 使用jquery.freezeheader.js 固定表头: 1-: 初始化: <!DOCTYPE html> <html lang="en"> ...

  2. bootstrap --- 在 modal中的 datetimepicker 关闭 ,会造成 modal也会被关闭.

    这个大概是事件冒泡造成的. 解决办法: <form id="userForm" class="form-horizontal"> <input ...

  3. python 元组攻略

    1.元组中只包含一个元素时,需要在元素后面添加逗号来消除歧义 tup1=(50,) 2.元组中的元素值使不允许修改的,但可以对元组进行连接组合复制代码 1 tup1=(12,34.56)2 tup2= ...

  4. 微信公众号开发遇到simplexml_load_string 未定义

    1.Go to /etc/php/7.0/fpm and edit php.ini 取消注释:     extension=php_xmlrpc.dll 2. sudo apt-get update ...

  5. HDU 6050 17多校2 Funny Function(数学+乘法逆元)

    Problem Description Function Fx,ysatisfies:For given integers N and M,calculate Fm,1 modulo 1e9+7.   ...

  6. 【Python】xpath-1

    1.coverage包实现代码覆盖率 (1)pip install coverage (2)coverage run XX.py(测试脚本文件) (3)coverage report -m 在控制台打 ...

  7. C++---String类小结

    本随笔是我自己在敲代码的时候一些感受,可能有的地方简略了,还有部分是融合了一点其他资料上的知识点,如有错误欢迎指出,谢谢_(:°з」∠)_ string类是C++中常用的字符串类型,比之普通的字符串要 ...

  8. 20165228 2017-2018-2 《Java程序设计》第2周学习总结

    20165228 2017-2018-2 <Java程序设计>第2周学习总结 教材学习内容总结 标识符:名字 组成:字母/下划线/美元符号或数字 注意:标识符的第一个字符不能是数字字符,且 ...

  9. POJ3525:Most Distant Point from the Sea(二分+半平面交)

    pro:给定凸多边形,求凸多边形内的点到最近边界的最远距离. sol:显然是二分一个圆,使得圆和凸多边形不相交,但是这样很难实现. 由于是凸多边形,我们可以把二分圆转化为二分凸多边形的移动. 如果每一 ...

  10. 用纯JS实现,点击一个列表时,输出对应的索引(不能用JQuery哦)

    你运行一下代码会发现,无论你点击哪个列表,控制台都是输出10.这是因为var声明的变量是函数作用域的,而不是块级作用域的.也就是说,for循环10次,每次都是改变同一个i,所以它的值会从0一直加到10 ...