概率與期望習題總結

liuchanglc發表於2020-10-02

總結

概率題一般正著推

期望題一般倒著推

圖上的問題如果是 \(DAG\) 可以直接轉移

否則可能要用到高斯消元

\(20\) 的資料範圍大概率是裝壓

有些看似無限迴圈的式子其實可以倒著遞推

1、骰子基礎版

題目描述

眾所周知,骰子是一個六面分別刻有一到六點的立方體,每次投擲骰子,從理論上講得到一點到六點的概率都是 \(1/6\)。今有骰子一顆,連續投擲 \(N\)次 ,問點數總和大於等於 \(X\) 的概率是多少?

輸入

僅有一行包含二個用空格隔開的整數,分別表示\(n\)\(x\),其中\(1<=N<=24,0<=x<150\)

輸出

僅有一行包含一個有理數,要求以最簡的形式精確地表達出連續投擲\(N\)次骰子,總點數大於等於\(X\)的概率。

樣例輸入

3 9

樣例輸出

20/27

分析

\(f[i][j]\) 為第 \(i\) 次投擲骰子且總得分為 \(j\) 的方案數

\(f[i][j]+=f[i-1][k],1 \leq j-k \leq 6\)

其實 \(24^6\) 的暴力也可以過

程式碼

#include<cstdio>
const int maxn=30,maxm=200;
typedef long long ll;
ll f[maxn][maxm],tot,ans;
int n,x;
ll gcd(ll aa,ll bb){
	if(bb==0) return aa;
	return gcd(bb,aa%bb);
}
int main(){
	scanf("%d%d",&n,&x);
	f[0][0]=1;
	for(int i=1;i<=n;i++){
		for(int j=1;j<=6;j++){
			for(int k=i;k<=i*6;k++){
				if(k<j) continue;
				f[i][k]+=f[i-1][k-j];
			}
		}
	}
	for(int i=1;i<=n*6;i++){
		tot+=f[n][i];
	}
	for(int i=x;i<=n*6;i++){
		ans+=f[n][i];
	}
	if(ans==0){
		printf("0\n");
	} else if(ans==tot){
		printf("1\n");
	} else{
		ll gys=gcd(ans,tot);
		printf("%lld/%lld\n",ans/gys,tot/gys);
	}
	return 0;
}

2、三角形的概率

題目描述

這是一道數學題。

隨機產生 \(3\) 個一定範圍內的正整數,作為一個三角形的三條邊,求他們能構成一個三角形的概率是多少?

你能證明嗎?

你能用程式碼驗證一下嗎?

輸入格式

輸出格式

一個浮點數,表示答案,保留三位小數。
資料範圍與提示

用作圖法來證明即可,自己算算

分析

其實是一道數學題

如果三條邊 \(a,b,c\) 能構成三角形

必定有 \(a+b<c\)\(c\) 為最長邊

移項會得到 \(\frac{a}{c}+\frac{b}{c}<1\)

實際上求的就是兩個小於 \(1\) 的正數相加大於 \(1\) 的概率

用幾何概型解決

畫一個邊長為為 \(1\) 的正方形,則在對角線上面的部分即為符合條件的,所以概率為\(0.5\)

程式碼

#include<cstdio>
int main(){
    printf("0.500\n");
    return 0;
}

3、聰聰和可可

題目描述

題目傳送門

分析

我們發現貓的走位比較神奇,因此可以提前預處理出貓在位置 \(i\) 且老鼠在位置 \(j\) 時,貓下一步走到的位置 \(nxt[i][j]\)

預處理可以用最短路來實現

如果 \(i\)\(j\) 有一條邊,並且 \(k\)\(i\) 的最短路等於 \(k\)\(j\) 的最短路加 \(1\),則 \(nxt[i][k]=j\)

剩下的過程可以用記憶化搜尋實現

程式碼

#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<queue>
inline int read(){
	int x=0,fh=1;
	char ch=getchar();
	while(ch<'0' || ch>'9'){
		if(ch=='-') fh=-1;
		ch=getchar();
	}
	while(ch>='0' && ch<='9'){
		x=(x<<1)+(x<<3)+(ch^48);
		ch=getchar();
	}
	return x*fh;
}
const int maxn=1e3+5;
int head[maxn],tot=1;
struct asd{
	int to,next;
}b[maxn<<1];
void ad(int aa,int bb){
	b[tot].to=bb;
	b[tot].next=head[aa];
	head[aa]=tot++;
}
int dis[maxn][maxn],nxt[maxn][maxn],n,m,s,t;
bool vis[maxn];
std::queue<int> q;
void spfa(int qd){
	memset(vis,0,sizeof(vis));
	dis[qd][qd]=0;
	q.push(qd);
	vis[qd]=1;
	while(!q.empty()){
		int now=q.front();
		q.pop();
		vis[now]=0;
		for(int i=head[now];i!=-1;i=b[i].next){
			int u=b[i].to;
			if(dis[qd][u]>dis[qd][now]+1){
				dis[qd][u]=dis[qd][now]+1;
				if(!vis[u]){
					q.push(u);
					vis[u]=1;
				}
			}
		}
	}
}
int du[maxn];
bool viss[maxn][maxn];
double f[maxn][maxn];
double dfs(int mao,int shu){
	if(viss[mao][shu]) return f[mao][shu];
	if(mao==shu) return 0;
	int aa=nxt[mao][shu];
	int bb=nxt[aa][shu];
	if(aa==shu || bb==shu) return 1;
	f[mao][shu]=1;
	for(int i=head[shu];i!=-1;i=b[i].next){
		int u=b[i].to;
		f[mao][shu]+=dfs(bb,u)/(du[shu]+1);
	}
	f[mao][shu]+=dfs(bb,shu)/(du[shu]+1);
	viss[mao][shu]=1;
	return f[mao][shu];
}
int main(){
	memset(head,-1,sizeof(head));
	memset(dis,0x3f,sizeof(dis));
	memset(nxt,0x3f,sizeof(nxt));
	n=read(),m=read(),s=read(),t=read();
	for(int i=1;i<=m;i++){
		int aa,bb;
		aa=read(),bb=read();
		ad(aa,bb);
		ad(bb,aa);
		du[aa]++;
		du[bb]++;
	}
	for(int i=1;i<=n;i++) spfa(i);
	for(int i=1;i<=n;i++){
		for(int j=head[i];j!=-1;j=b[j].next){
			int u=b[j].to;
			for(int k=1;k<=n;k++){
				if(dis[i][k]-1==dis[u][k]){
					nxt[i][k]=std::min(nxt[i][k],u);
				}
			}
		}
	}
	printf("%.3f\n",dfs(s,t));
	return 0;
}

4、OSU!

題目描述

題目傳送門

分析

\((x+1)^{3}=x^3+3x^2+3x+1\)

每次多增加的部分是 \(3x^2+3x+1\)

我們再開兩個陣列分別維護 \(x^2\) 的期望和 \(x\) 的期望

\(f1[i]=(f1[i-1]+1)\times p[i]\)

\(f2[i]=(f2[i-1]+2 \times f1[i-1] +1) \times p[i]\)

\(f3[i]=(f3[i-1]+3 \times f2[i-1] +3 \times f1[i-1] +1) \times p[i] +f3[i-1] \times (1-p[i])\)

注意 \(f3[i]\) 還要算上不增加的貢獻

程式碼

#include<cstdio>
const int maxn=1e5+5;
double f1[maxn],f2[maxn],f3[maxn],p[maxn],ans;
int n;
int main(){
	scanf("%d",&n);
	for(int i=1;i<=n;i++){
		scanf("%lf",&p[i]);
	}
	for(int i=1;i<=n;i++){
		f1[i]=(f1[i-1]+1.0)*p[i];
		f2[i]=(f2[i-1]+2.0*f1[i-1]+1)*p[i];
		f3[i]=(f3[i-1]+3.0*f1[i-1]+3.0*f2[i-1]+1.0)*p[i]+(1-p[i])*f3[i-1];
	}
	printf("%.1f\n",f3[n]);
	return 0;
}

5、守衛者的挑戰

題目描述

開啟了黑魔法師 \(Vani\) 的大門,隊員們在迷宮般的路上漫無目的地搜尋著關押\(applepi\)的監獄的所在地。突然,眼前一道亮光閃過。“我,\(Nizem\),是黑魔法聖殿的守衛者。如果你能通過我的挑戰,那麼你可以帶走黑魔法聖殿的地圖……”瞬間,隊員們被傳送到了一個擂臺上,最初身邊有一個容量為\(K\)的包包。

擂臺賽一共有\(N\)項挑戰,各項挑戰依次進行。第\(i\)項挑戰有一個屬性\(a_i\),如果\(a_i>=0\),表示這次挑戰成功後可以再獲得一個容量為\(a_i\)的包包;如果\(a_i=-1\),則表示這次挑戰成功後可以得到一個大小為\(1\) 的地圖殘片。地圖殘片必須裝在包包裡才能帶出擂臺,包包沒有必要全部裝滿,但是隊員們必須把 【獲得的所有的】地圖殘片都帶走(沒有得到的不用考慮,只需要完成所有\(N\)項挑戰後揹包容量足夠容納地圖殘片即可),才能拼出完整的地圖。並且他們至少要挑戰成功\(L\)次才能離開擂臺。

隊員們一籌莫展之時,善良的守衛者\(Nizem\)幫忙預估出了每項挑戰成功的概率,其中第\(i\)項挑戰成功的概率為\(p_i\%\)。現在,請你幫忙預測一下,隊員們能夠帶上他們獲得的地圖殘片離開擂臺的概率。

輸入格式

第一行三個整數\(N,L,K\)

第二行\(N\)個實數,第\(i\)個實數\(p_i\)表示第\(i\)項挑戰成功的百分比。

第三行\(N\)個整數,第\(i\)個整數\(a_i\)表示第\(i\)項挑戰的屬性值.

輸出格式

一個整數,表示所求概率,四捨五入保留\(6\) 位小數。

樣例

樣例輸入1

3 1 0
10 20 30
-1 -1 2

樣例輸出1

0.300000

樣例輸入2

5 1 2
36 44 13 83 63
-1 2 -1 2 1

樣例輸出2

0.980387

資料範圍與提示

若第三項挑戰成功,如果前兩場中某場勝利,隊員們就有空間來容納得到的地圖殘片,如果挑戰失敗,根本就沒有獲得地圖殘片,不用考慮是否能裝下;

若第三項挑戰失敗,如果前兩場有勝利,沒有包來裝地圖殘片,如果前兩場都失敗,不滿足至少挑戰成功次()的要求。因此所求概率就是第三場挑戰獲勝的概率。

對於 \(100\%\) 的資料,保證\(0<=K<=2000\)\(0<=N<=200\)\(-1<=ai<=1000\)\(0<=L<=N\)\(0<=p_i<=100\)

分析

\(f[i][j][k]\) 為前 \(i\) 回合贏了 \(j\) 回合剩餘揹包體積為 \(k\) 的概率

其中轉移過程中體積可以為負數,因為可以暫時不把碎片放到揹包裡

轉移方程為

\(f[i][j][k+a[i]]=f[i-1][j][k+a[i]] \times (1.0-p[i])\)

\(if(j) f[i][j][k+a[i]]=f[i-1][j-1][k] \times p[i]\)

程式碼

#include<cstdio>
#include<algorithm>
#include<cmath>
const int maxn=205;
const int bas=200;
double f[maxn][maxn][maxn<<1],p[maxn];
int n,l,k,a[maxn];
int main(){
   scanf("%d%d%d",&n,&l,&k);
   for(int i=1;i<=n;i++){
   	int aa;
   	scanf("%d",&aa);
   	p[i]=aa/100.0;
   }
   for(int i=1;i<=n;i++){
   	scanf("%d",&a[i]);
   }
   f[0][0][k+bas]=1;
   for(int i=1;i<=n;i++){
   	for(int j=0;j<=i;j++){
   		for(int k=-200;k<=200;k++){
   			f[i][j][std::min(k+bas+a[i],400)]=f[i-1][j][std::min(k+bas+a[i],400)]*(1.0-p[i]);
   			if(j) f[i][j][std::min(k+bas+a[i],400)]+=f[i-1][j-1][std::min(k+bas,400)]*p[i];
   		}
   	}
   }
   double ans=0;
   for(int i=l;i<=n;i++){
   	for(int j=bas;j<=200+bas;j++){
   		ans=ans+f[n][i][j];
   	}
   }
   printf("%.6f\n",ans);
   return 0;
}

6、 Easy

題目描述

某一天\(WJMZBMR\)在打\(osu\)但是他太弱逼了,有些地方完全靠運氣:(

我們來簡化一下這個遊戲的規則

\(n\) 次點選要做,成功了就是\(o\),失敗了就是\(x\),分數是按\(comb\)計算的,連續\(a個comb\)就有a \times a分,\(comb\)就是極大的連續\(o\)。比如ooxxxxooooxxx,分數就是\(2 \times 2+4 \times4=4+16=20\)

\(Sevenkplus\)閒的慌就看他打了一盤,有些地方跟運氣無關要麼是\(o\)要麼是\(x\),有些地方\(o\)或者\(x\)各有\(50\%\)的可能性,用\(?\)號來表示。比如\(oo?xx\)就是一個可能的輸入。

那麼\(WJMZBMR\)這場\(osu\)的期望得分是多少呢?比如\(oo?xx\)的話,\(?\)\(o\)的話就是\(oooxx => 9\),是\(x\)的話就是\(ooxxx => 4\) 期望自然就是\((4+9)/2 =6.5\)

輸入格式

第一行一個整數\(n\),表示點選的個數

接下來一個字串,每個字元都是\(ox?\)中的一個

輸出格式

一行一個浮點數表示答案

四捨五入到小數點後\(4\)

如果害怕精度跪建議用\(long double\)或者\(extended \)

樣例

樣例輸入

4
????

樣例輸出

4.1250

資料範圍與提示

\(n<=300000\)

\(osu很好玩的哦\)

\(WJMZBMR\)技術還行(霧),\(x\)基本上很少呢

分析

\(4\) 題的弱化版

程式碼

#include<bits/stdc++.h>
using namespace std;
typedef double dd;
const int maxn=1e6+5;
dd g[maxn],f[maxn];
char s[maxn];
int main(){
    int n;
    scanf("%d",&n);
    scanf("%s",s+1);
    for(int i=1;i<=n;i++){
		if(s[i]=='o'){
			f[i]=f[i-1]+2*g[i-1]+1;
			g[i]=g[i-1]+1;
		} 
		else if(s[i]=='x'){
			f[i]=f[i-1];
			g[i]=0;
		} else{
			g[i]=(g[i-1]+1)/2.0;
			f[i]=0.5*f[i-1]+0.5*(f[i-1]+2*g[i-1]+1);
		}
	}
	printf("%.4lf\n",f[n]);
    return 0;
}

7、單選錯位

題目描述

題目傳送門

分析

我們考慮 \(gx\) 期望做對的第 \(i+1\) 道題的概率

如果 \(a[i] \geq a[i+1]\) ,則有 \(\frac{a[i+1]}{a[i]}\)的概率落到正確答案的區間內,同時答對的可能性為 \(\frac{1}{a[i+1]}\)

如果 \(a[i] < a[i+1]\) ,則有 \(\frac{a[i]}{a[i+1]}\)的概率落到正確答案的區間內,同時答對的可能性為 \(\frac{1}{a[i]}\)

因此最終的答案為 \(\sum_{i=1}^n \frac{1}{max(a[i],a[i+1])}\)

程式碼

#include<cstdio>
#include<algorithm>
#include<cmath>
const int maxn=10000005;
int n,A,B,C;
long long a[maxn];
double ans;
int main(){
	scanf("%d%d%d%d%lld",&n,&A,&B,&C,&a[1]);
	for(int i=2;i<=n;i++){
		a[i] = ((long long)a[i-1] * A + B) % 100000001;
	}
	for(int i=1;i<=n;i++){
		a[i]=a[i]%C+1;
	}
	a[0]=a[n];
	for(int i=1;i<=n;i++){
		ans+=1.0/(std::max(a[i],a[i-1]));
	}
	printf("%.3f\n",ans);
	return 0;
}

8、洛谷P2059 [JLOI2013]卡牌遊戲

題目描述

題目傳送門

分析

這道題正著不好處理,倒著設比較方便

\(f[i][j]\)\(i\) 人形成的環中,第 \(j\) 個人獲勝的概率

已知 \(f[1][1]=1\)

那麼我們就可以模擬抽走每一張牌,計算出剩下的人在更小的環裡對應的位置

然後用之前已經求得的值更新當前值

程式碼

#include<bits/stdc++.h>
using namespace std;
const int maxn=55;
double f[maxn][maxn];
int a[maxn];
int main(){
    int n,m;
    scanf("%d%d",&n,&m);
    for(int i=1;i<=m;i++){
        scanf("%d",&a[i]);
    }
    f[1][1]=1.0;
    for(int i=2;i<=n;i++){
        for(int j=1;j<=i;j++){
            for(int k=1;k<=m;k++){
                int p=a[k]%i;
                if(p==0) p=i;
                if(p>j) f[i][j]+=f[i-1][i-p+j]/m;
                else f[i][j]+=f[i-1][j-p]/m;
            }
        }
    }
    for(int i=1;i<=n;i++){
        printf("%.2lf%% ",f[n][i]*100.0);
    }
    return 0;
}

9、洛谷 P3232 [HNOI2013]遊走

題目描述

遊走

分析

因為要使獲得總分的期望值最小

所以我們肯定要給經過次數少的邊賦大權值

但是邊的期望經過次數不好直接求

但是我們可以求出點的期望經過次數

邊的期望經過次數就是它所連點的期望經過次數除以點的入度再加和

我們設點 \(u\) 的期望經過次數為 \(f[u]\)

那麼 \(f[u]= \sum_{v-u}f[v]/du[v]\)

其中 \(du[v]\) 是節點 \(v\) 的入度

初始化 \(f[n]=1\)

要注意的是當 \(u=1\) 時,還要把 \(f[u]\) 加上 \(1\)

因為一開始是從 \(1\) 號節點出發的

然後就可以高斯消元求解了

程式碼

#include<cstdio>
#include<cstring>
#include<cmath>
#include<iostream>
#include<queue>
inline int read(){
	int x=0,fh=1;
	char ch=getchar();
	while(ch<'0' || ch>'9'){
		if(ch=='-') fh=-1;
		ch=getchar();
	}
	while(ch>='0' && ch<='9'){
		x=(x<<1)+(x<<3)+(ch^48);
		ch=getchar();
	}
	return x*fh;
}
const int maxn=505,maxm=125005;
const double eqs=1e-10;
int n,m,head[maxn],tot=1;
struct asd{
	int from,to,next;
}b[maxm<<1];
void ad(int aa,int bb){
	b[tot].from=aa;
	b[tot].to=bb;
	b[tot].next=head[aa];
	head[aa]=tot++;
}
int du[maxn];
double mp[maxn][maxn],ans[maxn];
void gsxy(){
	int now=1;
	for(int i=1;i<=n;i++){
		double mmax=0;
		int jl;
		for(int j=now;j<=n;j++){
			if(std::fabs(mmax)<std::fabs(mp[j][i])){
				mmax=mp[j][i];
				jl=j;
			}
		}
		if(std::fabs(mmax)<eqs) continue;
		if(jl!=now) std::swap(mp[jl],mp[now]);
		for(int j=i+1;j<=n+1;j++){
			mp[now][j]/=mp[now][i];
		}
		mp[now][i]=1.0;
		for(int j=now+1;j<=n;j++){
			double cs=mp[j][i];
			for(int k=i;k<=n+1;k++){
				mp[j][k]-=mp[now][k]*cs;
			}
		}
		now++;
	}
	ans[n]=mp[n][n+1];
	for(int i=n-1;i>=1;i--){
		ans[i]=mp[i][n+1];
		for(int j=i+1;j<=n;j++){
			ans[i]-=ans[j]*mp[i][j];
		}
	}
}
std::priority_queue<double> q;
int main(){
	memset(head,-1,sizeof(head));
	n=read(),m=read();
	for(int i=1;i<=m;i++){
		int aa,bb;
		aa=read(),bb=read();
		ad(aa,bb);
		ad(bb,aa);
		du[aa]++;
		du[bb]++;
	}
	for(int i=1;i<=n;i++){
		for(int j=head[i];j!=-1;j=b[j].next){
			int u=b[j].to;
			if(u==n) continue;
			mp[i][u]=-1.0/du[u];
		}
	}
	for(int i=1;i<=n;i++){
		mp[i][i]=1;
	}
	for(int i=1;i<n;i++){
		mp[n][i]=0;
	}
	mp[n][n]=1;
	mp[n][n+1]=1;
	mp[1][n+1]=1;
	gsxy();
	for(int i=1;i<tot;i+=2){
		int aa=b[i].from;
		int bb=b[i].to;
		q.push((double)(ans[aa]/du[aa]*(aa!=n)+ans[bb]/du[bb]*(bb!=n)));
	}
	double nans=0;
	for(int i=1;i<=m;i++){
		if(!q.empty()){
			nans+=q.top()*i;
			q.pop();
		}
	}
	printf("%.3f\n",nans);
	return 0;
}

10、洛谷P4284 [SHOI2014]概率充電器

題目描述

題目傳送門

分析

我們設 \(f[u]\) 為節點 \(u\) 不被點亮的概率

那麼需要滿足 \(u\) 既不會自己點亮自己,也不會被與它相鄰的點點亮

我們可以任選一個點作為根節點,求出節點 \(u\) 只被兒子節點影響的概率

然後再通過換根 \(DP\) 求出以其它節點為根的情況

程式碼

#include<cstdio>
#include<cstring>
const int maxn=1e6+5;
int head[maxn],tot=1,n;
struct asd{
    int to,next;
    double val;
}b[maxn];
void ad(int aa,int bb,int cc){
    b[tot].to=bb;
    b[tot].next=head[aa];
    b[tot].val=(double)cc*0.01;
    head[aa]=tot++;
}
double f[maxn],p[maxn],g[maxn];
void dfs(int now,int fa){
    f[now]=p[now];
    for(int i=head[now];i!=-1;i=b[i].next){
        int u=b[i].to;
        if(u==fa) continue;
        dfs(u,now);
        f[now]*=(1.0-b[i].val+b[i].val*f[u]);
    }
}
void dfs2(int now,int fa,double lat){
    if(now==1){
        g[now]=f[now];
    } else {
        double P=g[fa]/(1.0-lat+lat*f[now]);
        g[now]=f[now]*(1.0-lat+lat*P);
    }
    for(int i=head[now];i!=-1;i=b[i].next){
        int u=b[i].to;
        if(u==fa) continue;
        dfs2(u,now,b[i].val);
    }
}
int main(){
    memset(head,-1,sizeof(head));
    scanf("%d",&n);
    for(int i=1;i<n;i++){
        int aa,bb,cc;
        scanf("%d%d%d",&aa,&bb,&cc);
        ad(aa,bb,cc);
        ad(bb,aa,cc);
    }
    for(int i=1;i<=n;i++){
        int aa;
        scanf("%d",&aa);
        p[i]=1.0-(double)aa*0.01;
    }
    dfs(1,0);
    dfs2(1,0,0);
    double ans=0;
    for(int i=1;i<=n;i++){
        ans+=(1.0-g[i]);
    }
    printf("%.6f\n",ans);
    return 0;
}

11、一個人的遊戲

分析

這道題通過帶換系數的方法解決,思路不錯

詳解

12、Gambling Guide

分析

傳送門

13、換教室

題目描述

題目傳送門

分析

\(f[i][j][0]\) 為截止到第 \(i\) 節課,一共換了 \(j\) 次,其中第 \(i\) 節課沒有申請換教室的耗費體力值的最小期望

\(f[i][j][1]\) 為截止到第 \(i\) 節課,一共換了 \(j\) 次,其中第 \(i\) 節課申請換教室的耗費體力值的最小期望

狀態轉移就很簡單了

程式碼

#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
const int maxn=2e3+5;
int f[maxn][maxn];
int n,m,e,v,c[maxn],d[maxn];
typedef double db;
db k[maxn];
db dp[maxn][maxn][2];
int main(){
	memset(f,0x3f,sizeof(f));
	scanf("%d%d%d%d",&n,&m,&v,&e);
	for(int i=1;i<=n;i++){
		scanf("%d",&c[i]);
	}
	for(int i=1;i<=n;i++){
		scanf("%d",&d[i]);
	}
	for(int i=1;i<=n;i++){
		scanf("%lf",&k[i]);
	}
	for(int i=1;i<=v;i++){
		f[i][i]=0;
	}
	for(int i=0;i<=n;i++){
		for(int j=0;j<=m;j++){
			dp[i][j][0]=dp[i][j][1]=1e18;
		}
	}
	for(int i=1;i<=e;i++){
		int aa,bb,cc;
		scanf("%d%d%d",&aa,&bb,&cc);
		if(f[aa][bb]>cc) f[aa][bb]=f[bb][aa]=cc;
	}
	for(int kk=1;kk<=v;kk++){
		for(int i=1;i<=v;i++){
			for(int j=1;j<=v;j++){
				f[i][j]=std::min(f[i][j],f[i][kk]+f[kk][j]);
			}
		}
	}
	dp[1][1][1]=dp[1][0][0]=0;
	for(int i=2;i<=n;i++){
		for(int j=0;j<=std::min(i,m);j++){
			if(j)dp[i][j][1]=std::min(dp[i][j][1],dp[i-1][j-1][0]+k[i]*f[c[i-1]][d[i]]+(1-k[i])*f[c[i-1]][c[i]]);
			if(j)dp[i][j][1]=std::min(dp[i][j][1],dp[i-1][j-1][1]+k[i-1]*k[i]*f[d[i-1]][d[i]]+(1-k[i])*(1-k[i-1])*f[c[i-1]][c[i]]+k[i-1]*(1-k[i])*f[d[i-1]][c[i]]+k[i]*(1-k[i-1])*f[d[i]][c[i-1]]);
			dp[i][j][0]=std::min(dp[i][j][0],dp[i-1][j][0]+f[c[i]][c[i-1]]);
			dp[i][j][0]=std::min(dp[i][j][0],dp[i-1][j][1]+k[i-1]*f[d[i-1]][c[i]]+(1-k[i-1])*f[c[i]][c[i-1]]);
		}
	}
	double ans=1e18;
	for(int i=0;i<=m;i++){
		ans=std::min(ans,dp[n][i][0]);
		ans=std::min(ans,dp[n][i][1]);
	}
	printf("%.2f\n",ans);
	return 0;
}

14、跳一跳

題目描述

題目傳送門

分析

\(f[i]\) 為當前在 \(i\) 點,到達 \(n\) 點的期望時間

轉移很簡單,但是注意要把變數滾一下

因為本題卡空間

程式碼

#include<cstdio>
const int maxn=1e7+5;
const int mod=1e9+7;
int ny[maxn],n,ans,sum;
int main(){
    scanf("%d",&n);
    ny[1]=1;
    for(int i=2;i<=n;i++){
        ny[i]=1LL*(mod-mod/i)*ny[mod%i]%mod;
    }
    ans=0;
    for(int i=n-1;i>=1;i--){
        ans=1LL*(sum+n-i+1LL)*ny[n-i]%mod;
        sum=(sum+ans)%mod;
    }
    printf("%d\n",ans);
    return 0;
}

15、分手是祝願

題目描述

題目描述

分析

其實這道題不是很難

但是題面往往具有迷惑性

我們可以發現每個按鍵都不可能被其他按鍵的組合鍵替代

於是我們實際上可以從大到小掃一遍,碰到亮的就按一遍,這樣的話我們就可以找到所有必須要按的鍵

我們設 \(f[i]\) 為從第 \(i\) 個需要的鍵按到第 \(i-1\) 個需要的鍵期望按的次數

那麼就有

\(f[i]=\frac{i}{n}+\frac{n-i}{n} \times (f[i]+f[i+1]+1)\)

移項化簡就可以了

程式碼

#include<cstdio>
#include<vector>
const int maxn=1e5+5;
const int mod=100003;
inline int read(){
	int x=0,fh=1;
	char ch=getchar();
	while(ch<'0' || ch>'9'){
		if(ch=='-') fh=-1;
		ch=getchar();
	}
	while(ch>='0' && ch<='9'){
		x=(x<<1)+(x<<3)+(ch^48);
		ch=getchar();
	}
	return x*fh;
}
std::vector<int> g[maxn];
int n,a[maxn],f[maxn],ny[maxn],k;
void pre(int now){
	for(int i=1;i<=now;i++){
		int mmax=now/i;
		for(int j=1;j<=mmax;j++){
			g[i*j].push_back(i);
		}
	}
}
int main(){
	n=read(),k=read();
	for(int i=1;i<=n;i++){
		a[i]=read();
	}
	pre(n);
	ny[1]=1;
	for(int i=2;i<maxn;i++){
		ny[i]=1LL*(mod-mod/i)*ny[mod%i]%mod;
	}
	int cs=0;
	for(int i=n;i>=1;i--){
		if(a[i]==1){
			for(int j=0;j<g[i].size();j++){
				a[g[i][j]]^=1;
			}
			cs++;
		}
	}
	f[n]=0;
	for(int i=n;i>=1;i--){
		f[i]=1LL*(n+1LL*(n-i)*f[i+1]%mod)%mod*ny[i]%mod;
	}
	int ans=0;
	if(k>=cs){
		ans=cs;
	} else {
		for(int i=k+1;i<=cs;i++){
			ans=(ans+f[i])%mod;
		}
		ans=(ans+k)%mod;
	}
	for(int i=1;i<=n;i++){
		ans=1LL*ans*i%mod;
	}
	printf("%d\n",ans);
	return 0;
}

相關文章