[APIO2018] Duathlon 鐵人兩項
無向圖,選三個不同的點,使得存在一條的簡單路徑,求方案數。
題解:
圓方樹,縮點雙。兩個圓點樹上路徑中經過的方點對應點雙中的點都可以作爲。
方點權值爲點雙大小,圓點權值爲-1,那麼可選的的數量就是路徑點權和。統計一個點被多少圓點路徑經過即可,簡單樹上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
經過兩次可以轉化爲不經過,兩個小環形成一個大環。
所以就是求若干簡單環覆蓋圖的方案。同歐拉回路,若有點度數爲奇數無解。
簡單環點不重複,所以寫法有些奇特,每次只找一條邊,找到環之後退棧存環,然後將第一個點入棧繼續找。
當前弧優化以及給邊打標記保證了複雜度是的。
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
點仙人掌(每個點屬於一個簡單環),求到不經過重複邊的路徑條數
如果用圓方樹的話需要考慮的兒子及的父親是不是方點。(因爲是不經過重複邊,所以可以到它的點雙中轉一圈再往上)
而實際上,點仙人掌是可以直接縮的(因爲一個點只在一個環裏面,可以當成邊雙來縮點),然後就只需要看到的路徑上縮點之前大小>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噁心。。
仙人掌隨機點分治,求期望複雜度。
可以先看看樹隨機點分治怎麼做。
根據,可以分開算每個點的貢獻,就是它在點分樹中的深度。設 表示當刪除點 時 在前一時刻保持連通的概率,那麼點 的貢獻等價於。
如果在樹中,,即點 是路徑上第一個刪除的點。
在仙人掌上,由於一個點雙有兩條路徑,所以可以斷掉在某一半的點,仍然是連通的。
這時候 的計算方式大概有兩種思路:
-
方法一:
記表示經過的點雙大小之和(包括單點)。
求出表示在這個點中選擇個點刪去後仍然連通的方案數。
求答案就枚舉在路徑上是第幾個被刪去的點,假設有個點在它之前刪去,那麼概率是答案即爲
求 值的方法也有兩種:
- 轉移就是枚舉當前點雙選某一邊刪去個點,,但是枚舉需要預處理優化複雜度,同時預處理之後轉移時需要去掉同時從轉移來的部分,詳見cz_xuyixuan‘s blog
- 更改定義,值相當於是在求在一些集合裏面選出一些點,可以求出集合的總大小,再在其中選點,每個位置需要加上上一輪的值*-1,減去重複選點的方案。詳見chasedeath’s blog
-
方法二:
記 表示保留 個點使其連通(不能刪)的方案數(即每個點雙的兩邊強制一邊保留、另一邊任意),刪掉 個點中的任意一個都會使其不連通,所以在刪 時保持連通的概率就是 。( 第一個被刪)。轉移就枚舉哪一邊保留,於是答案中計算了強制左邊保留和強制右邊保留的概率之和,所以兩邊都連通的概率被重複計算了,需要令
(略迷)
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
點仙人掌,對每個點獨立算出:將仙人掌刪成一棵樹後離它最遠的點的距離的最小值。
題解:
稍作思考,實際上求的是點仙人掌上最短路的最大值。(貪心,如果從進入環,那麼刪的一定是環上與相對的那條邊,每個點的最短路都能取到)
先把第一個環作爲根拿出來考慮。
先BFS求出每個點往下的最遠距離,此時第一個環的 就已知了。
記第一個環上的點經過橋邊走到的最遠距離爲 ,那麼對於環上的點 ,它的答案就是
是環上 的最短距離,記每個點的答案爲 ,環上的 就可以通過單調隊列順時針逆時針分別轉一圈半算出。
然後要轉移到與根相連的環,假設相接的點是 ,那麼要用 去更新 。 是根據 是否等於 來決定是否取次大值。
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);
}