第一题:数组推导
签到题
比较简单,遍历序列B,若要让序列A的和最大,每次都取最大值即可;若要让序列A的和最小,只要序列B没上升(意味着最大值未改变)就取0,上升的话就取最大值。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | #include <iostream>#include <cstring>#include <algorithm>using namespace std;int n;int b[150];int sum_max, sum_min;int main() { cin >> n; for (int i = 1; i <= n; i ++ ) cin >> b[i]; for (int i = 1; i <= n; i++) { sum_max += b[i]; if (b[i] > b[i - 1]) sum_min += b[i]; } cout << sum_max << endl << sum_min; return 0;} |
第二题:非零段划分
大概题意是,给定一个序列A,然后让你进行一次操作,这个操作是将序列中小于p的数全部变成0,让序列A有最多的非零段。暴力做法是遍历所有的p,但是这样只能拿70分,剩余的会超时。
要做改进首先要发现一个东西,在序列A中每到一个“上坡”,即A[i+1]>A[i]时,只要p位于A[i+1]和A[i]之间,都可以产生一个非零段。注意到之后就可以开一个p的取值范围大小的差分数组,存储各个p能产生多少非零段,最后求一下前缀和就能找到合适的p。
大概题意是,给定一个序列A,然后让你进行一次操作,这个操作是将序列中小于p的数全部变成0,让序列A有最多的非零段。暴力做法是遍历所有的p,但是这样只能拿70分,剩余的会超时。
要做改进首先要发现一个东西,在序列A中每到一个“上坡”,即A[i+1]>A[i]时,只要p位于A[i+1]和A[i]之间,都可以产生一个非零段。注意到之后就可以开一个p的取值范围大小的差分数组,存储各个p能产生多少非零段,最后求一下前缀和就能找到合适的p。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | #include <iostream>#include <cstring>#include <algorithm>using namespace std;const int N = 5 * 1e5 + 10, M = 1e4 + 10;int n;int a[N], s[M];int ans;void add(int a, int b) { s[b]++, s[a + 1]--;}int main() { scanf("%d", &n); for (int i = 1; i <= n; i ++ ) scanf("%d", &a[i]); for (int i = 1; i <= n + 1; i ++ ) { if (a[i] > a[i - 1]) { add(a[i], a[i - 1] + 1); } } for (int i = 1; i < M; i++) { s[i] += s[i - 1]; ans = max(s[i], ans); } cout << ans; return 0;} |
第三题:脉冲神经网络
大模拟
注意读清楚题目来模拟即可,用邻接表来存储,对于延时到达的脉冲,用滚动数组来存储其信息即可,可以节省很多空间,不过记得在一个单位时间后清零上一单位时间的脉冲信息。这道题目卡常非常厉害,使用scanf读入、不封装函数、不封装结构体等等,尽量节约时间。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 | #include <iostream>#include <cstring>#include <algorithm>using namespace std;const int N = 1024, M = 2 * N, MOD = N - 1;static unsigned long nxt = 1;/* RAND_MAX assumed to be 32767 */inline int myrand(void) { nxt = nxt * 1103515245 + 12345; return((unsigned)(nxt/65536) % 32768);}int n, s, p, t;double dt;double u[N], v[N], a[N], b[N], c[N], d[N], ik[N][N];int sh[N]; // 神经元double r[N]; // 脉冲源int h[M], e[M], ne[M], idx;double w[N];int dl[N]; // 突触inline void add(int& x, int& y, double& wi, int& di) { e[idx] = y, w[idx] = wi, dl[idx] = di, ne[idx] = h[x], h[x] = idx ++ ;}int main() { memset(h, -1, sizeof h); scanf("%d%d%d%d", &n, &s, &p, &t); scanf("%lf", &dt); int i = 0, rn; while (i < n) { scanf("%d", &rn); scanf("%lf%lf%lf%lf%lf%lf", &v[i], &u[i], &a[i], &b[i], &c[i], &d[i]); for (int j = i + 1; j < i + rn; j++) v[j] = v[i], u[j] = u[i], a[j] = a[i], b[j] = b[i], c[j] = c[i], d[j] = d[i]; i += rn; } for (int i = 0; i < p; i++) scanf("%lf", &r[i]); int x, y, di; double wi; for (int i = 0; i < s; i++) { scanf("%d%d", &x, &y); scanf("%lf%d", &wi, &di); add(x, y, wi, di); } // 时间步模拟 for (register int i = 1; i <= t; i++) { // 遍历脉冲源 for (int j = 0; j < p; j++) { if (r[j] > myrand()) { for (int k = h[j + n]; k != -1; k = ne[k]) { ik[(i + dl[k]) & MOD][e[k]] += w[k]; } } } // 遍历神经元 for (int j = 0; j < n; j++) { double old_u = u[j], old_v = v[j]; v[j] += dt * (0.04 * old_v * old_v + 5 * old_v + 140 - old_u) + ik[i & MOD][j]; u[j] += dt * a[j] * (b[j] * old_v - old_u); if (v[j] >= 30.0) { sh[j]++; for (int k = h[j]; k != -1; k = ne[k]) { ik[(i + dl[k]) & MOD][e[k]] += w[k]; } v[j] = c[j]; u[j] += d[j]; } } // 由于是循环使用,故记得清零 memset(ik[i & MOD], 0, sizeof ik[i & MOD]); } double v_min = v[0], v_max = v[0]; int sh_min = sh[0], sh_max = sh[0]; for (int i = 1; i < n; i++) { v_min = min(v_min, v[i]); v_max = max(v_max, v[i]); sh_min = min(sh_min, sh[i]); sh_max = max(sh_max, sh[i]); } printf("%.3lf %.3lf\n", v_min, v_max); printf("%d %d", sh_min, sh_max); return 0;} |
第四题:卡牌收集
状压DP
一开始应该可以想到暴力DFS的做法,然后可以再考虑记忆化搜索,这时候就用到状态压缩,用一个16位的二进制数表示当前已经拥有的卡牌。
这里用循环来实现DP,先考虑状态的表示,dp[i][j]表示在抽第i次时已拥有的卡牌为j,二进制的j表示拥有哪些卡牌,dp[i][j]存储的就是达到这个状态的概率;再去考虑状态的计算,对于每个状态dp[i][j],假设拥有的卡牌为c1、c2、…、ck,那么那么第i次抽卡的结果就是这k种牌的其中一种,然后对于每种抽到的牌都有两种可能:新获得的、再次获得的。根据上述就能进行状态转移的计算,遍历当前拥有的牌,每种牌分为新获得、再次获得两种情况,逐次累加概率即可,需要注意的就是如果用来计算的旧的状态已经完成了卡牌收集,那么不能转移到当前状态。
需要注意的是,官网上判断精度有点问题,要保留到10位
一开始应该可以想到暴力DFS的做法,然后可以再考虑记忆化搜索,这时候就用到状态压缩,用一个16位的二进制数表示当前已经拥有的卡牌。
这里用循环来实现DP,先考虑状态的表示,dp[i][j]表示在抽第i次时已拥有的卡牌为j,二进制的j表示拥有哪些卡牌,dp[i][j]存储的就是达到这个状态的概率;再去考虑状态的计算,对于每个状态dp[i][j],假设拥有的卡牌为c1、c2、…、ck,那么那么第i次抽卡的结果就是这k种牌的其中一种,然后对于每种抽到的牌都有两种可能:新获得的、再次获得的。根据上述就能进行状态转移的计算,遍历当前拥有的牌,每种牌分为新获得、再次获得两种情况,逐次累加概率即可,需要注意的就是如果用来计算的旧的状态已经完成了卡牌收集,那么不能转移到当前状态。
需要注意的是,官网上判断精度有点问题,要保留到10位
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 | #include <iostream>#include <cstdio>#include <cstring>#include <algorithm>using namespace std;const int N = 17, M = 1 << N;int n, k;double p[N];double dp[N * 5][M];int cnt[M];double ans;void cnt_card() { for (int i = 0; i < M; i ++ ) { int j = i; while(j) { j -= j & -j; cnt[i]++; } }}int main() { cin >> n >> k; for (int i = 0; i < n; i ++ ) cin >> p[i]; for (int j = 0; j < n; j++) dp[1][1 << j] = p[j]; cnt_card(); for (int i = 2; i <= n * k; i++) { for (int j = 1; j < 1 << n; j++) { for (int s = 0; s < n; s++) { if (j & (1 << s)) { if (i - 1 - cnt[j ^ (1 << s)] + cnt[j ^ (1 << s)] * k < n * k) dp[i][j] += dp[i - 1][j ^ (1 << s)] * p[s]; if (i - 1 - cnt[j] + cnt[j] * k < n * k) dp[i][j] += dp[i - 1][j] * p[s]; } } if (i - cnt[j] + cnt[j] * k >= n * k) { ans += dp[i][j] * i; } } } printf("%.10lf", ans); return 0;} |
