P7648 [COCI2012-2013#5] MNOGOMET 题解

P7648 [COCI2012-2013#5] MNOGOMET 题解


知识点

概率 DP,状态割裂动态规划。


分析

暴力

首先,我们可以将两个队伍转换为一张图,得到一个暴力状态:\(f_{t,ra,rb,i}\) 在第 \(t\) 秒比分为 \(ra:rb\) 时,球在第 \(i,i\in[1,2n]\) 号点上的概率。

边界条件:\(f_{0,0,0,1}=1\)

那么方程就是:

  1. 对于 \(v \ne 1 \land v \ne n+1\)

    \[f_{t+1,ra,rb,v} = \sum_{(u,v) \in G} \frac{f_{t,ra,rb,u}}{N_{f_u} + N_{e_u} + 1} \]

  2. 对于 \(v = 1\)

    \[f_{t+1,ra,rb,v} = \sum_{(u,v) \in G} \frac{f_{t,ra,rb,u}}{N_{f_u} + N_{e_u} + 1} + \sum_{(u,v) \in G \land u\in[n+1,2n]} \frac{(1-p_u)f_{t,ra,rb,u}+p_uf_{t,ra,rb-1,u}}{N_{f_u} + N_{e_u} + 1} \\ \]

  3. 对于 \(v = n + 1\)

    \[f_{t+1,ra,rb,v} = \sum_{(u,v) \in G} \frac{f_{t,ra,rb,u}}{N_{f_u} + N_{e_u} + 1} + \sum_{(u,v) \in G \land u\in[1,n]} \frac{(1-p_u)f_{t,ra,rb,u}+p_uf_{t,ra,rb-1,u}}{N_{f_u} + N_{e_u} + 1} \\ \]

最后简单统计一下答案,可以在洛谷得到130分的低分。

正解

正解需要我们转换状态,它没法直接化简(至少我不会),所以我们要用到状态割裂。用这类技巧的 DP 题比较稀有,需要好好珍惜,这也是我写这篇题解来总结这道题的原因。

我们发现一个问题:好像在 DP 过程中,开球是 \(1\) 号队员,射门后收球的也是 \(1\) 号队员,其他的队员好像都很闲,我们是不是可以直接把两个队伍缩成两个 \(1\) 号队员,然后把除射门之外的过程另外预处理(毕竟它们都是重复的),最后再处理比分的变化?

答案是当然可以。

我们设 \(h_{0/1,t,i}\) 表示一开始球在 0/1 号队伍的 \(1\) 号球员手中,\(t\) 秒后到 \(i\) 号球员手中的概率。

\[h_{0,0,1} = h_{1,0,n+1} = 1 \\ h_{team,t,v} = \sum_{(u,v) \in G} h_{team,t-1,u} \\ \]

现在我们处理了传球的部分,我们需要再开两个数组把他们缩成两个点以过度到比分的部分:

\({win/lose}_{0/1,0/1,i}\) 分别表示一开始球在 0/1 号队伍的 \(1\) 号球员,\(t\) 秒后 0/1 号队伍的球员射门进球、不进球的概率,那么就可以统计一下:

\[\begin{aligned} win_{team,0,t} & = \sum_{u=1}^n \frac{p_u h_{team,t-1,u}}{N_{f_u} + N_{e_u} + 1} \\ win_{team,1,t} & = \sum_{u=n+1}^{2n} \frac{p_u h_{team,t-1,u}}{N_{f_u} + N_{e_u} + 1} \\ lose_{team,0,t} & = \sum_{u=1}^n \frac{(1-p_u) h_{team,t-1,u}}{N_{f_u} + N_{e_u} + 1} \\ lose_{team,1,t} & = \sum_{u=n+1}^{2n} \frac{(1-p_u) h_{team,t-1,u}}{N_{f_u} + N_{e_u} + 1} \\ \end{aligned} \]

那么我们现在可以开始转换了:设 \(f_{t,ra,rb,0/1}\) 为在 \(t\) 时刻比分为 \(ra:rb\) 时,球在 0/1 队伍的 \(1\) 号球员手中的概率,方程如下:

\[f_{0,0,0,0} = 1 \\ f_{t,ra,rb,0} = \sum_{t'=0}^{t-1} \sum_{team \in{\{ 0,1 \}}} f_{t',ra,rb,team} \cdot lose_{team,1,t-t'} + (f_{t',ra,rb-1,team} \cdot win_{team,1,t-t'} [rb>0]) \\ f_{t,ra,rb,1} = \sum_{t'=0}^{t-1} \sum_{team \in{\{ 0,1 \}}} f_{t',ra,rb,team} \cdot lose_{team,0,t-t'} + (f_{t',ra-1,rb,team} \cdot win_{team,0,t-t'} [ra>0]) \\ \]

但是转换完了之后我们发现似乎还是不太对劲,因为需要统计答案,那我们来思考一下:一个状态 \(f_{t,ra,rb,0/1}\) 对于答案的总贡献分为什么?以便统计,我们可以大致分成以下两类:

  1. 射门转移到其他状态 \(f_{t',ra',rb',1/0}\) 去的;
  2. 再也不射门,比分保持在 \(ra:rb\)

那么我们发现,对于一个状态 \(f_{t,ra,rb,0/1}\),我们只要计算再也不射门的情况,就可以做到完全统计,因为射门转移走的会有其他状态统计掉。

只要再开一个数组 \(none_{0/1,t}\) 表示一开始球在 0/1 号队伍的 \(1\) 号球员,后面 \(t\) 秒都不再射门的概率,方程如下:

\[none_{team,t} = \sum_{u=1}^{2n} h_{team,t,u} \\ \]

最后浅浅统计一下答案,本题至此结束。


CODE

#include<bits/stdc++.h>
#define FOR(i,a,b) for(int i=(a);i<=(int)(b);++i)
#define EDGE(g,i,x,y) for(int i=(g).h[(x)],y=(g)[(i)].v;(i);(i)=(g)[(i)].nxt,(y)=(g)[(i)].v)
#define main Main();signed main(){ios::sync_with_stdio(0);cin.tie(0);return Main();}signed Main
using namespace std;
constexpr int N=200+10,R=10+10,T=5e2+10;
int n,r,Tim;
int d[N];
double p[N];
double none[2][T],ans[R][R];
double h[2][T][N],win[2][2][T],lose[2][2][T];
double f[T][R][R][2];
struct CFS{
	int tot,h[N];
	struct edge{
		int v,nxt;
		edge(int v=0,int nxt=-1):v(v),nxt(nxt){}
	}e[N*N];
	edge &operator[](int i){
		return e[i];
	}
	void att(int u,int v){
		e[++tot]=edge(v,h[u]),h[u]=tot;
	}
}g;
signed main(){
	cin>>n>>r>>Tim;
	FOR(u,1,n<<1){
		int v,F,E;
		cin>>p[u]>>F>>E,d[u]=F+E+1;
		FOR(j,1,F)cin>>v,g.att(u,v+(u>n?n:0));
		FOR(j,1,E)cin>>v,g.att(u,v+(u<=n?n:0));
	}
	h[0][0][1]=h[1][0][n+1]=1.0;
	FOR(team,0,1)FOR(tim,0,Tim)FOR(u,1,n<<1){
		EDGE(g,i,u,v)h[team][tim+1][v]+=h[team][tim][u]/d[u];
		none[team][tim]+=h[team][tim][u];
		lose[team][u>n][tim+1]+=h[team][tim][u]*(1-p[u])/d[u];
		win[team][u>n][tim+1]+=h[team][tim][u]*p[u]/d[u];
	}
	f[0][0][0][0]=1.0;
	FOR(tim,0,Tim-1){
		FOR(ra,0,r-1)FOR(rb,0,r-1)FOR(team,0,1){
			FOR(i,1,Tim-tim){
				f[tim+i][ra][rb][0]+=f[tim][ra][rb][team]*lose[team][1][i];
				f[tim+i][ra][rb][1]+=f[tim][ra][rb][team]*lose[team][0][i];
				f[tim+i][ra][rb+1][0]+=f[tim][ra][rb][team]*win[team][1][i];
				f[tim+i][ra+1][rb][1]+=f[tim][ra][rb][team]*win[team][0][i];
			}
			ans[ra][rb]+=f[tim][ra][rb][team]*none[team][Tim-tim];
		}
		FOR(i,0,r-1)ans[r][i]+=f[tim][r][i][0]+f[tim][r][i][1],ans[i][r]+=f[tim][i][r][0]+f[tim][i][r][1];
	}
	FOR(ra,0,r)FOR(rb,0,r)if(ra!=r||rb!=r)ans[ra][rb]+=f[Tim][ra][rb][0]+f[Tim][ra][rb][1];
	FOR(ra,0,r)FOR(rb,0,r)if(ra!=r||rb!=r)cout<<fixed<<setprecision(10)<<ans[ra][rb]<<endl;
	return 0;
}

启示

我们在 DP 过程中,如果整体处理不容易求解,我们可以考虑状态割裂,分步解决。就如以上我的推导过程就把整个踢球的过程分成了传球和射门,并把一个队伍缩成了一个点。


posted @ 2024-08-20 22:13  Add_Catalyst  阅读(15)  评论(0)    收藏  举报