(暑假打的训练赛 部分题有参考别人的博客 已经在本人的水平下尽力讲解清楚 如果有疑问看不懂或者有错误疏漏 可以留言)
多校链接Search Result (hdu.edu.cn)
K-Random
这个求期望的题容易得出公式((n-m)/2)%1e9+7 但出现了小数是无法取模的 这里我们利用除法取模取逆元 即可得到答案
add:除法取模取逆元 这里我们给出求逆元的公式 1/a%p等价于 a^p-2
给出数学证明:
费马小定理:如果p是一个质数,而整数a不是p的倍数,则有a^(p-1)≡ 1(mod p)。
#include<bits/stdc++.h>
#define int long long using namespace std; const int mod = 1e9 + 7;
//用一个快速幂把指数运算优化到log级别 int qpow(int a, int k, int mod) { int res = 1; while (k) { if (k & 1) res = res * a % mod; a = a * a % mod; k >>= 1; } return res; } signed main() { int T; scanf("%lld",&T); while (T--) { int n, m; scanf("%lld %lld",&n,&m); printf("%lld\n", (n - m) * qpow(2, mod - 2, mod) % mod ); } }
L-Alice and Bob
博弈论
但感觉问题的关键在于你是不是有把大问题拆分成小问题的算法设计与解决问题的思想。
所以我们从“小”到“大”考虑 ,有一个0 Alice赢 、两个1 Alice、个2 Alice赢 、八个3 Alice赢……以此类推 ,每个数字都是对Alice赢有贡献的,若Alice想赢就要尽可能的有更多威胁到Bob的组合,达到上限后,Alice必赢 。
从“大”到“小”考虑 (大问题变成小问题,从后往前循环一遍 a [ i − 1 ] + = a [ i ] / 2 ,最后考虑a[0]是否大于等于1 ,即是否有0存在 ,有0存在即 Alice赢。
#include<bits/stdc++.h> #define int long long using namespace std; const int mod = 1e9 + 7, N = 1e5 + 5; int a[N]; void solve() { int n; scanf("%lld", &n); for (int i = 0; i <= n; i++) scanf("%lld",&a[i]); for (int j = n; j > 0; j--) a[j - 1] += a[j] / 2; if (a[0]) printf("Alice\n"); else printf("Bob\n"); } signed main() { int T; scanf("%lld",&T); while (T--) solve(); }
B-Dragon slayer
1.首先观察矩形区域数据范围就是一个搜索题,但这个题起点和终点都不是在整数点上,如果直接建图处理无疑是比较麻烦的,我们直接将原有的坐标放大两倍建图。
2.这个题直接对图跑dfs枚举路径,记录遇到几次墙显然是行不通的,因为我们在递归中不知道且无法判断是否会重复穿过一堵墙(可能会在墙的不同位置穿过)。暴力枚举路径显然行不通,这里我们观察墙的数据范围也是最大不超过15,那么直接暴力枚举墙的有无不就可以了吗(最大2^15种可能),枚举判断图起点和终点是否联通,不连通舍去,联通则不断更新找到墙的最大数max,当墙数达到max时,那么最少需删去的墙数就是总墙数-max,即题目所求。
3.wait,墙怎么枚举?dfs?dfs也行 我们这里介绍另一种枚举方法,二进制枚举法。(也不是说更好 单纯拓展一下视野。。。二进制枚举代码中有详细注释,这里简单解释一些位操作符:1 << k:表示1向右移k位,即2^k 、i >> j & 1:i向右移j位&1,如果结果为1代表第j位为1,反之为0 。)(后面再更新一份dfs代码)
#include<bits/stdc++.h> using namespace std; const int N = 30; typedef pair<int, int> PII; struct Node { int x1, y1, x2, y2; }wall[N]; int n, m, k, sx, sy, ex, ey; bool vis[N][N], g[N][N]; int dx[4] = { -1, 0, 1, 0 }, dy[4] = { 0, 1, 0, -1 }; int ans; int lowbit(int x) { return x & -x; } void add(int id) { for (int i = wall[id].x1; i <= wall[id].x2; i++) for (int j = wall[id].y1; j <= wall[id].y2; j++) g[i][j] = 1; } void del(int id) { for (int i = wall[id].x1; i <= wall[id].x2; i++) for (int j = wall[id].y1; j <= wall[id].y2; j++) g[i][j] = 0; } //简单bfs 判断联通 bool bfs() { queue<PII> q; q.push({ sx, sy }); memset(vis, 0, sizeof vis); vis[sx][sy] = true; while (q.size()) { pair<int, int> t = q.front(); //t类型名要和点的类型一样别忘了 q.pop(); int x = t.first, y = t.second; if (x == ex && y == ey) return 1; //如果找到终点,返回1。 for (int i = 0; i < 4; i++) { int a = x + dx[i], b = y + dy[i]; if (a == 2 * n || a == 0 || b == 2 * m || b == 0 || g[a][b] || vis[a][b]) continue; //注意不管是x还是y,0是不能达到的,最低也是1。 vis[a][b] = 1; q.push({ a, b }); } } return 0; //所有可以遍历的都遍历完了,没有找到终点,返回0。 } void solve() { cin >> n >> m >> k; ans = 0; memset(g, 0, sizeof(g)); cin >> sx >> sy; cin >> ex >> ey; sx = sx * 2 + 1, sy = sy * 2 + 1, ex = ex * 2 + 1, ey = ey * 2 + 1; for (int i = 0; i < k; i++) { int a, b, c, d; cin >> a >> b >> c >> d; wall[i] = { a * 2, b * 2, c * 2, d * 2 }; } for (int i = 0; i < (1 << k); i++) // 二进制枚举 每个二进制数每一位的数是0或者1可表示加墙或不加墙 { int cnt = 0; for (int t = i; t; t -= lowbit(t)) cnt++;// 墙的个数 if (cnt <= ans) continue; // 如果我们之前计算的答案比cnt多,说明删掉的墙更多了,无意义,直接continue for (int j = 0; j < k; j++) if (i >> j & 1) add(j); // 如果该位上是1加墙 if (bfs()) ans = cnt; //如果联通 更新cnt for (int j = 0; j < k; j++) if (i >> j & 1) del(j); // 如果该位上是1删墙(因为之前加过了要删掉不对下一次枚举造成影响) } cout << k - ans << endl; } int main() { int T; cin >> T; while (T--) solve(); }
C-Backpack
首先拿到题,明显是dp。那么对谁做dp(对象是什么),对价值肯定是不对的,问题是求异或和,那么正确的是对异或和。我们设dp[i][j][k],表示前i个物品中,容量为j的物品的最大异或和k是否存在。观察数据,三维的背包时间复杂度O(n3),肯定会超时。
朴素dp,超时代码。。。。
#include<bits/stdc++.h> using namespace std; int dp1[1050][1050], dp2[1050][1050];
//第一维滚动优化掉,只需要记录j和k。
//因为是01背包,判断异或总和有无的话和求最大值不太一样,这里我们需要两个二维数组dp1,dp2
//dp1记录选该物品之前的情况,dp2记录选该物品多出来的情况 该物品选完后再把dp2多出来的情况加到dp1中
//再把dp2全部清除进行下一次,相当于一个temp数组。
int main() { ios::sync_with_stdio(false); int T; cin >> T; int n, m; while (T--) { cin >> n >> m; int w, v; fill(dp1[0], dp1[0] + 1050 * 1050, 0); dp1[0][0] = 1; for (int i = 1; i <= n; i++) { fill(dp2[0], dp2[0] + 1050 * 1050, 0); cin >> v >> w; for (int j = 0; j <= m; j++) { for (int k = 0; k <= 1024; k++) { if (j - v >= 0 && dp1[j - v][k]) { dp2[j][k ^ w] = 1; } } } for (int j = 0; j <= m; j++) { for (int k = 0; k <= 1024; k++) { if (dp2[j][k]) { dp1[j][k] = 1; } } } } int flag = 0; for (int i = 1024; i >= 0; i--) { if (dp1[m][i]) { flag = 1; cout << i << endl; break; } } if (flag == 0) cout << -1 << endl; } }
那么我们怎么去优化呢?可以发现我们的数组里面只存了0或者1,可不可以用不开二维的数组,枚举两个变量,只用一维的枚举就能存体积和异或值呢,这里我们还是介绍一种二进制的处理方法,用二进制的位(bit)来存储某个体积值下的情况。c++stl里有现成的容器bitset,我们这里简单提一下。bitset<n> bst1; 代表bst是一个n位组成的对象,存储二进制数。我们开一个数组bitset<n> bst1[maxn]; 用数组下标存储异或值,数组里存储的二进制数的第x位代表体积为x,若该位为1、下标为y,则代表的是体积x下有最终的异或总和值y(异或值是下标)。
给出二进制存储体积优化后的代码,用c++bitset实现。
#include<bits/stdc++.h> using namespace std; const int N = 1100; bitset<N> f[N], g[N]; //g是一个临时数组用来记录选当前这个个物品时多出的情况 int n, m; int main() { ios::sync_with_stdio(false); int T; cin >> T; while (T--) { cin >> n >> m; for (int i = 0; i <= 1024; i++) f[i].reset(), g[i].reset();//将bitset全部置为0 f[0][0] = 1; for (int i = 1; i <= n; i++) { int w, v; cin >> v >> w; for (int j = 0; j < 1024; j++) { g[j] = f[j]; g[j] <<= v; //左移就是加体积 位记录体积的信息 1代表这种体积下异或总和j存在 0则不存在 } for (int j = 0; j < 1024; j++) { f[j] |= g[j ^ w]; //这一步比较对异或没有掌握好的萌新比较难理解 //建议多代几个数据进去 发现有a^b=c c^b=a这种规律 } } int flag = 0; for (int i = 1024; i >= 0; i--) if (f[i][m]) { flag = 1; cout << i << endl; break; } if (flag==0) cout << -1 << endl; } }
I-Laser
(努力更新中)