五一集训:day3A
题目:同时抛掷n个理想的正方体骰子,有多少概率使得其中若干个骰子点数和为m?
多组数据
对于 50% 的数据,n≤2。
对于 100% 的数据,1≤n≤8,1≤T≤10000,1≤m≤50
挺好的题,在这么多天的集训里算一道清流了
先想暴力:枚举每个骰子的点数,最后再跑一遍dfs判断
O(6^n*2^n)死的很惨
优化:枚举每个骰子点数,最后用完全背包判断
O(6^n*36n)死的还行
再优化:由于每个骰子对背包的影响是独立的,所以可以在枚举的时候顺带维护背包
O(6^n*6n),能过,但不是最好
注意到优化中,每一个背包状态都只有0/1,即能否组成,而f至多50个
所以可将f压到一个long long里,状压dp即可
O(6^n)
顺带,这个题数据范围很大,但是有很多重复,所以应该记录哪些n已经被询问,这样避免T
#include<cstdio> #include<cstring> #include<algorithm> #include<queue> #include<cmath> #include<iostream> using namespace std; #define O(x) cout << #x << " " << x << endl; #define B cout << "breakpoint" << endl; inline int read() { int ans = 0,op = 1; char ch = getchar(); while(ch < '0' || ch > '9') { if(ch == '-') op = -1; ch = getchar(); } while(ch >= '0' && ch <= '9') { (ans *= 10) += ch - '0'; ch = getchar(); } return ans * op; } #define clr(a) memset(a,0,sizeof(a)); int ans[10][50]; bool f[50]; int a[50]; int n,m; void dfs(int cur)//cur:第几个 { if(cur == n + 1) { for(int j = 1;j <= 48;j++) ans[n][j] += f[j]; return; } for(int i = 1;i <= 6;i++)//cur的值是啥 { a[cur] = i; vector<int> tp; for(int j = 6 * n;j >= a[cur];j--) { if(f[j] == 0 && f[j - a[cur]]) tp.push_back(j); f[j] |= f[j - a[cur]]; } dfs(cur + 1); for(int j = 0;j < tp.size();j++) f[tp[j]] = 0; } } int gcd(int x,int y) { return !y ? x : gcd(y,x % y); } bool exist[10]; int main() { int t = read(); while(t--) { f[0] = 1; n = read(),m = read(); if(m > 48) {printf("0/1\n"); continue; } if(!exist[n]) exist[n] = 1,dfs(1); int base = pow(6,n); int g = gcd(ans[n][m],base); printf("%d/%d\n",ans[n][m] / g,base / g); } }