看是老家的中学出的题目,会不会可爱一点呢?
嗯,真可爱,可爱的我两道题文件都错了。
因为最近我把文件输入输出改成#ifndef和#endif的形式了,然后放在了一个函数里,结果敲板子的时候忘调用了……
鬼知道为啥我T1敲了上去。
T1 rps
期望得分:20
实际得分:40
看了看题,发现这其实构成了一棵满二叉树,但这好像并没有什么用。然后就开始想暴力,可惜的是暴力都不知道怎么写。随便看了看上个厕所,突然发现只要最后的胜者确定了,这个序列就是唯一的,仅仅是没排好序。于是我就开始枚举胜者,再分治判断,然后我就不知怎么的开始犯zz了:如果合法,就直接排序输出,然后break了!我当时不知怎么想的,认为合法的序列就一个,然后又担心时间复杂度,决定找一个就完事……更有乐的是,我排序的时候又怕时间不够,只比较了两个序列的前4个字符……
所以估分的时候就只敢保证送的那20分。
正解其实和我的思路一样:枚举最终胜者,判定是否合法,如果合法,就排完序存下来,最后从得到的三个序列中取字典序最小的。排序就像归并排序,所以复杂度O(3 * nlogn)。
1 #include<cstdio> 2 #include<iostream> 3 #include<cmath> 4 #include<algorithm> 5 #include<cstring> 6 #include<cstdlib> 7 #include<cctype> 8 #include<vector> 9 #include<stack> 10 #include<queue> 11 using namespace std; 12 #define enter puts("") 13 #define space putchar(' ') 14 #define Mem(a, x) memset(a, x, sizeof(a)) 15 #define rg register 16 typedef long long ll; 17 typedef double db; 18 const int INF = 0x3f3f3f3f; 19 const db eps = 1e-8; 20 const int maxn = (1 << 20) + 5; 21 const int base = 100; 22 inline ll read() 23 { 24 ll ans = 0; 25 char ch = getchar(), last = ' '; 26 while(!isdigit(ch)) {last = ch; ch = getchar();} 27 while(isdigit(ch)) {ans = ans * 10 + ch - '0'; ch = getchar();} 28 if(last == '-') ans = -ans; 29 return ans; 30 } 31 inline void write(ll x) 32 { 33 if(x < 0) x = -x, putchar('-'); 34 if(x >= 10) write(x / 10); 35 putchar(x % 10 + '0'); 36 } 37 void MYFILE() 38 { 39 #ifndef mrclr 40 freopen("rps.in", "r", stdin); 41 freopen("rps.out", "w", stdout); 42 #endif 43 } 44 45 int r, p, s, n; 46 const char c[3] = {'P', 'R', 'S'}; 47 int num[3], a[maxn], ans[3][maxn]; 48 49 bool judge(int L, int R, int x) 50 { 51 if(L == R) 52 { 53 if(--num[x] < 0) return 0; 54 a[L] = x; return 1; 55 } 56 int mid = (L + R) >> 1; 57 return judge(L, mid, x) && judge(mid + 1, R, ++x > 2 ? 0 : x); 58 } 59 void rotate(int L, int R) //归并排序 60 { 61 if(L == R) return; 62 int mid = (L + R) >> 1; 63 rotate(L, mid); rotate(mid + 1, R); 64 bool flg = 0; 65 for(int i = L, j = mid + 1; i <= mid; ++i, ++j) 66 if(a[i] > a[j]) {flg = 1; break;} 67 if(flg) for(int i = L, j = mid + 1; i <= mid; ++i, ++j) 68 swap(a[i], a[j]); 69 } 70 71 void init() 72 { 73 num[0] = p; num[1] = r; num[2] = s; 74 } 75 76 void work(int x) 77 { 78 init(); 79 if(judge(1, n, x)) 80 { 81 rotate(1, n); 82 for(int i = 1; i <= n; ++i) ans[x][i] = a[i]; 83 } 84 } 85 86 int main() 87 { 88 MYFILE(); 89 r = read(), p = read(), s = read(); 90 n = r + p + s; 91 for(int i = 0; i < 3; ++i) ans[i][1] = 'Z'; 92 for(int i = 0; i < 3; ++i) work(i); 93 for(int i = 1; i <= n; ++i) //一下繁琐的代码其实就是在找三者中字典序最小的…… 94 { 95 int Min = ans[0][i], pos = 0, pos2 = ans[0][i]; 96 for(int j = 1; j < 3; ++j) 97 { 98 if(ans[j][i] < Min) Min = ans[j][i], pos = j; 99 if(ans[j][i] != pos2) pos2 = -1; 100 } 101 if(pos2 == -1) 102 { 103 for(int j = 1; j <= n; ++j) putchar(c[ans[pos][j]]); 104 enter; return 0; 105 } 106 } 107 printf("IMPOSSIBLE "); 108 return 0; 109 }
T2 vote
期望得分:30
实际得分:0(30)
0分就是文件的事了……加上后是30.
一看就不会呀!管他什么期望dp,上去先捡了10分,然后对于1,2测试点,二进制暴力枚举,复杂度O(C(k, n) * C(k / 2, k) * n),代码里面我预处理了从n中取k个的所有情况,所以实际复杂度能更低一点。
1 #include<cstdio> 2 #include<iostream> 3 #include<cmath> 4 #include<algorithm> 5 #include<cstring> 6 #include<cstdlib> 7 #include<cctype> 8 #include<vector> 9 #include<stack> 10 #include<queue> 11 using namespace std; 12 #define enter puts("") 13 #define space putchar(' ') 14 #define Mem(a, x) memset(a, x, sizeof(a)) 15 #define rg register 16 typedef long long ll; 17 typedef double db; 18 const int INF = 0x3f3f3f3f; 19 const db eps = 1e-8; 20 const int maxn = 18; 21 inline ll read() 22 { 23 ll ans = 0; 24 char ch = getchar(), last = ' '; 25 while(!isdigit(ch)) {last = ch; ch = getchar();} 26 while(isdigit(ch)) {ans = ans * 10 + ch - '0'; ch = getchar();} 27 if(last == '-') ans = -ans; 28 return ans; 29 } 30 inline void write(ll x) 31 { 32 if(x < 0) x = -x, putchar('-'); 33 if(x >= 10) write(x / 10); 34 putchar(x % 10 + '0'); 35 } 36 void MYFILE() 37 { 38 #ifndef mrclr 39 freopen("vote.in", "r", stdin); 40 freopen("vote.out", "w", stdout); 41 #endif 42 } 43 44 int n, k; 45 db a[maxn]; 46 47 int s[1 << maxn], cnt = 0; 48 int s2[1 << maxn], cnt2 = 0; 49 void init() 50 { 51 for(int i = 0; i < (1 << n); ++i) 52 { 53 int x = 0, tp = i; 54 while(tp) 55 { 56 if(tp & 1) x++; 57 tp >>= 1; 58 } 59 if(x == k) s[++cnt] = i; 60 if(x == (k >> 1)) s2[++cnt2] = i; 61 } 62 } 63 64 db ans = 0; 65 66 int main() 67 { 68 MYFILE(); //就少了这个! 69 n = read(); k = read(); 70 for(int i = 1; i <= n; ++i) scanf("%lf", &a[i]); 71 if(n == 2) {printf("%.8lf ", a[1] * (1 - a[2]) + (1 - a[1]) * a[2]); return 0;} 72 if(n > 16) {printf("0.500000000 "); return 0;} 73 init(); 74 for(int i = 1; i <= cnt; ++i) 75 { 76 db Max = 0; 77 for(int j = 1; j <= cnt2; ++j) if((s[i] & s2[j]) == s2[j]) 78 { 79 db x = 1; 80 for(int h = 1; h <= n; ++h) 81 { 82 if((1 << (h - 1)) & s2[j]) x *= (1 - a[h]); 83 else if((1 << (h - 1)) & s[i]) x *= a[h]; 84 } 85 Max += x; 86 } 87 ans = max(ans, Max); 88 } 89 printf("%.8lf ", ans); 90 return 0; 91 }
正解其实还是比较好理解的……
有一个结论,就是先排个序,选出的k个数一定是一个前缀和一个后缀,证明放在最后。
接下来就比较好想了:我们先做一个n = k的情况:令dp[i][j]表示在选了的长度为 i 的前缀中,用 j 个人投了“好票”的概率,那么dp[i][j] = a[i] * dp[i - 1][j - 1] + (1 - a[i]) * dp[i - 1][j]。
最终输出dp[n][n / 2]即可。
对于k != n的情况,我们既然已经知道了肯定是选一个前缀和后缀,那么就dp两次:令pre[i][j]表示在选了的前 i 个人中有 j 个投了“好票”的概率,suf[i][j]表示在选了的后 i 个人中有 j 个人头好票的概率。转移方程和上面几乎一样,就不讲了。
维护好了这俩,我们再O(n2)枚举一下就行:枚举选了前 x 个人,所以后缀就是k - x个,再枚举在x个人中有h个人投“好票”。然后维护最大值就行了。
1 #include<cstdio> 2 #include<iostream> 3 #include<cmath> 4 #include<algorithm> 5 #include<cstring> 6 #include<cstdlib> 7 #include<cctype> 8 #include<vector> 9 #include<stack> 10 #include<queue> 11 using namespace std; 12 #define enter puts("") 13 #define space putchar(' ') 14 #define Mem(a, x) memset(a, x, sizeof(a)) 15 #define rg register 16 typedef long long ll; 17 typedef double db; 18 const int INF = 0x3f3f3f3f; 19 const db eps = 1e-8; 20 const int maxn = 2e3 + 5; 21 inline ll read() 22 { 23 ll ans = 0; 24 char ch = getchar(), last = ' '; 25 while(!isdigit(ch)) {last = ch; ch = getchar();} 26 while(isdigit(ch)) {ans = ans * 10 + ch - '0'; ch = getchar();} 27 if(last == '-') ans = -ans; 28 return ans; 29 } 30 inline void write(ll x) 31 { 32 if(x < 0) x = -x, putchar('-'); 33 if(x >= 10) write(x / 10); 34 putchar(x % 10 + '0'); 35 } 36 void MYFILE() 37 { 38 #ifndef mrclr 39 freopen("vote.in", "r", stdin); 40 freopen("vote.out", "w", stdout); 41 #endif 42 } 43 44 int n, k; 45 db a[maxn]; 46 db pre[maxn][maxn], suf[maxn][maxn]; 47 48 int main() 49 { 50 MYFILE(); 51 n = read(); k = read(); 52 for(int i = 1; i <= n; ++i) scanf("%lf", &a[i]); 53 sort(a + 1, a + n + 1); 54 pre[0][0] = 1; 55 for(int i = 1; i <= n; ++i) 56 { 57 for(int j = 1; j <= i; ++j) 58 pre[i][j] = a[i] * pre[i - 1][j - 1] + (1 - a[i]) * pre[i - 1][j]; 59 pre[i][0] = (1 - a[i]) * pre[i - 1][0]; 60 } 61 suf[n + 1][0] = 1; 62 for(int i = n; i; --i) 63 { 64 for(int j = 1; j <= n - i + 1; ++j) 65 suf[i][j] = a[i] * suf[i + 1][j - 1] + (1 - a[i]) * suf[i + 1][j]; 66 suf[i][0] = (1 - a[i]) * suf[i + 1][0]; 67 } 68 db ans = 0; 69 for(int L = 0; L <= k; ++L) //pre : 0 ~ k 70 { 71 int R = n + 1 + L - k; 72 db Max = 0; 73 for(int i = 0; i <= (k >> 1); ++i) Max += pre[L][i] * suf[R][(k >> 1) - i]; 74 ans = max(ans, Max); 75 } 76 printf("%.8lf ", ans); 77 return 0; 78 }
证明来啦:
首先是感性理解:假设有两个人,一个人投“好票”的概率很大,一个人投“好票”的概率很小,那么他们两人投平票的概率就很大。完了。
数学证明(Ssy大佬讲的,巨):我们假设[1, L]和[R, n]已经都选了,也就是说我们要证明下一个数应该在L +1处或是R - 1处取到。
dp方程可以写出来:dp[i][j] = a[i] * dp[L][j - 1] + (1 - a[i]) * dp[L][j] i∈[L + 1, R - 1]
因为对于dp过的结果不会改变,不妨令A = dp[L][j - 1], B = dp[L][j],
则dp[i][j] = A * a[i] + B * (1 - a[i]) = (A - B) * a[i] + B
可见这是一个一次函数,那么自然在端点处取得最值。
T3 factory
期望得分:10
实际得分:0(20)
我再也不会忘写文件了。
看者数据范围我就觉得是最小割,结果图怎么也建不出来,最后搞了一个只能过样例的错误建图方法,自然是gg了。
题解没看懂,大概是用二分图证明任意一个极大匹配都是完美匹配,然后状压dp求解。
今天是真有点惨,希望明天能扳回来点儿……