8.21總結前日及今日
估計拿不到打區域賽的名額了,還是有點憂傷,哎
8.21場鏈接
A題:
題意:
給出一個0~n-1的單詞表,問要組成一個長度為m,且單詞權值之和為k的單詞,的方案總數有多少種
解法:
其實就是問把k個球放到m個桶中(同中球數可以為空),且每個桶中的球數不超過n-1個,的方案總數。(仔細想一下,很有意思)
怎麽容斥? 總情況數 — 桶中球數超過n個的情況數即可,即:
當i等於0的時候,即是總的方案數,註意容斥的時候是奇數項為負,偶數項為正
1 #include<cstdio> 2 #include<cmath> 3 using namespace std;4 const int maxn = 1e6 + 7; 5 const long long mod = 998244353; 6 long long fac[maxn], inv[maxn]; 7 long long tmp[maxn]; 8 int n, m, k; 9 int t; 10 11 void init() 12 { 13 tmp[0] = tmp[1] = 1; 14 inv[1] = inv[0] = 1; 15 for (int i = 2; i < maxn; i++) 16 { 17 tmp[i] = (mod - mod / i) * tmp[mod % i] % mod;18 inv[i] = inv[i - 1] * tmp[i] % mod; 19 } 20 fac[0] = 1; 21 for (int i = 1; i < maxn; i++) 22 fac[i] = (fac[i - 1] * i) % mod; 23 } 24 25 long long c(int n, int m) //c(n,m) 26 { 27 if (n < m) return 0; 28 return fac[n] % mod * inv[n - m] % mod * inv[m] % mod;29 } 30 31 long long solve(int i) 32 { 33 return c(m + k - 1 - i * n, m - 1) % mod * c(m, i) % mod; 34 } 35 36 int main(int argc, char const *argv[]) 37 { 38 init(); 39 scanf("%d", &t); 40 while (t--) 41 { 42 scanf("%d%d%d", &n, &m, &k); 43 long long ans = 0; 44 for (int i = 0; i <= m && (i * n) <= k; i++) 45 { 46 if (i % 2) ans -= solve(i); 47 else ans += solve(i); 48 } 49 ans %= mod; 50 printf("%lld\n", (ans + mod) % mod); 51 } 52 return 0; 53 }
Uva12034
題意:兩匹馬比賽有三種比賽結果,n匹馬比賽的所有可能結果總數
解法:
設答案是f[n],則假設第一名有i個人,有C(n,i)種可能,接下來還有f(n-i)種可能性,因此答案為 ΣC(n,i)f(n-i)
另外這裏給出兩個求組合數的模板,盧卡斯定理的p是模數,並且要求是素數,第二個是遞推式,適合於n<2000的情況
1 #include<cstdio> 2 using namespace std; 3 const int maxn = 1e3; 4 const int mod = 10056; 5 typedef long long ll; 6 7 /*--------------------------盧卡斯定理取模-----------------------*/ 8 ll exp_mod(ll a, ll b, ll p) { 9 ll res = 1; 10 while (b != 0) { 11 if (b & 1) res = (res * a) % p; 12 a = (a*a) % p; 13 b >>= 1; 14 } 15 return res; 16 } 17 18 ll Comb(ll a, ll b, ll p) { 19 if (a < b) return 0; 20 if (a == b) return 1; 21 if (b > a - b) b = a - b; 22 23 ll ans = 1, ca = 1, cb = 1; 24 for (ll i = 0; i < b; ++i) { 25 ca = (ca * (a - i)) % p; 26 cb = (cb * (b - i)) % p; 27 } 28 ans = (ca*exp_mod(cb, p - 2, p)) % p; 29 return ans; 30 } 31 //Lucas定理對組合數取模 32 ll Lucas(int n, int m, int p) { 33 ll ans = 1; 34 while (n&&m&&ans) { 35 ans = (ans*Comb(n%p, m%p, p)) % p; 36 n /= p; 37 m /= p; 38 } 39 return ans; 40 } 41 42 /*----------------------組合數遞推公式(適用n<2000)---------------------------*/ 43 int C[maxn+10][maxn+10]; 44 void Cal_C(int n) { 45 //傳遞的是一個二維的數組c 46 for (int i = 0; i <= n; i++){ 47 C[i][0] = C[i][i] = 1; 48 for (int j = 1; j < i; j++) 49 C[i][j] = (C[i - 1][j - 1]%mod + C[i - 1][j]%mod)%mod; 50 } 51 return; 52 } 53 /*--------------------------------------------------------------------------*/ 54 55 int f[maxn+11]; 56 void generate() { 57 Cal_C(maxn); 58 f[0] = 1; 59 for (int i = 1; i <= maxn; i++) { 60 f[i] = 0; 61 for (int j = 1; j <= i; j++) 62 f[i] = (f[i] + C[i][j] * f[i - j]) % mod; 63 } 64 return; 65 } 66 67 int main() { 68 int T; scanf("%d", &T); 69 int kase = 1; 70 generate(); 71 while (T--) { 72 int n; scanf("%d", &n); 73 printf("Case %d: %d\n", kase++, f[n]); 74 } 75 return 0; 76 }
Uva12230
題意:A,B相距D,A,B間有n條河,河寬Li,每條河上有一個速度為vi的船,在河山來回行駛,每條河離A的距離為pi,現在求從A到B時間的期望,步行速度始終為1
解法:首先如果全部步行則期望為D,現在每遇到一條河,求過河時間的期望,等待時間的區間為(0,2*L/v),船在每個地方都是等可能的,所以等待的期望就是(0 + 2*L/v) / 2 = L / v,又過河還要L / v,所以總的渡河期望值為2 * L / v,所以每遇到一條河拿D減去假設步行過河的期望L再加上實際過河期望2 * L / v即可,最後發現和p沒有卵關系,真開心~
1 #include<iostream> 2 using namespace std; 3 4 int main() { 5 int n, dis; 6 int kase = 1; 7 while (scanf("%d%d", &n, &dis)) { 8 if (n == 0 && dis == 0)break; 9 double ans = 0, suml = 0; 10 for (int i = 1; i <= n; i++) { 11 double p, l, v; cin >> p >> l >> v; 12 ans += 2 * l / v; 13 suml += l; 14 } 15 ans += (dis - suml); 16 printf("Case %d: %.3f\n\n", kase++, ans); 17 } 18 return 0; 19 }
Uva1639
題意:
有兩個盒子各有n個糖果(n<=200000),每天隨機選擇一個:選第一個盒子的概率是p(0 ≤ p ≤ 1),第二個盒子的概率為1-p,然後吃掉其中的一顆。直到有一天,隨機選擇一個盒子打開一看,沒糖了!現在請你計算另一個盒子裏剩下的糖果數量的期望值。
解法:
我們假設到第n天的時候取得是第1個盒子的糖,此時第2個盒子有i顆糖,則在此之前打開了n+(n-i)次盒子, 其中n次打開了第一個盒子,(n-i)次打開了第二個盒子,則概率是C(2n-i,n)*p^(n+1)*(1-p)^n-i。
由於n高達20w,所以二次項系數會非常大,而後面的概率會非常小,所以如果直接計算會爆精度,所以這裏我們用求對數的方法進行計算
1 #include<iostream> 2 #include<cmath> 3 using namespace std; 4 typedef long double lb; 5 const int maxn = 2e5 + 5; 6 long double logF[2 * maxn + 66]; 7 8 void generate() { 9 //預處理出n!的log值 10 logF[0] = 0; 11 for (int i = 1; i <= maxn; i++) 12 logF[i] = logF[i - 1] + log(i); 13 } 14 // C(n,m) = n!/(m!(n-m)!) 15 long double logC(int n, int m) { 16 return logF[n] - logF[m] - logF[n - m]; 17 } 18 19 int main() { 20 int n; double p; 21 generate(); 22 int kase = 1; 23 while (scanf("%d%lf", &n, &p)!=EOF) { 24 double ans = 0; 25 for (int i = 0; i <= n; i++) { 26 long double v1 = logC(2 * n - i, n) + (n + 1)*log(p) + (n - i)*log(1 - p); 27 long double v2 = logC(2 * n - i, n) + (n + 1)*log(1 - p) + (n - i)*log(p); 28 ans += (i*(exp(v1) + exp(v2))); 29 } 30 printf("Case %d: %.6lf\n", kase++, ans); 31 } 32 return 0; 33 }
Uva1640
題意:
統計兩個整數a,b之間各個數字(0~9)出現的次數,如1024和1032,他們之間的數字有1024 1025 1026 1027 1028 1029 1030 1031 1032 總共有10個0,10個1,3個3等等。
解法:
這類問題的一般步驟都是先用f(n,d)計算出0~n中d數字出現的次數,那麽答案就是f(b,d)-f(a-1,d)
下面程序中的註釋為(1,2974)的第一層(未遞歸)解釋,遞歸後同理
1-2974 拆分為1-2970 和 2971-2974
1 #include<iostream> 2 #include<algorithm> 3 #include<cstring> 4 using namespace std; 5 int l, r; 6 int a[11], b[11]; 7 8 void solve(int n, int m) { 9 int x = n / 10;//除了末位以外的 x=297 10 int y = n % 10;//最後一位的數字範圍 y=4 11 int tmp = x; 12 //計算小於10*x+1到10*x+y中個位數的個數,b[1-y]也就是(2971-2974)個位數 13 for (int i = 1; i <= y; i++)b[i] += m; 14 //計算10*x-10到10*x-1中個位數的總個數,296 0-296 9 個位數個數每個都是296個 15 for (int i = 0; i <= 9; i++)b[i] += m*x; 16 while (tmp) { 17 //10*x 到10*x+y非個位的個數,因為從0開始,所以*(y+1)就是非個位位數,當然還要乘m 18 b[tmp % 10] += m*(y + 1); 19 tmp /= 10; 20 } 21 if (x) solve(x - 1, m * 10); 22 } 23 24 int main() { 25 while (cin >> l >> r && (l || r)) { 26 memset(b, 0, sizeof(b)); 27 if (l > r)swap(l, r); 28 solve(l - 1, 1); 29 for (int i = 0; i <= 9; i++)a[i] = b[i], b[i] = 0; 30 solve(r, 1); 31 cout << b[0] - a[0]; 32 for (int i = 1; i <= 9; i++)cout << " " << b[i] - a[i]; 33 cout << endl; 34 } 35 return 0; 36 }
Uva1641
題意:給出一個由 ‘ \ ‘,‘ . ‘, ‘ / ‘構成的圖形,問這個圖形的面積是多少
解法:對於’/‘或者’\‘,每個對面積的貢獻是0.5,對於‘.’來說從左到右,從上到下,判斷之前括號的個數是否為奇數即可,如果是奇數則說明在圖形內,反之亦然
1 #include<cstdio> 2 using namespace std; 3 const int maxn = 1e2 + 5; 4 char c[maxn][maxn]; 5 int h, w; 6 int main() { 7 while (scanf("%d%d", &h, &w)!=EOF) { 8 double ans = 0.0;int cnt = 0; 9 getchar(); 10 for (int i = 0; i < h; i++) { 11 for (int j = 0; j < w; j++) { 12 scanf("%c", &c[i][j]); 13 if (c[i][j] != ‘.‘) ans += 0.5,cnt++; 14 else {if (cnt & 1)ans += 1; } 15 } 16 getchar(); 17 } 18 printf("%d\n",(int)ans); 19 } 20 return 0; 21 }
Uva1363
題意:
輸入正整數n和k(範圍均為1e9),求∑(k mod i),i從1~n
解法:
首先這道題直接暴力親測會超時。
之後我們寫幾組數據之後可以發現當k/i的商相同的時候他們的余數成一個等差數列,而且數列首相是q,公差是p,項的個數是余數/商。
具體寫法網上面有分情況討論的,但是較為繁瑣,這裏LRJ的板子感覺寫法就很精煉。
我們從左到右依次枚舉每一項i(核心思想是減少i的枚舉個數),計算出k除以這個數的商和余數, 如果這個商是0,說明此時的i已經大於k;如果不為0(即大於0),即來計算等差的數列的值。
如果k%i==0,則項的個數為0,計算和之後為0,其他情況就很正常了。
1 #include<cstdio> 2 #include<algorithm> 3 using namespace std; 4 typedef long long ll; 5 ll series_sum(int p, int d, int n) { return (ll)(2 * p - n*d)*(n + 1) / 2; } 6 int main() { 7 int n, k; 8 while (scanf("%d%d", &n, &k) != EOF) { 9 ll ans = 0, i = 1; 10 while (i <= n) { 11 int p = k / i, q = k%i; 12 int cnt = n - i; 13 if (p > 0)cnt = min(cnt, q / p);//計算項的個數,避免超出n的範圍 14 ans += series_sum(q, p, cnt); 15 i += cnt + 1; 16 } 17 printf("%lld\n", ans); 18 } 19 return 0; 20 }
8.21總結前日及今日