更新中...
day1-T3-random
理解題意,打出第一個暴力
首先需要知道,隨機生成的樹(如果不特別說明隨機方法,就是指在所有\(n^{n-2}\)棵樹里隨機),最大深度期望是\(O(\sqrt{n})\)的。
看本題。首先,期望是嚇唬你的,因為期望乘上階乘,其實就是求和。於是我們要求【所有排列下【操作后所有點的權值和】的和】。因此暴力做法就是枚舉一個排列,模擬操作,時間復雜度\(O(n!\cdot \text{poly}(n))\)。值得一提的是,操作時,枚舉子樹里所有的點,可以通過\(\text{dfs}\)序實現不重不漏的枚舉,比較優美。
深度對應系數,從樹上問題到序列問題
繼續優化。首先發現,一個點\(u\)的權值\(a_u\),在完成所有操作后,能貢獻到另一個點\(v\)的權值上,當且僅當\(u\)是\(v\)的祖先(其實這里強調的是必要性。充分性你可以理解為貢獻為\(0\)也是貢獻)。於是我們枚舉每一對這樣的\(u,v\)。考慮\(u,v\)的距離,也就是深度差。發現當深度差相等時,\(u\)對\(v\)貢獻的系數是一樣的!
具體來說,假設\(u,v\)之間的路徑上,共有\(d\)個點(含\(u\)和\(v\))。我們預處理出一個系數\(f_d\)。則答案就是:
其中\({n\choose d}\times (n-d)!\)是因為,除\(u,v\)路徑以外的點,不會影響\(u\)對\(v\)的貢獻,所以可以任意排列。
考慮預處理出\(f_d\)。先看是什么,再看怎么求。\(f_d\)是什么呢?可以考慮一個長度為\(d\)的序列\(x_1,x_2,\dots ,x_d\),初始時等於\(1,0,0,\dots 0\)。我們枚舉\(d!\)個排列,對每個排列\(p_1,p_2,\dots ,p_d\),依次對每個位置\(p_i\),把\(x_{p_i},x_{p_i+1},\dots ,x_{d}\)加上\(x_{p_i}\)。把每個排列下,最后得到的\(x_d\)的值加起來,就是\(f_d\)了。
也就是說,朴素求\(f_d\)的復雜度是\(d!\)的。因為\(d\leq \text{maxdep}=O(\sqrt{n})\),所以暴力預處理的復雜度\(O((\sqrt{n})!\cdot \text{poly}(\sqrt{n}))\)。也可以改成狀壓DP。都能比\(n!\)純暴力多拿一些分。
遞推求出系數
考慮遞推求\(f_{1\dots \text{maxdep}}\)。特別地,我們定義\(f_1=1\)。可能有人會認為\(f_1=2\)(因為題目說了,子樹加的時候包括自己)。但遞推時,我們只考慮第一個點對最后一個點貢獻了多少,既然是貢獻就不算它原本自己有的一份。不過這樣在上面求答案的那個式子里,可能\(u=v\)時就要特判一下了。
從\(f_{d-1}\)遞推到\(f_d\)時,考慮在\(1\)之后被操作到的點有幾個。也就是說,假設排列里\(p_t=1\),那么在\(1\)之后被操作到的點就有\(d-t\)個,設\(i=d-t\),枚舉\(i\) (\(0\leq i<d\))。得到如下遞推式:
解釋一下這個式子:
顯然,在操作到\(1\)之前,前面的操作都是無效的。因為它們的初始值都是\(0\)。這些東西可以任意排列,並且要選出哪幾個位置排在\(1\)之前,於是乘上系數\({d-1\choose i}(d-i-1)!\)。
接下來只考慮后\(i\)個被操作的點,它們的不同排列順序下,產生的貢獻之和。
操作到\(1\)時,會令\(x_2\dots x_d\)全部變成\(1\)。那么在所有情況下,\(1\)對\(x_d\)會先有一個\(i!\)的貢獻(因為后面的數有這么多種排列方式,每次的貢獻都是\(1\))。
接下來,會依次對\(i\)個數進行操作,且它們初始值都是\(1\)。可以再用一次算貢獻的思想,分別計算這\(i\)個\(1\)的貢獻,那么就是\(f_1+f_2+\dots +f_i\)。其中 \(f_j\) 是離 \(x_d\) 第 \(j\) 近的、未被操作過的位置。對於每個 \(j\),\(f_j\) 里已經包含了它和它后面的數(以位置來說的“后面”)的所有排列方式,但它前面的、未被操作過的數,可以按任意順序操作(\((i - j)!\) 種操作順序)。並且前后兩部分之間互不影響,各自確定了順序后可以穿插在一起(\({i\choose j}\) 種穿插情況)。
朴素地按這個式子遞推,復雜度是\(O(\text{maxdep}^3)\approx O(n\sqrt{n})\)。我沒試過,不確定能不能AC。不過我們很容易將它優化到\(O(n)\)。
定義:
則:
每算出來一個\(f_d\)就更新一下它對\(g_{d\dots n}\)的貢獻即可。
時間復雜度\(O(n)\)。
參考代碼:
//problem:ZR1534
#include <bits/stdc++.h>
using namespace std;
#define pb push_back
#define mk make_pair
#define lob lower_bound
#define upb upper_bound
#define fi first
#define se second
#define SZ(x) ((int)(x).size())
typedef unsigned int uint;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int,int> pii;
template<typename T>inline void ckmax(T& x,T y){x=(y>x?y:x);}
template<typename T>inline void ckmin(T& x,T y){x=(y<x?y:x);}
const int MAXN=1e5;
const int MOD=1e9+7;
inline int mod1(int x){return x<MOD?x:x-MOD;}
inline int mod2(int x){return x<0?x+MOD:x;}
inline void add(int& x,int y){x=mod1(x+y);}
inline void sub(int& x,int y){x=mod2(x-y);}
inline int pow_mod(int x,int i){int y=1;while(i){if(i&1)y=(ll)y*x%MOD;x=(ll)x*x%MOD;i>>=1;}return y;}
int fac[MAXN+5],ifac[MAXN+5];
inline int comb(int n,int k){
if(n<k)return 0;
return (ll)fac[n]*ifac[k]%MOD*ifac[n-k]%MOD;
}
void facinit(int lim=MAXN){
fac[0]=1;
for(int i=1;i<=lim;++i)fac[i]=(ll)fac[i-1]*i%MOD;
ifac[lim]=pow_mod(fac[lim],MOD-2);
for(int i=lim-1;i>=0;--i)ifac[i]=(ll)ifac[i+1]*(i+1)%MOD;
}
int n,a[MAXN+5];
struct EDGE{int nxt,to;}edge[MAXN*2+5];
int head[MAXN+5],tot;
inline void add_edge(int u,int v){edge[++tot].nxt=head[u],edge[tot].to=v,head[u]=tot;}
int dep[MAXN+5],dfn[MAXN+5],ofn[MAXN+5],rev[MAXN+5],cnt_dfn;
void dfs(int u,int fa){
dfn[u]=++cnt_dfn;
rev[cnt_dfn]=u;
for(int i=head[u];i;i=edge[i].nxt){
int v=edge[i].to;
if(v==fa) continue;
dep[v]=dep[u]+1;
dfs(v,u);
}
ofn[u]=cnt_dfn;
}
int f[MAXN+5],g[MAXN+5];
int main() {
cin>>n;
facinit(n);
for(int i=1;i<n;++i){
int u,v; cin>>u>>v;
add_edge(u,v);
add_edge(v,u);
}
for(int i=1;i<=n;++i)
cin>>a[i];
dfs(1,0);
int maxd=0;
for(int i=1;i<=n;++i)
ckmax(maxd,dep[i]+1);
f[1]=1;
for(int i=1;i<maxd;++i){
g[i]=(ll)f[1]*comb(i,1)%MOD*fac[i-1]%MOD;
}
for(int d=2;d<=maxd;++d){
for(int i=0;i<d;++i){
f[d]=((ll)f[d] + (ll)comb(d-1,i)%MOD*fac[d-i-1]%MOD*(fac[i]+g[i])) %MOD;
}
for(int i=d;i<maxd;++i){
g[i]=((ll)g[i] + (ll)f[d]*comb(i,d)%MOD*fac[i-d])%MOD;
}
}
//for(int i=1;i<=maxd;++i)cout<<f[i]<<" ";cout<<endl;
/*
// 暴力
static int ff[100],p[100],val[100];
ff[1]=1;
for(int d=2;d<=maxd;++d){
for(int i=1;i<=d;++i)p[i]=i;
do{
for(int i=2;i<=d;++i)val[i]=0;
val[1]=1;
for(int i=1;i<=d;++i){
int u=p[i];
int x=val[u];
for(int j=u;j<=d;++j){
add(val[j],x);
}
}
add(ff[d],val[d]);
}while(next_permutation(p+1,p+d+1));
}
for(int i=1;i<=maxd;++i)cout<<ff[i]<<" ";cout<<endl;
*/
for(int i=1;i<=maxd;++i)f[i]=(ll)f[i]*comb(n,i)%MOD*fac[n-i]%MOD;
int ans=0;
for(int u=1;u<=n;++u){
int sum=2*fac[n]%MOD;
for(int i=dfn[u]+1;i<=ofn[u];++i){
int v=rev[i];
add(sum,f[dep[v]-dep[u]+1]);
}
ans=((ll)ans + (ll)sum*a[u])%MOD;
}
cout<<ans<<endl;
return 0;
}
day3-T1-cut
結論:\(m>0\) 時,答案一定是 \(\text{YES}\)。證明如下:
將每條邊定向:從編號較小的點,連向編號較大的點。那么會得到一個 DAG。
按拓撲序確定每個點的顏色(\(0\) 或 \(1\),分別代表工業城市/農業城市)。初始時,我們讓入度為 \(0\) 的點,顏色全部為 \(0\)。
考慮其它任意一點 \(u\) 的入邊。假設有 \(x\) 條邊來自 \(0\), \(y\) 條邊來自 \(1\):
- 如果 \(x\geq y\),令 \(u\) 的顏色等於 \(1\);
- 如果 \(x<y\),令 \(u\) 的顏色等於 \(0\)。
那么可以保證,任意一點 \(u\) 的所有入邊里,兩端顏色不同的邊數 \(\geq\) 兩端顏色相同的邊數。當且僅當 \(x=y\) 時取等號。
通過考慮每個點的入邊,我們不重不漏地考慮到了所有的邊。因此,現在可以保證整張圖里,兩端顏色不同的邊數 \(\geq\) 兩端顏色相同的邊數。並且只要存在一個點,它的 \(x\neq y\),整張圖就能取大於號。
因為初始時,我們讓入度為 \(0\) 的點全部染了相同的顏色,所以發現所有“第二層”的點,一定是:\(x>0,y=0\)。因此存在 \(x>y\) 的點,即整張圖里 兩端顏色不同的邊數 \(>\) 兩端顏色相同的邊數。
參考代碼:
#include <bits/stdc++.h>
int main() {
int n, AMD;
std :: cin >> n >> AMD;
return std :: cout << (AMD ? "Yes" : "No") << std :: endl, 0;
}