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=0;bool fx=1;char ch=getchar(); while(ch>'9'||ch<'0'){if(ch=='-')fx=0;ch=getchar();} while(ch>='0'&&ch<='9')ret=ret*10+ch-'0',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+5,M=4e5+5; 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]=1; vis[cr]=1; 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=1;i<=n;i++) if(!vis[i]) { tot=0; ini_dfs(i,0); tot=siz[i]; dfs(i,0); ans-=(ll)tot*(tot-1)/2; } printf("%lld\n",ans*2); } } namespace S2{ bool vis[N],flag; int cnt; void dfs(int cr,int fa) { vis[cr]=1; cnt++; for(int i=hd[cr],v;i;i=nxt[i]) if((v=to[i])!=fa) { if(vis[v]){flag=1;return;} dfs(v,cr); } } void solve() { ll ans=0; for(int i=1;i<=n;i++) if(!vis[i]) { flag=0;cnt=0;dfs(i,0); if(cnt<=2)continue; if(!flag) { for(int j=2;j<cnt;j++) { int ct=cnt-j; ans+=(ll)ct*(j-1)*2;//*2 } } else ans+=(ll)(cnt-2)*(cnt-1)*cnt; } printf("%lld\n",ans); } } namespace S3{ const int N2=N<<1; 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]=1; 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]=0; add(tot,tp); add(tp,tot); }while(sta[top--]!=v); } } } } void dfs(int cr,int fa) { col[cr]=cnt;dep[cr]=dep[fa]+1;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=0; for(int i=1;i<=n;i++) if(!dfn[i])top=0,tarjan(i,0); for(int i=1;i<=tot;i++) if(!col[i])cnt++,dfs(i,0); int ans=0; for(int s=1;s<=n;s++) for(int c=1;c<=n;c++) if(s!=c&&col[s]==col[c]) for(int t=1;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=1005; 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]=1; 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]=0;top--;return;} do{ int tp=sta[top]; a[++tot]=tp; vis[tp]=1; ins[tp]=0; }while(sta[top--]!=cr); } } void dfs(int cr,int fa) { dp[cr][0]=1; 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]+1); for(int j=1;j<=dep[cr];j++) for(int k=0;k<=dep[v];k++) { ll tp=(ll)dp[cr][j]*dp[v][k]; ans+=tp*(j+k); } for(int k=0;k<=dep[v];k++) dp[cr][k+1]+=dp[v][k]; } } void solve() { for(int i=1;i<=n;i++) { if(dfn[i])continue; tot=tim=0;tarjan(i,0); if(!tot) { dfs(i,0); for(int j=2;j<=dep[i];j++) ans+=(ll)dp[i][j]*(j-1); continue; } for(int j=1;j<=tot;j++)dfs(a[j],0); for(int s=1;s<=tot;s++) for(int t=s+1;t<=tot;t++) for(int j=0;j<=dep[s];j++) for(int k=0;k<=dep[t];k++) { ll tp=(ll)dp[a[s]][j]*dp[a[t]][k]; ans+=tp*(j+k+t-s-1); } } printf("%lld\n",ans); } } bool vis[N],fg; void chk_dfs(int cr,int fa) { vis[cr]=1; for(int i=hd[cr],v;i;i=nxt[i]) if((v=to[i])!=fa) { if(vis[v]){fg=1;return;} chk_dfs(v,cr); if(fg)return; } } int main() { n=rdn();m=rdn(); for(int i=1,u,v;i<=m;i++) { u=rdn();v=rdn();add(u,v);add(v,u); } for(int i=1;i<=n;i++) if(!vis[i]){chk_dfs(i,0);if(fg)break;} if(!fg){S1::solve();return 0;} fg=0; for(int i=1;i<=n;i++)if(rd[i]>2){fg=1;break;} if(!fg){S2::solve();return 0;} if(n<=50){S3::solve();return 0;} if(n<=1000){S4::solve();return 0;} return 0; }
既然有了那个判断的想法,即一个 c 可行当且仅当它是 s -> f 路径上的方点连出去的某个圆点,那么就可以考虑怎样快速计算!
比如对于一对 s , f ,贡献就是路径上方点连出去的圆点个数 - 2 。
考虑每个点的贡献是多少,就能通过算该点在多少路径里而算出答案了。
令方点权值是连出去的圆点个数,圆点权值是 -1 即可。考虑如果是端点的圆点,只和一个方点相邻,被算了一遍又自己减去一遍;如果是路径中的圆点,和两个方点相邻,被算了两遍又自己减去一遍,就正好。
路径应该是两端是圆点的路径。计算方法见代码即可。
#include<cstdio> #include<cstring> #include<algorithm> #define ll long long using namespace std; int rdn() { int ret=0;bool fx=1;char ch=getchar(); while(ch>'9'||ch<'0'){if(ch=='-')fx=0;ch=getchar();} while(ch>='0'&&ch<='9')ret=ret*10+ch-'0',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+5,M=4e5+5; 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]=1; 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]=1; do{ int tp=sta[top]; ins[tp]=0; 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=0; 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-1; else ans+=c[cr]*tp; } int main() { n=rdn();m=rdn(); for(int i=1,u,v;i<=m;i++) { u=rdn();v=rdn();add(u,v);add(v,u); } tn=n; xnt=0; for(int i=1;i<=n;i++) if(!dfn[i]) { tim=tot=0;tarjan(i,0); dfs(i,0); } printf("%lld\n",ans*2); return 0; }