20201023模擬賽總結
阿新 • • 發佈:2020-10-23
T1
DP
T2
頭一次在比賽中做出來數論題qwq
寫完 T1 看 T2 ,看見是數學題直接自閉。看完 T3 T4 發現後面的題更自閉滾回來看 T2 。
首先試圖定義 dp[i][j][k] 為比了 \(i\) 場,平了 \(j\) 場,贏了 \(k\) 場的期望得分,然後發現數據範圍 \(n \leq 2000\) 給自己期望一個 \(0pts\) ,當場自閉。
又試圖搞別的方法,定義 f[i][j] 為比了 i 場,平了 j 場的概率,g[i][j] 為在非平局的 i 場中獲勝 j 場的概率,那麼可以推出當平了 i 場的時候對答案的貢獻是 \((i+1)\times f_i \times \sum\limits_{j>n-i-j}g_{n-i,j}\)
在推 \(f\) 和 \(g\) 的遞推式的時候發現好複雜,又逐漸自閉……
我們可以找到直接推出 \(f_{i,j}\) 的方法 : \(f_{i,j} = C_i^j\times\left(\frac{p}{p+2}\right)^j \times \left(\frac{2}{p+2}\right)^{i-j}\)
但是發現 \(g\) 的式子並不是很好推,然而突然發現這裡兩個人獲勝的概率是相同的,因此可以想到一個人贏比輸少與輸比贏少的概率應當是相同的,要注意當 \(2\mid i\)
我們只需要列舉平局的數量,即可線性求出答案。
code:
#include <cstdio> #include <cstring> #include <algorithm> #define ll long long #define prev Prev using namespace std; int read() { int a = 0,x = 1;char ch = getchar(); while(ch > '9' || ch < '0') {if(ch == '-') x = -1;ch = getchar();} while(ch >= '0' && ch <= '9') {a = a*10 + ch-'0';ch = getchar();} return a*x; } const int N=3e6+7,P=998244353; int n,p; ll prev; ll tmp[N],rev[N]; ll fpow(ll a,ll x) { if(x == 1) return a; if(x == 0) return 1; ll t = fpow(a,x/2); if(x&1) return t*t%P*a%P; else return t*t%P; } ll f[N],g[N],h[N]; inline ll C(int a,int b) {return b?tmp[a]*rev[b]%P*rev[a-b]%P:1;} inline ll F(int i,int j) {return C(i,j)*f[j]%P*g[i-j]%P;} inline ll G(int x) { if(!x) return 0; if(x&1) return rev[2]; else return (1-C(x,x/2)*h[x]%P+P)%P*rev[2]%P; } int main() { freopen("fight.in","r",stdin); freopen("fight.out","w",stdout); n = read(),p = read(); prev = fpow(p+2,P-2); tmp[0] = rev[0] = f[0] = g[0] = h[0] = rev[1] = 1; for(register int i = 2;i <= n;i ++) {rev[i] = (P-P/i)*rev[P%i];if(rev[i]>P) rev[i]%=P;} for(register int i = 1;i <= n;i ++) {tmp[i] = tmp[i-1]*i;rev[i] = rev[i-1]*rev[i];if(tmp[i]>P)tmp[i]%=P;if(rev[i]>P)rev[i]%=P;} for(register int i = 1;i <= n;i ++) { f[i] = f[i-1]*p%P*prev%P; g[i] = g[i-1]*2%P*prev%P; h[i] = h[i-1]*rev[2]%P; } ll ans = 0; for(register int i = 0;i <= n;i ++) { ans = (ans+F(n,i)*G(n-i)%P*(i+1)%P)%P; } printf("%lld",ans); return 0; }