「SOL」Social Distance(AtCoder)

ARC花式掉分ide


# 題面

數軸上從左到右給出 \(n+1\) 個不重疊的點 \(x_0,x_1,\cdots,x_n\);另有 \(n\) 個動點 \(y_0,y_1,\cdots,y_{n-1}\),其中 \(\forall i,y_i\in(x_i,x_{i+1})\)優化

若每一個 \(y_i\) 落在其取值範圍內的每一個位置機率相等,求「動點兩兩之間的最小距離」的指望,對 \(998244353\) 取模。spa

數據規模 \(1\le n\le20\)debug


# 解析

看到求最小距離可能會先想到 min-max 反演 轉化成最大距離(雖然我連這個都沒有想到),可是這個想法是不可行的。設計

點擊展開/摺疊 此題「min-max反演」的誤區

由題意,設 $n$ 個動點的點集爲 $\mathbb{P}$,至關於求 $$ E\Big(\min_{i,j\in \mathbb{P}}\{|x_i-x_j|\}\Big) $$ code

彷佛由 min-max 反演,結合指望線性性可得下式:get

$$ \sum_{\mathbb{S}\subset\mathbb{P}}(-1)^{|\mathbb{P}|-|\mathbb{S}|}E\Big(\max_{i,j\in\mathbb{S}}\{x_i-x_j\}\Big) $$ input

進而一個點集的最遠點距只與左右兩個點有關,因而能夠快速地計算。可是看一眼 $n$ 的範圍,感受哪裏不對……可是哪裏不對?string

再觀察一下 min-max 反演的式子:it

$$ \min_{x\in \mathbb{S}}\{x\}=\sum_{\mathbb{T}\subset\mathbb{S}}(-1)^{|\mathbb{S}|-|\mathbb{T}|}\max_{x\in\mathbb{T}}\{x\} $$

其實上述作法的誤區在於「集合」,這裏求 min 的集合並非點集 $\mathbb{P}$,而是點對集 $\{(i,j)\mid i,j\in\mathbb{P}\}$。因而枚舉的子集也應該是點對集的子集,而枚舉點對集的子集並不能簡化問題。

轉化成相鄰兩點的最小距離。設相鄰兩點的最小距離爲 \(t\),則下式成立:

\[E(t)=\int_0^{+\infty}P(t\ge i)\rm{d}i \]

點擊摺疊/展開「關於上式」

能夠從離散隨機變量的指望計算式不嚴謹地證實上式,若離散隨機變量 $X$ 知足其取值只多是正數,則

$$ E(X)=\sum_{i=1}iP(X=i) $$

則必有 $E(x)=\sum_{i=1}P(x\ge i)$。從代數角度容易證實,而直觀也比較好理解——$P(x=i)$ 對指望的貢獻係數爲 $i$,將係數 $i$ 均攤到 $1\sim i$ 上,每一個位置分攤 $1$ 的係數。

而如果連續隨機變量(取值必定非負),咱們仍有

$$ E(X)=\int_{0}^{+\infty}iP(X=i)\rm{d}i $$

依然考慮把 $P(X=i)$ 的係數 $i$ 均攤,那麼既然是連續的,就要把係數分攤到區間 $(1,i)$ 上,每 $\rm di$ 分攤 $\rm di$ 的係數,獲得結論。

先不考慮別的,假如給定最小距離 \(d\),應該如何求解 \(P(t\ge d)\)

根據題意,有下式:

\[y_{i+1}-y_i\ge t\Leftrightarrow y_{i+1}-(i+1)t\ge y_i-it \]

那麼設 \(y'_i=y_i-it\),則有 \(y'_{i+1}\ge y_i'\)\(y_i'\in\big(x_i-it,x_{i+1}-it\big)\)

這一步轉化很是重要。

可是因爲變量是連續的,咱們不可能直接決策每一個變量的具體的值,而是決定每一個變量落在哪一個範圍內

\(n\) 對形如 \(x_i-it,x_{i+1}-it\) 的點將 \(y'_i\) 可能存在的區間劃分紅 \(2n-1\) 段。咱們把這 \(2n-1\) 個段依次編號爲 \(0\sim 2n-2\),能夠預先求得 \(y_i'\) 可能落在的段的編號範圍,記爲 \([l_i,r_i]\)

而後咱們就能夠設計一個 DP —— \(f(i,j,k)\) 表示 \(y_i'\) 落在第 \(j\) 段內,此時第 \(j\) 段中已有 \(k\) 個變量。

那麼 \(y_i'\) 要麼和 \(y_{i-1}'\) 落在同一個段內,要麼落在以後的段內。

考慮由 \(f(i-1,j,k)\) 轉移到下一個值:

  • \(y_i'\)\(y_{i-1}'\) 落在同一個段:則先前落在段 \(j\) 內的點把第 \(j\) 段劃分紅 \(k+1\) 小段,而 \(y_i'\) 只能落在最後一小段,所以機率除以 \(k+1\),再乘上 \(y_i'\) 落在第 \(j\) 段的機率 \(P(i,j)\),則

    \[f(i,j,k+1):=f(i,j,k+1)+\frac{f(i-1,j,k)P(i,j)}{k+1} \]

  • \(y_i'\) 落在以後的段中

    \[f(i,j',1):=f(i,j',1)+f(i-1,j,k)P(i,j) \]

令段 \(j\) 的長度爲 \(L_j\)\(y'_i\) 可能的取值範圍長度爲 \(D_i\);那麼 \(P(i,j)=\frac{L_j}{D_i}\)。咱們發現 \(D_i\) 是一個常值,而 \(L_j\) 是關於 \(t\) 的一個一次多項式

第二種轉移能夠前綴和優化,因而能夠在 \(O(n^3R)\) 的複雜度內完成整個DP,\(R\) 是單次轉移的複雜度。

因而能夠推得 \(f(n-1,j,k)\) 是關於 \(t\)\(n\) 次多項式。單詞轉移要麼是對多項式乘以一個常數,要麼是一個一次多項式與另外一個多項式相乘,那麼單次轉移複雜度 \(R=n\)

因此 DP 的複雜度爲 \(O(n^4)\)

再看看怎麼求答案,不可能直接枚舉 \(t\),那麼必定也是 \(t\) 的一個取值範圍一塊兒計算答案。因爲咱們能夠 DP 求得 \(f(n,j,k)\) 關於 \(t\) 的多項式,若是知道 \(t\) 的對應範圍,就能夠經過定積分求解指望。

不難注意到,只要 \(2n\) 個點 \(x_i-it,x_{i+1}-it\) 的大小順序不變,則最後獲得的 \(f(n-1,j,k)\) 關於 \(t\) 的表達式就不會變。

因此對每一種點的順序,都要計算一次 DP。

最後一個問題,一共有多少種點的順序?答案是 \(O(n^2)\)。注意到點的平移速度不一樣,但對於同一個點,其平移速度是常數,因此最初靠後的點 \(A\) 平移超過最初靠前的一個點 \(B\) 後(\(A\) 的平移速度大於 \(B\)),\(A\) 就一直在 \(B\) 前面。只會發生 \(O(n^2)\) 次點的相對位置變化。


# 源代碼

點擊展開/摺疊代碼
/*Lucky_Glass*/
#include<vector>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;

typedef pair<int,int> pii;
const int M=1e6+10,N=42,MOD=998244353;
const double EPS=1e-7;
#define con(type) const type &
inline int add(con(int)a,con(int)b){return a+b>=MOD?a+b-MOD:a+b;}
inline int sub(con(int)a,con(int)b){return a-b<0?a-b+MOD:a-b;}
inline int mul(con(int)a,con(int)b){return int(1ll*a*b%MOD);}
inline int ina_pow(con(int)a,con(int)b){return b?mul(ina_pow(mul(a,a),b>>1),(b&1)?a:1):1;}

int n;
int invi[M],posx[N];
double segl[N],segr[N];

struct Poly{
	vector<int> ary;
	Poly(){}
	Poly(con(int)x0){ary.push_back(x0);}
	Poly(con(int)x0,con(int)x1){ary.push_back(x0),ary.push_back(x1);}
	inline int degr()const{return (int)ary.size();}
	inline void extend(con(int)len){ary.resize(len);}
	inline int& operator [](con(int)i){return ary[i];}
	inline int operator [](con(int)i)const{return ary[i];}
	inline int calc(con(int)x)const{
		int ret=0;
		for(int nowx=1,i=0,ii=degr();i<ii;i++,nowx=mul(nowx,x))
			ret=add(ret,mul(ary[i],nowx));
		return ret;
	}
	inline int calcInt(con(int)le,con(int)ri)const{return sub(calc(ri),calc(le));}
	inline bool operator <(con(Poly)b)const{return degr()<b.degr();}
	friend Poly operator +(con(Poly)a,con(Poly)b){
		Poly c;c.extend(max(a.degr(),b.degr()));
		for(int i=0,ii=a.degr();i<ii;i++) c[i]=add(c[i],a[i]);
		for(int i=0,ii=b.degr();i<ii;i++) c[i]=add(c[i],b[i]);
		return c;
	}
	friend Poly operator -(con(Poly)a,con(Poly)b){
		Poly c;c.extend(max(a.degr(),b.degr()));
		for(int i=0,ii=a.degr();i<ii;i++) c[i]=add(c[i],a[i]);
		for(int i=0,ii=b.degr();i<ii;i++) c[i]=sub(c[i],b[i]);
		return c;
	}
	friend Poly operator *(con(Poly)a,con(Poly)b){
		Poly c;
		c.extend(a.degr()+b.degr()-1);
		for(int i=0,ii=a.degr();i<ii;i++)
			for(int k=0,kk=b.degr();k<kk;k++)
				c[i+k]=add(c[i+k],mul(a[i],b[k]));
		return c;
	}
	Poly getInt()const{
		Poly c;c.extend(degr()+1);
		for(int i=1,ii=c.degr();i<ii;i++)
			c[i]=mul(ary[i-1],invi[i]);
		return c;
	}
	void debug()const{
		for(int i=0,ii=degr();i<ii;i++) printf("%d ",ary[i]);
		printf("\n");
	}
}f[2][N<<1][N],fans;

pii vpos[N<<1];
vector< pair<double,Poly> > vdiv;
vector< pair<double,int> > tim_lis;

int ina_abs(con(int)x){return x<0?-x:x;}
double ina_abs(con(double)x){return x<0?-x:x;}
int sgn(con(double)x){
	if(ina_abs(x)<EPS) return 0;
	return x<0?-1:1;
}
void init(){
	invi[0]=1;
	for(int i=1;i<M;i++) invi[i]=mul(invi[i-1],i);
	invi[M-1]=ina_pow(invi[M-1],MOD-2);
	for(int i=M-2;i;i--){
		int nxt=mul(invi[i+1],i+1);
		invi[i+1]=mul(invi[i+1],invi[i]);
		invi[i]=nxt;
	}
}
//put i in [vdiv_j,vdiv_j+1]
Poly getPossi(con(int)i,con(int)j){
	if(sgn(vdiv[j+1].first-vdiv[j].first)<=0 || sgn(vdiv[j+1].first-segl[i])<=0 || sgn(segr[i]-vdiv[j].first)<=0)
		return Poly(0);
	return (vdiv[j+1].second-vdiv[j].second)*Poly(invi[posx[i+1]-posx[i]]);
}
int main(){
	// freopen("input.in","r",stdin);
	init();
	scanf("%d",&n);
	for(int i=0;i<=n;i++) scanf("%d",&posx[i]);
	for(int i=0;i<n;i++){
		vpos[i<<1]=make_pair(-i,posx[i]);
		vpos[i<<1|1]=make_pair(-i,posx[i+1]);
	}
	tim_lis.push_back(make_pair(0,0));
	tim_lis.push_back(make_pair(1e6,1000000));
	for(int i=0,icap=n<<1;i<icap;i++)
		for(int j=i+1;j<icap;j++){
			if(sgn(vpos[i].first-vpos[j].first)==0) continue;
			double tim=(double)(vpos[j].second-vpos[i].second)/(vpos[i].first-vpos[j].first);
			if(sgn(tim)<=0 || sgn(tim-1e6)>=0) continue;
			int vmod=mul(ina_abs(vpos[j].second-vpos[i].second),invi[ina_abs(vpos[i].first-vpos[j].first)]);
			tim_lis.push_back(make_pair(tim,vmod));
		}
	sort(tim_lis.begin(),tim_lis.end());
	int ans=0;
	for(int t=1,tt=(int)tim_lis.size();t<tt;t++){
		if(sgn(tim_lis[t].first-tim_lis[t-1].first)<=0) continue;
		vdiv.clear();
		double mi=(tim_lis[t].first+tim_lis[t-1].first)/2;
		for(int i=0;i<n;i++){
			segl[i]=posx[i]-i*mi;
			segr[i]=posx[i+1]-i*mi;
			vdiv.push_back(make_pair(segl[i],Poly(posx[i],sub(0,i))));
			vdiv.push_back(make_pair(segr[i],Poly(posx[i+1],sub(0,i))));
		}
		sort(vdiv.begin(),vdiv.end());
		int now=0;
		for(int j=0;j<=2*n;j++)
			for(int k=0;k<=n;k++)
				f[now][j][k].ary.clear();
		f[0][0][0]=Poly(1);
		for(int i=0;i<n;i++){
			for(int j=0;j<=2*n;j++)
				for(int k=0;k<=n;k++)
					f[now^1][j][k].ary.clear();
			for(int j=0,hj=(int)vdiv.size()-1;j<hj;j++)
				for(int k=0;k<n;k++){
					if(!f[now][j][k].degr()) continue;
					f[now^1][j][k+1]=f[now^1][j][k+1]+f[now][j][k]*getPossi(i,j)*Poly(invi[k+1]);
					f[now][j+1][0]=f[now][j+1][0]+f[now][j][k];
				}
			now^=1;
		}
		// printf("done");
		fans.ary.clear();
		for(int j=0;j<=2*n;j++)
			for(int k=0;k<=n;k++)
				fans=fans+f[now][j][k];
		ans=add(ans,fans.getInt().calcInt(tim_lis[t-1].second,tim_lis[t].second));
	}
	printf("%d\n",ans);
	return 0;
}

THE END

Thanks for reading!

結伴而行 這末路並不孤寂
就算放肆 也總有港灣棲息
義無反顧 將身後交於你
縱然爲敵 也不肯生死別離

——《陷落之序》By 著小生zoki

> Link 陷落之序-Bilibili

相關文章
相關標籤/搜索