20200615 仙人掌、圓方樹、支配樹、跳舞鏈作業

[APIO2018] Duathlon 鐵人兩項

無向圖,選三個不同的點s,c,fs,c,f,使得存在一條scfs\to c\to f的簡單路徑,求方案數。

題解:
圓方樹,縮點雙。兩個圓點樹上路徑中經過的方點對應點雙中的點都可以作爲cc
方點權值爲點雙大小,圓點權值爲-1,那麼可選的cc的數量就是路徑點權和。統計一個點被多少圓點路徑經過即可,簡單樹上DP。

Code:

#include<bits/stdc++.h>
#define maxn 200005
#define maxm 400005
using namespace std;
int n,m,tsz,sz,val[maxn];
long long ans;
namespace RST{
	int siz[maxn],fir[maxn],nxt[maxm],to[maxm],tot;
	inline void line(int x,int y){nxt[++tot]=fir[x],fir[x]=tot,to[tot]=y;}
	void dfs(int u){
		siz[u]=val[u]?0:(val[u]=-1,1);
		for(int i=fir[u],v;i;i=nxt[i]) 
			dfs(v=to[i]),ans+=1ll*siz[u]*siz[v]*val[u],siz[u]+=siz[v];
		ans+=1ll*(tsz-siz[u])*siz[u]*val[u];
	}
}
int dfn[maxn],low[maxn],tim,stk[maxn],top;
int fir[maxn],nxt[maxm],to[maxm],tot;
inline void line(int x,int y){nxt[++tot]=fir[x],fir[x]=tot,to[tot]=y;}
void tarjan(int u){
	dfn[u]=low[u]=++tim,stk[++top]=u,tsz++;
	for(int i=fir[u],v;i;i=nxt[i])
		if(!dfn[v=to[i]]){
			tarjan(v),low[u]=min(low[u],low[v]);
			if(dfn[u]<=low[v]){
				RST::line(u,++sz),val[sz]++;
				do RST::line(sz,stk[top]),val[sz]++; while(stk[top--]!=v);	
			}
		}
		else low[u]=min(low[u],dfn[v]);
}
int main()
{
	scanf("%d%d",&n,&m),sz=n;
	for(int i=1,x,y;i<=m;i++) scanf("%d%d",&x,&y),line(x,y),line(y,x);
	for(int i=1;i<=n;i++) if(!dfn[i]) tsz=0,tarjan(i),RST::dfs(i);
	printf("%lld\n",ans<<1);
}

「POI2011 R2 Day1」垃圾運輸 Garbage

經過兩次可以轉化爲不經過,兩個小環形成一個大環。
所以就是求若干簡單環覆蓋圖的方案。同歐拉回路,若有點度數爲奇數無解。
簡單環點不重複,所以寫法有些奇特,每次只找一條邊,找到環之後退棧存環,然後將第一個點入棧繼續找。
當前弧優化以及給邊打標記保證了複雜度是O(m)O(m)的。

Code:

#include<bits/stdc++.h>
#define maxn 100005
#define maxm 2000005
using namespace std;
int n,m,d[maxn];
int fir[maxn],nxt[maxm],to[maxm],tot=1;
void line(int x,int y){nxt[++tot]=fir[x],fir[x]=tot,to[tot]=y;}
vector<int>cir[maxn];
int cnt,S[maxn],tp; bool inq[maxn],vis[maxm];
void dfs(int u){
	if(inq[u]){
		cir[++cnt].push_back(u); int v;
		do inq[v=S[tp--]]=0,d[v]-=2,cir[cnt].push_back(v); while(v!=u);
	}
	S[++tp]=u,inq[u]=1;
	for(int &i=fir[u];i;i=nxt[i]) if(!vis[i]){
		vis[i]=vis[i^1]=1,dfs(to[i]); return;
	}
}
int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1,x,y,s,t;i<=m;i++){
		scanf("%d%d%d%d",&x,&y,&s,&t);
		if(s!=t) line(x,y),line(y,x),d[x]++,d[y]++;
	}
	for(int i=1;i<=n;i++) if(d[i]&1) return puts("NIE"),0;
	for(int i=1;i<=n;i++) while(d[i]) dfs(i);
	printf("%d\n",cnt);
	for(int i=1,lim;i<=cnt;i++){
		printf("%d",lim=cir[i].size()-1);
		for(int j=0;j<=lim;j++) printf(" %d",cir[i][j]); putchar('\n');
	}
}

CF231E Cactus

點仙人掌(每個點屬於一個簡單環),求xxyy不經過重複邊的路徑條數

如果用圓方樹的話需要考慮x,y,lcax,y,lca的兒子及lcalca的父親是不是方點。(因爲是不經過重複邊,所以xx可以到它的點雙中轉一圈再往上)

而實際上,點仙人掌是可以直接縮的(因爲一個點只在一個環裏面,可以當成邊雙來縮點),然後就只需要看scc[x]scc[x]scc[y]scc[y]的路徑上縮點之前大小>1的點的個數就可以了。做個樹上前綴和+LCA輕鬆解決。

Code:

#include<bits/stdc++.h>
#define maxn 100005
#define pb(x) push_back(x)
#define rep(v,G) for(int i=G.size()-1,v;i>=0?(v=G[i],1):0;i--)
using namespace std;
const int mod = 1e9+7;
int n,m,scnt,scc[maxn],siz[maxn],dfn[maxn],low[maxn],tim,stk[maxn],top;
vector<int>G[maxn],id[maxn],E[maxn];
void tarjan(int u,int ff){
	dfn[u]=low[u]=++tim,stk[++top]=u;
	rep(v,G[u]) if(id[u][i]!=ff){
		if(!dfn[v]) tarjan(v,id[u][i]),low[u]=min(low[u],low[v]);
		else low[u]=min(low[u],dfn[v]);
	}
	if(dfn[u]==low[u]){
		++scnt;
		do scc[stk[top]]=scnt,siz[scnt]++; while(stk[top--]!=u);
	}
}
int dep[maxn],fa[maxn],s[maxn],f[17][maxn],pw[maxn];
void dfs(int u,int ff){
	dep[u]=dep[f[0][u]=ff]+1,s[u]=s[ff]+(siz[u]>1);
	for(int i=1;1<<i<dep[u];i++) f[i][u]=f[i-1][f[i-1][u]];
	rep(v,E[u]) if(v!=ff) dfs(v,u);
}
int LCA(int u,int v){
	if(dep[u]<dep[v]) swap(u,v);
	for(int i=0,d=dep[u]-dep[v];d;d>>=1,i++) if(d&1) u=f[i][u];
	if(u==v) return u;
	for(int i=16;i>=0;i--) if(f[i][u]!=f[i][v]) u=f[i][u],v=f[i][v];
	return f[0][u];
}
int main()
{
	scanf("%d%d",&n,&m); int x,y;
	for(int i=1;i<=m;i++) scanf("%d%d",&x,&y),G[x].pb(y),G[y].pb(x),id[x].pb(i),id[y].pb(i);
	tarjan(1,0);
	for(int u=1;u<=n;u++) rep(v,G[u]) if((x=scc[u])!=(y=scc[v])) E[x].pb(y);
	dfs(1,0);
	int Q;scanf("%d",&Q);
	for(int i=pw[0]=1;i<=n;i++) pw[i]=pw[i-1]*2%mod;
	for(int z;Q--;) scanf("%d%d",&x,&y),z=LCA(x=scc[x],y=scc[y]),printf("%d\n",pw[s[x]+s[y]-s[z]-s[f[0][z]]]);
}

LOJ#6240. 仙人掌

這概率計算好tm噁心。。

仙人掌隨機點分治,求期望複雜度。
可以先看看樹隨機點分治怎麼做。
根據E(xi)=E(xi)E(\sum x_i)=\sum E(x_i),可以分開算每個點的貢獻,就是它在點分樹中的深度。設 Pi,jP_{i,j} 表示當刪除點 jji,ji,j 在前一時刻保持連通的概率,那麼點 ii 的貢獻等價於jPi,j\sum_{j}P_{i,j}

如果在樹中,Pi,j=1number of points on path(i,j)P_{i,j}=\frac {1}{number~of~points ~on~path(i,j)},即點 jj 是路徑上第一個刪除的點。
在仙人掌上,由於一個點雙有兩條路徑,所以可以斷掉在某一半的點,i,ji,j仍然是連通的。
這時候 Pi,jP_{i,j} 的計算方式大概有兩種思路:

  • 方法一:

    cntcnt表示iji\to j經過的點雙大小之和(包括單點)。
    求出dp[j][x]dp[j][x]表示在這cntcnt個點中選擇xx個點刪去後i,ji,j仍然連通的方案數。
    求答案就枚舉jj在路徑上是第幾個被刪去的點,假設有xx個點在它之前刪去,那麼概率是1cnt1cnt1...1cntxx!=(cntx1)!x!cnt!\frac {1}{cnt}*\frac {1}{cnt-1}...\frac 1{cnt-x}*x!=\frac {(cnt-x-1)!x!}{cnt!}

    答案即爲j=1nx=0cnt2dp[j][x](cntx1)!x!cnt!\sum\limits_{j=1}^n\sum\limits_{x=0}^{cnt-2}dp[j][x]*\frac {(cnt-x-1)!x!}{cnt!}

    dpdp 值的方法也有兩種:

    • 轉移就是枚舉當前點雙選某一邊刪去kk個點,dp[u][i]dp[v][i+k]dp[u][i]\to dp[v][i+k],但是枚舉需要預處理優化複雜度,同時預處理之後轉移時需要去掉同時從dp[u]dp[u]轉移來的部分,詳見cz_xuyixuan‘s blog
    • 更改定義,dpdp值相當於是在求在一些集合裏面選出一些點,可以求出集合的總大小,再在其中選點,每個位置需要加上上一輪的值*-1,減去重複選點的方案。詳見chasedeath’s blog

  • 方法二:

    dp[j][x]dp[j][x] 表示保留 xx 個點使其連通(不能刪)的方案數(即每個點雙的兩邊強制一邊保留、另一邊任意),刪掉 xx 個點中的任意一個都會使其不連通,所以在刪 jj 時保持連通的概率就是 1x+1\frac 1{x+1}。(jj 第一個被刪)。轉移就枚舉哪一邊保留,於是答案中計算了強制左邊保留和強制右邊保留的概率之和,所以兩邊都連通的概率被重複計算了,需要令 dp[v][x+circle1]=dp[u][x]dp[v][x+circle-1]-=dp[u][x]
    (略迷)

Code(方法二):

#include<bits/stdc++.h>
#define maxn 805
#define pb(x) push_back(x)
#define rep(v,G) for(int i=G.size()-1,v;i>=0&&(v=G[i]);i--)
using namespace std;
const int mod = 998244353;
int n,m,dfn[maxn],low[maxn],tim,stk[maxn],top,sz,pos[maxn][maxn],siz[maxn];
int inv[maxn],f[maxn][maxn],ans;
vector<int>G[maxn],E[maxn];
void tarjan(int u){
	dfn[u]=low[u]=++tim,stk[++top]=u;
	rep(v,G[u])
		if(!dfn[v]){
			tarjan(v),low[u]=min(low[u],low[v]);
			if(dfn[u]<=low[v]){
				E[++sz].pb(u),E[u].pb(sz); int x,d=0;
				do x=stk[top],E[x].pb(sz),E[sz].pb(x),pos[sz][x]=++d; while(stk[top--]!=v);
				siz[sz]=d+1;
			}
		}
		else low[u]=min(low[u],dfn[v]);
}
void dfs(int u,int ff,int cnt){
	for(int i=1;i<=cnt;i++) if(f[u][i]) ans=(ans+1ll*f[u][i]*inv[i])%mod;
	rep(k,E[u]) if(k!=ff)
		rep(v,E[k]) if(v!=u){
			int d=abs(pos[k][v]-pos[k][u]),s=siz[k];
			for(int j=1;j<=cnt;j++) if(f[u][j]){
				(f[v][j+d]+=f[u][j])%=mod,
				(f[v][j+s-d]+=f[u][j])%=mod,
				(f[v][j+s-1]-=f[u][j])%=mod;
			}
			dfs(v,k,cnt+s-1);
		}
}
int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1,x,y;i<=m;i++) scanf("%d%d",&x,&y),G[x].pb(y),G[y].pb(x);
	sz=n,tarjan(1);
	inv[0]=inv[1]=1; for(int i=2;i<=n;i++) inv[i]=1ll*(mod-mod/i)*inv[mod%i]%mod;
	for(int i=1;i<=n;i++) memset(f,0,sizeof f),f[i][1]=1,dfs(i,0,1);
	printf("%d\n",(ans+mod)%mod);
}

CF980F Cactus to Tree

點仙人掌,對每個點獨立算出:將仙人掌刪成一棵樹後離它最遠的點的距離的最小值。

題解:
稍作思考,實際上求的是點仙人掌上最短路的最大值。(貪心,如果從xx進入環,那麼刪的一定是環上與xx相對的那條邊,每個點的最短路都能取到)

先把第一個環作爲根拿出來考慮。
先BFS求出每個點往下的最遠距離,此時第一個環的 LL 就已知了。
記第一個環上的點經過橋邊走到的最遠距離爲 L[x]L[x],那麼對於環上的點 uu,它的答案就是 maxL[v]+dist(u,v)\max L[v]+dist(u,v)
dist(u,v)dist(u,v) 是環上 u,vu,v 的最短距離,記每個點的答案爲 f[x]f[x],環上的 ff 就可以通過單調隊列順時針逆時針分別轉一圈半算出。

然後要轉移到與根相連的環,假設相接的點是 u,vu,v,那麼要用 max(f[u]+1,l+1)\max (f[u]+1,l+1) 去更新 L[p]L[p]ll 是根據 L[p]+1L[p]+1 是否等於 L[u]L[u] 來決定是否取次大值。

Code:

#include<bits/stdc++.h>
#define maxn 500005
#define pb(x) push_back(x)
#define rep(i,j,k) for(int i=(j),LIM=(k);i<=LIM;i++)
using namespace std;
const int mod = 998244353;
int n,m,dfn[maxn],low[maxn],tim,stk[maxn],top,scnt,scc[maxn];
vector<int>G[maxn],cir[maxn];
void tarjan(int u,int ff){
	dfn[u]=low[u]=++tim,stk[++top]=u;
	for(int v:G[u]) if(v!=ff){
		if(!dfn[v]) tarjan(v,u),low[u]=min(low[u],low[v]);
		else low[u]=min(low[u],dfn[v]);
	}
	if(dfn[u]==low[u]){
		++scnt;
		for(int x=-1;x!=u;) scc[x=stk[top--]]=scnt,cir[scnt].pb(x);
	}
}
int d[maxn],mxd[maxn];
void BFS(){
	queue<int>q; memset(d,-1,sizeof d),d[1]=0,q.push(1);
	while(!q.empty()) {int u=q.front();q.pop(); for(int v:G[u]) if(d[v]==-1) d[v]=d[u]+1,q.push(v);}
}
bool vis[maxn];
void DFS(int u){
	vis[u]=1,mxd[u]=d[u];
	for(int v:G[u]) if(!vis[v]) DFS(v),mxd[u]=max(mxd[u],mxd[v]);
}
int L[maxn],CL[maxn],f[maxn];
void dfs(int u,int ff,int fL){
	int o=scc[u];
	for(int u:cir[o]) for(int v:G[u]) if(scc[v]!=o){
		int now = scc[u]>scc[v] ? mxd[v]-d[u] : fL;
		if(now>L[u]) CL[u]=L[u],L[u]=now;
		else CL[u]=max(CL[u],now);
	}
	static pair<int,int>q[maxn<<1];
	int len=cir[o].size(),lim=len/2,l,r;
	#define work \
	q[l=r=1]=make_pair(L[cir[o][0]],0);\
	rep(i,1,len-1+lim){\
		while(i-q[l].second>lim) l++;\
		int u=cir[o][i%len]; f[u]=max(f[u],i+q[l].first);\
		while(l<=r&&q[r].first<=L[u]-i) r--;\
		q[++r]=make_pair(L[u]-i,i);\
	}
	work
	reverse(cir[o].begin(),cir[o].end());
	work
	for(int u:cir[o]) for(int v:G[u]) if(scc[v]!=o&&v!=ff)
		dfs(v,u,max(f[u],mxd[v]-d[u]==L[u]?CL[u]:L[u])+1);
}
int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1,x,y;i<=m;i++) scanf("%d%d",&x,&y),G[x].pb(y),G[y].pb(x);
	tarjan(1,0);
	BFS(),DFS(1);
	dfs(1,0,0);
	rep(i,1,n) printf("%d%c",max(f[i],L[i]),i==n?10:32);
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章