骰子基礎版
高中數學題,我是暴力做的。
最多甩24次骰子,可以發現 \(6^{24}\) 沒有爆\(unsigned\ long\ long\),直接快速冪搞定分母(基本事件總數)
然后考慮>X點的方案,我是稍微轉移下,定義dp[i][j]表示前i個骰子總點數是j的方案數,直接轉移搞搞即可,分子也搞定了。
求個gcd約個分,就ok了
三角形的概率
高中數學題,結論題。
答案是:
puts("0.500")
證明看這里神犇LZZ的博客
聰聰和可可
新加的題,是個期望dp,轉移和預處理比較復雜,但是不難理解。
定義dp[i][j]表示貓在i,鼠在j的期望時間,運用dfs記憶化搜索的形式dp。
預處理一堆:
- next[i][j]表示貓在i,鼠在j的貓下一步會走哪(根據題目定義,貓會向離鼠近的編號最小的走,可以預處理)
- dis[i][j]表示i到j的距離,每個點bfs一遍\(n^2\)求出
轉移(記得期望dp要倒着推):
\(dp[i][j]=dp[next[next[i][j]][j]][j]*\frac{1}{degree[j]+1}+\sum\limits_{k}^{k是j的臨接點} dp[next[next[i][j]][j]][k]*\frac{1}{degree[j]+1}\)
這里next兩次是因為貓一次走兩步,degree[j]+1是因為從j除了到他的臨接點,還可以不動,所以老鼠這一步走的總方案數是degree[j]+1。
dp數組的初始化:對於距離為1、2的兩點i,j貓吃鼠時間需要1,距離為0的貓吃鼠時間需要0;
代碼:
#include<bits/stdc++.h>
using namespace std;
const int maxn=2e3+10;
inline int read(){
int x=0;char ch=getchar();
while(ch<'0'||ch>'9')ch=getchar();
while(ch>='0'&&ch<='9'){
x=x*10+ch-'0';
ch=getchar();
}#include<bits/stdc++.h>
using namespace std;
const int maxn=2e3+10;
inline int read(){
int x=0;char ch=getchar();
while(ch<'0'||ch>'9')ch=getchar();
while(ch>='0'&&ch<='9'){
x=x*10+ch-'0';
ch=getchar();
}
return x;
}
int next[maxn][maxn],dis[maxn][maxn],degree[maxn];
int n,m,cat,mouse;
double dp[maxn][maxn];
struct E{
int to,next;
}edge[maxn];
int head[maxn],tot;
void add(int from,int to){
edge[++tot].to=to;
edge[tot].next=head[from];
head[from]=tot;
}
queue<int> q;
void bfs(int s){
for(register int i=head[s];i;i=edge[i].next){
int v=edge[i].to;
dp[s][v]=1;
for(register int j=head[v];j;j=edge[j].next){
int w=edge[j].to;
if(w==s)continue;
dp[s][w]=1;
}
}
q.push(s);dis[s][s]=0;dp[s][s]=0;
while(!q.empty()){
int u=q.front();q.pop();
for(register int i=head[u];i;i=edge[i].next){
int v=edge[i].to;
if(dis[s][v]>dis[s][u]+1){
dis[s][v]=dis[s][u]+1;
q.push(v);
}
}
}
}
void init(int from,int to){
int Min=0x7fffffff;
for(register int i=head[from];i;i=edge[i].next){
int v=edge[i].to;
if(dis[v][to]==Min){
if(v<next[from][to]){
next[from][to]=v;
}
}
if(dis[v][to]<Min){
next[from][to]=v;
Min=dis[v][to];
}
}
}
double dfs(int u,int v){
if(dp[u][v]!=0||u==v){
return dp[u][v];
}
int now=next[next[u][v]][v];
dp[u][v]+=(dfs(now,v)+1)/(degree[v]+1);
for(register int i=head[v];i;i=edge[i].next){
dp[u][v]+=(dfs(now,edge[i].to)+1)/(degree[v]+1);
}
return dp[u][v];
}
int main(){
freopen("cchkk.in","r",stdin);
freopen("cchkk.out","w",stdout);
n=read();m=read();cat=read();mouse=read();
for(register int i=1;i<=m;i++){
int from=read(),to=read();
add(from,to);add(to,from);
degree[from]++;
degree[to]++;
}
memset(dis,0x3f,sizeof(dis));
memset(next,0x3f,sizeof(next));
for(register int i=1;i<=n;i++){
bfs(i);
}
for(register int i=1;i<=n;i++){
for(register int j=1;j<=n;j++){
init(i,j);
}
}
printf("%.3lf\n",dfs(cat,mouse));
return 0;
}
return x;
}
int next[maxn][maxn],dis[maxn][maxn],degree[maxn];
int n,m,cat,mouse;
double dp[maxn][maxn];
struct E{
int to,next;
}edge[maxn];
int head[maxn],tot;
void add(int from,int to){
edge[++tot].to=to;
edge[tot].next=head[from];
head[from]=tot;
}
queue<int> q;
void bfs(int s){
for(register int i=head[s];i;i=edge[i].next){
int v=edge[i].to;
dp[s][v]=1;
for(register int j=head[v];j;j=edge[j].next){
int w=edge[j].to;
if(w==s)continue;
dp[s][w]=1;
}
}
q.push(s);dis[s][s]=0;dp[s][s]=0;
while(!q.empty()){
int u=q.front();q.pop();
for(register int i=head[u];i;i=edge[i].next){
int v=edge[i].to;
if(dis[s][v]>dis[s][u]+1){
dis[s][v]=dis[s][u]+1;
q.push(v);
}
}
}
}
void init(int from,int to){
int Min=0x7fffffff;
for(register int i=head[from];i;i=edge[i].next){
int v=edge[i].to;
if(dis[v][to]==Min){
if(v<next[from][to]){
next[from][to]=v;
}
}
if(dis[v][to]<Min){
next[from][to]=v;
Min=dis[v][to];
}
}
}
double dfs(int u,int v){
if(dp[u][v]!=0||u==v){
return dp[u][v];
}
int now=next[next[u][v]][v];
dp[u][v]+=(dfs(now,v)+1)/(degree[v]+1);
for(register int i=head[v];i;i=edge[i].next){
dp[u][v]+=(dfs(now,edge[i].to)+1)/(degree[v]+1);
}
return dp[u][v];
}
int main(){
freopen("cchkk.in","r",stdin);
freopen("cchkk.out","w",stdout);
n=read();m=read();cat=read();mouse=read();
for(register int i=1;i<=m;i++){
int from=read(),to=read();
add(from,to);add(to,from);
degree[from]++;
degree[to]++;
}
memset(dis,0x3f,sizeof(dis));
memset(next,0x3f,sizeof(next));
for(register int i=1;i<=n;i++){
bfs(i);
}
for(register int i=1;i<=n;i++){
for(register int j=1;j<=n;j++){
init(i,j);
}
}
printf("%.3lf\n",dfs(cat,mouse));
return 0;
}
OSU!
一道期望dp題,有思考點。
期望是線性的,上數學課的時候,老師這么跟我們說,我們真正理解這句話了嗎,我就沒有
公式:\(E(kx+b)=k*E[x]+b\)
我們還知道:期望的平方不等於平方的期望
公式:\(E(x^2)\neq E^2(x)\)
但是:\(E^2(x+1)=E^2(x)+2*E(x)+1\)
別問我證明,但他確實是對的
這道題讓求后綴1長度3次方的期望和(不是異或3啊)
轉化成立方和公式,然后就好求了。
注意我們的E(x)要定義為“后綴1長度為x的期望”,而不是“后綴1長度為x的期望和”,那個不好轉移。
轉移方程就是:
\(E(i)=(E(i-1)+1)*p[i]\)
\(E2(i)=(E2[i-1]+2*E[i-1]+1)*p[i]\)
\(dp[i]=(dp[i-1]+3*E2[i-1]+3*E[i-1]+1)*p[i]+(1-p[i])*dp[i-1]\)
這個三次方的概率跟之前的不一樣是因為它表示的是前i的期望和,是題目中所求。
比較簡單就不放代碼了。
守衛者的挑戰
語文閱讀理解題
本題的題意較難理解,是本題的難點。
一句話題意:有n項挑戰,問通過大於等於L項挑戰而且所得背包容量大小大於地圖數量的概率
轉移類似背包,dp[i][j][k]表示第i輪,已經贏了k輪當前背包剩余容量為j的概率(這里j是可以為負的),直接轉移,沒啥好說的。
記得,第二維可能是負數,需要加一個值保證為正,類似之前考得某個大模擬。
代碼:
#include<bits/stdc++.h>
using namespace std;
const int maxn=2e2+1;
double dp[maxn][2*maxn][maxn],p[maxn];
int mode[maxn];
int main(){
int n,l,k;
scanf("%d%d%d",&n,&l,&k);
for(int i=1;i<=n;i++){
int x;
scanf("%d",&x);
p[i]=1.0*x/100;
}
for(int i=1;i<=n;i++){
scanf("%d",&mode[i]);
}
dp[0][k+200][0]=1;
for(int i=1;i<=n;i++){
for(int j=-200;j<=200;j++){
for(int k=0;k<=i;k++){
if(mode[i]>=0){
if(j-mode[i]>=-200)dp[i][j+200][k]+=p[i]*dp[i-1][j+200-mode[i]][k-1];
}else{
if(j+200)dp[i][j+200][k]+=p[i]*dp[i-1][j+200+1][k-1];
}
dp[i][j+200][k]+=(1-p[i])*dp[i-1][j+200][k];
}
}
}
double ans=0;
for(int j=0;j<=200;j++){
for(int k=l;k<=n;k++){
ans+=dp[n][j+200][k];
}
}
printf("%lf",ans);
return 0;
}
Easy
和OSU!是兄弟題,在轉移的時候注意下:如果是'o'的話,相當於一個概率為100%的塊,'x'的話,相當於一個概率為0%的塊,然后這道題是維護后綴1長度平方,比OSU!還簡單些。
單選錯位
有點i思維但是還是很裸的期望dp。
這道題主要難處理的是每一步的概率,我們可以這樣想。
這道題本來有1~a個選項,你答的是1~b中的答案,那答對的概率就是\(\frac{min(a,b)}{a*b}\),通過公式\(P(A)= \frac{事件A發生的情況}{基本事件總數}\)得出
然后就是裸的dp了,這道題並沒有嚴格的要求從前往后推還是從后往前推,正推即可。
太裸了不放代碼了
卡牌游戲
寫完這道題感覺對約瑟夫問題的遞推公式有了更深的理解。
這道題其實是一個概率+約瑟夫問題,在一般約瑟夫中,我們每輪隔着幾個人干掉一個人是確定的,這道題確實有概率的。
沿用約瑟夫問題的想法,倒着推。
定義dp[i][j]還剩i個人,1做庄,第j個人的勝率
只有一個人的時候,dp[1][1]=1;他勝利的概率是100%。
然后轉移到下一個,枚舉上一個轉移過來的牌是啥,推出當前這個j是下一輪的哪個人,再由它的概率乘上這張牌的概率,累加到j的答案里面。
大概是這樣的:
#include<bits/stdc++.h>
using namespace std;
double dp[61][61];
int a[60],n,m;
int getd(int i,int k){
k%=i;
return 1+k;
}
int main(){
scanf("%d%d",&n,&m);
for(register int i=1;i<=m;i++)scanf("%d",&a[i]);
dp[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 d=a[k]%i;
if(d==0)d=i;
//這里的d就是當前的數字a[k]在i那輪是選中了誰干掉,他的下一號重新標記為1號,多手%一下,可以自己推出來剩i個人,從1開始順時針第a[k]個是誰。
//然后就能推出來j的上一輪,a[k]步之前,它的編號是啥,然后就可以轉移了
//約瑟夫問題的每輪重新編號的思想這里需要運用的很純熟。
if(d>j)dp[i][j]+=dp[i-1][i-(d-j)]/m;
if(d<j)dp[i][j]+=dp[i-1][j-d]/m;
}
}
}
for(int i=1;i<=n;i++)
printf("%.2lf%% ",dp[n][i]*100);
return 0;
}
換教室
期望開始跟圖論結合了,vvv。
這道題並不是太難的圖論期望題,還是朴素的期望dp,更像dp一些,狀態定義和轉移比較難。
先考慮沒有概率會怎樣。
定義dp[i][j][0/1]第i段課,已經換了j節課,當前這節課換沒換。
轉移就是:
\(dp[i][j][0]=min(dp[i-1][j][1]+d[i-1]+c[i],dp[i-1][j][0]+c[i-1]+c[i]);\)
\(dp[i][j][1]=min(dp[i-1][j-1][1]+d[i-1]+d[i],dp[i-1][j-1][0]+c[i-1]+d[i]);\)
考慮加入了概率會怎樣。
每次換課都有可能成功或不成。
所以我們稍微換一下dp定義:dp[i][j][0/1]第i段課,已經申請換了j節課,當前這節課在哪上的。
每次涉及到換課時候,就直接分概率算,每次申請換課都有可能成或不成。
\(dp[i][j][0]=min(dp[i-1][j][0]+c[i-1]+c[i],dp[i-1][j][1]+p[i-1]*(d[i-1]+c[i])+(1-p[i-1])*(c[i-1]+c[i]));\)
顯然如果不換課,通過率是100%,那就沒概率啥事,要換課,就有可能不成功,所以這么轉移。
dp[i][j][1]的轉移類似。
\(dp[i][j][1]=min(dp[i][j][1],min(dp[i-1][j-1][0]+p[i]*dis[c[i-1]][d[i]]+(1-p[i])*dis[c[i-1]][c[i]],dp[i-1][j-1][1]+p[i]*p[i-1]*dis[d[i-1]][d[i]]+(1-p[i-1])*(1-p[i])*dis[c[i-1]][c[i]]+(1-p[i])*p[i-1]*dis[d[i-1]][c[i]]+(1-p[i-1])*p[i]*dis[c[i-1]][d[i]]));\)
就是長了一些,類似一些ex的一堆轉移柿子的dp題,但是不難理解
#include<bits/stdc++.h>
using namespace std;
inline int read(){
int x=0;char ch=getchar();
while(ch<'0'||ch>'9')ch=getchar();
while(ch>='0'&&ch<='9'){
x=x*10+ch-'0';
ch=getchar();
}
return x;
}
const int maxn=4.5e3l+10;
struct E{
int to,val,next;
}edge[5*maxn];
int head[maxn],tot;
void add(int from,int to,int val){
edge[++tot].to=to;
edge[tot].val=val;
edge[tot].next=head[from];
head[from]=tot;
}
double dp[maxn][maxn][2],p[maxn];
int n,m,v,e,c[maxn],d[maxn];
int dis[maxn][maxn],vis[maxn],val[maxn][maxn];
queue<int> q;
void spfa(int s){
dis[s][s]=0;
memset(vis,0,sizeof(vis));q.push(s);
while(!q.empty()){
int u=q.front();q.pop();vis[u]=0;
for(int i=head[u];i;i=edge[i].next){
int v=edge[i].to;
if(dis[s][v]>dis[s][u]+edge[i].val){
dis[s][v]=dis[s][u]+edge[i].val;
if(!vis[v]){
q.push(v);
vis[v]=1;
}
}
}
}
}
int main(){
n=read();m=read();v=read();e=read();
for(int i=1;i<=n;i++)c[i]=read();
for(int i=1;i<=n;i++)d[i]=read();
for(int i=1;i<=n;i++)scanf("%lf",&p[i]);memset(val,0x3f,sizeof(val));
memset(dis,0x3f,sizeof(dis));
for(int i=1;i<=e;i++){
int from=read(),to=read(),val=read();
add(from,to,val);add(to,from,val);
dis[from][to]=dis[to][from]=min(val,dis[from][to]);
}
for(int i=1;i<=v;i++){
dis[i][i]=0;
}
for(int i=1;i<=v;i++){
for(int j=1;j<=v;j++){
for(int k=1;k<=v;k++){
dis[j][k]=min(dis[j][k],dis[j][i]+dis[i][k]);
}
}
}
for(int i=1;i<=n;i++){
for(int j=0;j<=m;j++){
dp[i][j][0]=dp[i][j][1]=0x3f3f3f3f;
}
}
dp[1][0][0]=dp[1][1][1]=0;
for(int i=2;i<=n;i++){
dp[i][0][0]=dp[i-1][0][0]+dis[c[i-1]][c[i]];
for(int j=1;j<=m;j++){
int pos1=c[i-1],pos2=d[i-1],pos3=c[i],pos4=d[i];
dp[i][j][0]=min(dp[i][j][0],min(dp[i-1][j][0]+dis[pos1][pos3],dp[i-1][j][1]+p[i-1]*dis[pos2][pos3]+(1-p[i-1])*dis[pos1][pos3]));
dp[i][j][1]=min(dp[i][j][1],min(dp[i-1][j-1][0]+p[i]*dis[pos1][pos4]+(1-p[i])*dis[pos1][pos3],dp[i-1][j-1][1]+p[i]*p[i-1]*dis[pos2][pos4]+(1-p[i-1])*(1-p[i])*dis[pos1][pos3]+(1-p[i])*p[i-1]*dis[pos2][pos3]+(1-p[i-1])*p[i]*dis[pos1][pos4]));
}
}
double ans=0x3f3f3f3f;
for(int j=0;j<=m;j++){
ans=min(ans,min(dp[n][j][1],dp[n][j][0]));
}
printf("%.2lf\n",ans);
return 0;
}
獎勵關
閱讀理解題2
題意又是不很好理解,而且就算理解了思維量也不小。
但是還是逃不出期望dp的范疇,好好想想怎么定義,怎么轉移,題目是難不倒我們的!
先翻譯題面:
每次有\(\frac{1}{n}\)幾率扔出來一個東西,你可以選擇撿或不撿(吃還是不吃什么的太奇怪了!)
然后你每撿一個東西,都會加一個值,值可正可負,然后一個物品被撿有前提條件,就是不滿足前提條件就不允許撿ta。
問最大的期望。
注意本題n<=15,小的離譜,直接狀壓搞上,定義dp[i][S]表示第i輪狀態為S的期望得分
我們沿用期望dp倒推的思路,考慮每個i,S怎么被轉移。
枚舉每個被扔出來的物品,它會對這次得分造成一定貢獻。
- 如果被扔出來的物品,已經滿足它的前提條件,那么我們可以把它塞到現在的答案里,也可能不塞,用類似背包的思路,直接對兩種情況取max轉移:
\(dp[i][S]+=\sum\limits_{x}^{x\in(1~n)}\frac{1}{n}max(dp[i+1][S],dp[i+1][S|1<<(x-1)]+score[x]);\)
x是枚舉的當前蹦出來的那個。 - 如果扔出來的物品,沒滿足前提條件,那就沒法要它的分,直接由dp[i+1][S]轉移過來即可。
然后就沒了,直接轉移就可以得出正確的答案了。最后答案就是dp[1][0]。
碼:
#include<bits/stdc++.h>
using namespace std;
const int maxn=1e5+10;
double dp[110][maxn];
int pre[maxn],sc[maxn];
int n,k;
int main(){
scanf("%d%d",&k,&n);
for(int i=1;i<=n;i++){
scanf("%d",&sc[i]);
int x;
while(1){
scanf("%d",&x);
if(x==0)break;
else pre[i]|=(1<<x-1);
}
}
int Max=(1<<n)-1;
for(int i=k;i>=1;i--){
for(int S=0;S<=Max;S++){
for(int j=1;j<=n;j++){
int now=1<<j-1;
if((pre[j]&S)==pre[j]){
dp[i][S]+=max(dp[i+1][S],(dp[i+1][S|now]+sc[j]))/n;
}else{
dp[i][S]+=dp[i+1][S]/n;
}
}
}
}
printf("%.6lf\n",dp[1][0]);
return 0;
}
游走
這道題是期望與高斯消元的結合(歷史性的會面)之后他倆會經常放在一起ex我們。
這道題主要的思路就是:
求邊的期望-->求點的期望-->高斯消元求解-->反推邊的期望
幾個前備知識:
首先,我們知道邊的期望經過次數,等於它端點的期望經過次數×(1/端點的度數)
這挺顯然的,一個邊只有可能由它的兩個端點走過來,那它的期望也一定是兩個端點的期望乘上他們經過這條邊的概率(如題,就是度數分之一)
接着,我們考慮點的期望怎么求。
一個點只有可能從與它相鄰的點轉移過來,所以類比上面的結論,一個點的期望,就等於它的鄰接點們的期望,乘以它鄰接點各自轉移過來的概率。
所以,綜上我們可以得出一個方程式子:(E表示期望)
\(E(u)= \sum\limits_{v}^{edge(u,v)\in m}\frac{1}{degree[v]}\)
注意:1和n這兩個點需要特判,1這個點作為起點,默認已經經過一次,n這個點到這就不會在走,所以他不會去更新它鄰接點的答案。也就是說計算一個點的期望時,如果它的鄰接點有n,不算1/degree[n]這個值。
接下來,考慮得出這個柿子后,怎么求出每個dp的確定值。
首先:類似dp轉移的那種方法肯定不行,每個點都會對別的點有貢獻,別的點也會對這個點有貢獻,這會出現環,dp轉移會轉移死循。
所以這道題求出每個點的期望的方法就是解方程辣。
根據上面的柿子,我們發現每個點的期望都最多只會加上n-2個點的期望乘概率,而n的范圍允許我們\(n^3\)做,那么就把每個點的轉移寫成\(a*E[1]+b*E[2]+c*E[3]+d*E[4]+……=x\)的形式,然后發現這是一個矩陣是(n-1)*n的n-1元方程,(不用算n這個點的期望,因為它不會給點貢獻期望,所以也不會給邊貢獻期望),高斯消元即可。
最后統計一下每條邊的期望值,從大到小排個序,分別標號1~m然后就出結果了。