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

时间:2021-08-24 23:05:15

题目: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」铁人两项&lbrack;圆方树&plus;树形DP&rsqb;

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

  2. LOJ &num;2587「APIO2018」铁人两项

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

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

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

  4. &lbrack;APIO2018&rsqb; Duathlon 铁人两项 圆方树,DP

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

  5. 【LOJ】&num;2587&period; 「APIO2018」铁人两项

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

  6. &lbrack;APIO2018&rsqb;铁人两项 --- 圆方树

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

  7. &lbrack;APIO2018&rsqb;铁人两项——圆方树&plus;树形DP

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

  8. &lbrack;APIO2018&rsqb;铁人两项 &lbrack;圆方树模板&rsqb;

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

  9. 【Luogu4630】【APIO2018】 Duathlon 铁人两项 &lpar;圆方树&rpar;

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

随机推荐

  1. RHEL7文件管理

    Linux系统目录结构 主要目录说明 目录 说明 / 通常称为根分区所有的文件和目录的起始点只有root用户对此目录拥有写权限 /home 普通用户的宿主目录 /root 超级用户的宿主目录 /dev ...

  2. &lbrack;Usaco2006 Nov&rsqb;Corn Fields牧场的安排 壮压DP

    看到第一眼就发觉是壮压DP 然后就三进制枚举子集吧. 这题真是壮压入门好题... 对于dp[i][j] 表示第i行,j状态下前i行的分配方案数. 那么dp[i][j]肯定是从i-1行转过来的 那么由于 ...

  3. &lbrack;每天一个Linux小技巧&rsqb; gdb 下一次运行多个命令

    一般gdb运行的时候,我们仅仅能输入一个命令. 如: (gdb) c (gdb) bt 假设想运行多个命令怎么办? 能否像bash那样, 使用; 如 ls; ls 结论是不行. 但能够通过gdb 内建 ...

  4. mysql 获取上个月,这个月的第一天或最后一天

    /*上个月今天的当前时间*/select date_sub(now(),interval 1 month) /*上个月今天的当前时间(时间戳)*/select UNIX_TIMESTAMP(date_ ...

  5. linux 下 ifcfg-ethx配置和解析

    网络接口配置文件[root@localhost ~]# cat /etc/sysconfig/network-scripts/ifcfg-eth0# Intel Corporation 82545EM ...

  6. Oracle11g&lowbar;OCM 课堂教学目录表

    注:本文为原著(其内容来自 腾科教育培训课堂).阅读本文注意事项如下: 1:所有文章的转载请标注本文出处. 2:本文非本人不得用于商业用途.违者将承当相应法律责任. 3:该系列文章目录列表: 一:&l ...

  7. C&num;自定义Button按钮控件

    C#自定义Button按钮控件 在实际项目开发中经常可以遇到.net自带控件并不一定可以满足需要,因此需要自定义开发一些新的控件,自定义控件的办法也有多种,可以自己绘制线条颜色图形等进行重绘,也可以采 ...

  8. 微信小程序 -- 数据请求

    微信小程序 -- 数据请求 微信小程序请求数据,并不是一个可以在url打开有数据就可以拿到数据那么简单 浏览器地址输入 可以获取参数的url 微信小程序中 代码展示 wxml <view> ...

  9. BZOJ 2720&colon; &lbrack;Violet 5&rsqb;列队春游

    2720: [Violet 5]列队春游 Time Limit: 5 Sec  Memory Limit: 128 MBSubmit: 189  Solved: 133[Submit][Status] ...

  10. 使用Bootstrap的suggest下拉插件

    前端代码 /*html代码*/ <input type="text" class="form-control search_ul" id="ca ...