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=;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」铁人两项——圆方树的更多相关文章
- loj2587 「APIO2018」铁人两项[圆方树+树形DP]
主要卡在一个结论上..关于点双有一个常用结论,也经常作为在圆方树/简单路径上的良好性质,对于任意点双内互不相同的三点$s,c,t$,都存在简单路径$s\to c\to t$,证明不会.可以参见clz博 ...
- LOJ #2587「APIO2018」铁人两项
是不是$ vector$存图非常慢啊...... 题意:求数对$(x,y,z)$的数量使得存在一条$x$到$z$的路径上经过$y$,要求$x,y,z$两两不同 LOJ #2587 $ Solutio ...
- 【刷题】LOJ 2587 「APIO2018」铁人两项
题目描述 比特镇的路网由 \(m\) 条双向道路连接的 \(n\) 个交叉路口组成. 最近,比特镇获得了一场铁人两项锦标赛的主办权.这场比赛共有两段赛程:选手先完成一段长跑赛程,然后骑自行车完成第二段 ...
- [APIO2018] Duathlon 铁人两项 圆方树,DP
[APIO2018] Duathlon 铁人两项 LG传送门 圆方树+简单DP. 不会圆方树的话可以看看我的另一篇文章. 考虑暴力怎么写,枚举两个点,答案加上两个点之间的点的个数. 看到题面中的一句话 ...
- 【LOJ】#2587. 「APIO2018」铁人两项
题解 学习了圆方树!(其实是复习了Tarjan求点双) 我又双叒叕忘记了tarjan点双一个最重要,最重要的事情! 就是--假如low[v] >= dfn[u],我们就找到了一个点双,开始建立方 ...
- [APIO2018]铁人两项 --- 圆方树
[APIO2018] 铁人两项 题目大意: 给定一张图,问有多少三元组(a,b,c)(a,b,c 互不相等)满足存在一条点不重复的以a为起点,经过b,终点为c的路径 如果你不会圆方树 ------- ...
- [APIO2018]铁人两项——圆方树+树形DP
题目链接: [APIO2018]铁人两项 对于点双连通分量有一个性质:在同一个点双里的三个点$a,b,c$,一定存在一条从$a$到$c$的路径经过$b$且经过的点只被经过一次. 那么我们建出原图的圆方 ...
- [APIO2018]铁人两项 [圆方树模板]
把这个图缩成圆方树,把方点的权值设成-1,圆点的权值设成点双的size,算 经过这个点的路径的数量*这个点的点权 的和即是答案. #include <iostream> #include ...
- 【Luogu4630】【APIO2018】 Duathlon 铁人两项 (圆方树)
Description 给你一张\(~n~\)个点\(~m~\)条边的无向图,求有多少个三元组\(~(x, ~y, ~z)~\)满足存在一条从\(~x~\)到\(~z~\)并且经过\(~y~\)的 ...
随机推荐
- C点滴成海------Dev C++怎么修改成简体中文
第一步:选择菜单中的Tools 第二步:选择Tools中的“Envirnoment Options”,即第二个选项 第三步:选择中文并保存 将"1"的语言改成中文就行了
- base64encode 编码原理
Base64编码,是我们程序开发中经常使用到的编码方法.它是一种基于用64个可打印字符来表示二进制数据的表示方法.它通常用作存储.传输一些二进制数据编码方法!也是MIME(多用途互联网邮件扩展,主要用 ...
- kali linux 入侵window实例
我使用Kali Linux的IP地址是192.168.0.112:在同一局域网内有一台运行Windows XP(192.168.0.108)的测试电脑. 本文演示怎么使用Metasploit入侵win ...
- tomcat 配置成服务
1.下载Zip版Tomcat;选择:32-bit Windows zip(pgp,md5)下载解压文件到指定目录,如:D:/ProgramFiles/Tomcat6 进入D:/ProgramFiles ...
- endnote插入文献的一些问题
1. 文末参考文献前面加上序号 eg.[1] 设置如下图,参考网址https://zhidao.baidu.com/question/360201207639069172.html edit-> ...
- http状态码301和302的区别
1.官方的比较简洁的说明: 301 redirect: 301 代表永久性转移(Permanently Moved) 302 redirect: 302 代表暂时性转移(Temporarily Mov ...
- 如何在Power BI和Tableau之间进行选择?
作者:Mike Gallucci 如今,当谈到商业智能(BI)分析软件时,有两个主要的行业领导者 - 微软的Power BI和Tableau.根据分析和业务平台的2018年Gartner Magic ...
- tomcat自动缓存的几种解决方式
第一种方法:打开一个项目,这里我打开的Mail项目,然后点击Myeclipse菜单栏中的project-选择clean: 选择要clean的项目,确定即可不用进入tomcat服务器直接清理缓存. 上面 ...
- linux command1
#列出指定用户(当前用户)的组信息 groups #将指定的用户添加(-a)到指定的组内(改组必须已经存在)或指定用户从指定的组中删除(-d) gpasswd –a/-d username grou ...
- 全局(Global) 与本地(Local)索引的区别
一.定义说明 oracle数据库中,存在多种对象,最常见的表和索引,索引的目的是为了加快查询:oracle建议一个表>2g时,就建议进行分区,分区表的好处此处省略,分区表有维护操作,但是某些维护 ...