2015多校联赛第三场(部分题解)
1001. Magician (hdu5316)
这个题目用到线段树来合并区间,题目有句话的意思A beautiful subsequence is a subsequence that all the adjacent pairs of elves in the sequence have a different parity of position说的是相邻的两个元素必须有不同的奇偶位置,所有一个序列有四种可能的形式,奇...奇, 奇...偶,偶...奇, 偶...偶,这四种形式只是端点的奇偶性,所以线段树的每个节点就得有四个值,分别维护这四个值就行了,举个例子来说,根节点的偶偶的最大值取决于 左孩子的偶偶 + 右孩子的奇偶, 左孩子的偶奇 + 右孩子的偶偶, 左孩子的偶偶,右孩子的偶偶, 所以根节点的偶偶就等于他们当中的最大值,同理,其他的三个也是这样,剩下的另外一种操作就是更新点了。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include<iostream> #include <stdio.h> using namespace std; typedef long long LL; const int maxn = 100010; const LL inf = 1LL << 61; LL d[maxn]; struct Data{ LL s00, s01, s10, s11;//s00表示偶偶, s01表示偶奇, s10表示奇偶,s11表示奇奇 void init() { s00 = s01 = s10 = s11 = -inf;//初始化一定要为负无穷,因为可能有负数 } LL max_ele() { return max(max(s00, s01), max(s10, s11));//找出他们当中最大的 } }; struct tree{ Data data; }; tree sum[maxn * 4];//线段树 void checkmax(LL &a, const LL b) { if (a < b) a = b; } Data operator + (const Data &a, const Data &b) { Data ret; ret.s11 = max(a.s11 + b.s01, a.s10 + b.s11); checkmax(ret.s11, a.s11); checkmax(ret.s11, b.s11); ret.s10 = max(a.s11 + b.s00, a.s10 + b.s10); checkmax(ret.s10, a.s10); checkmax(ret.s10, b.s10); ret.s00 = max(a.s00 + b.s10, a.s01 + b.s00); checkmax(ret.s00, a.s00); checkmax(ret.s00, b.s00); ret.s01 = max(a.s00 + b.s11, a.s01 + b.s01); checkmax(ret.s01, a.s01); checkmax(ret.s01, b.s01); return ret; } void pushup(int rt) { sum[rt].data = sum[rt<<1].data + sum[rt<<1|1].data; } void build(int L, int R, int rt) { sum[rt].data.init(); if (L == R) { if (L & 1) sum[rt].data.s11 = d[L]; else sum[rt].data.s00 = d[L]; return; } int mid = (L + R) / 2; build(L, mid, rt<<1); build(mid + 1, R, rt<<1|1); pushup(rt); } void update(int pos, LL value, int L, int R, int rt) { if (L == pos && R == pos) { if (pos & 1) sum[rt].data.s11 = value; else sum[rt].data.s00 = value; return; } int mid = (L + R) / 2; if (pos <= mid) update(pos, value, L, mid, rt<<1); else update(pos, value, mid + 1, R, rt<<1|1); pushup(rt); } Data query(int l, int r, int L, int R, int rt) { if (l <= L && r >= R) { return sum[rt].data; } int mid = (L + R) >> 1; Data lson, rson; lson.init(); rson.init(); if (l <= mid) lson = query(l, r, L, mid, rt<<1); if (r > mid) rson = query(l, r, mid + 1, R, rt<<1|1); return lson + rson; } int main() { int T, n, m; scanf("%d", &T); while (T--) { scanf("%d %d", &n ,&m); for (int i = 1; i <= n; i++) scanf("%I64d", &d[i]); build(1, n, 1); int op, a, b; for (int i = 0; i < m; i++) { scanf("%d %d %d", &op, &a, &b); if (op) update(a, (LL)b, 1, n, 1); else { Data ans = query(a, b, 1, n, 1); printf("%I64d\n", ans.max_ele()); } } } return 0; }
1002.RGCDQ (hdu5317)
这道题F(x)表示x当中素因子的个数,由于x最大1000000,所以F(x)最多不超过8个,这个可以拿出前几个素数乘一下试试,2*3*5*7*11*13*17*19=9699690>1000000所以可以先预处理出来每个数的F(x),然后求出他的前缀和,用O(1)的复杂度去查询,求F(x)的时候注意不要除着求,要直接像筛素数一样去求。具体见代码
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include <cstdio> #include <string.h> const int maxn = 1000010; int prime[maxn];//标记筛选的素数 int f[maxn];//F(x) int S[maxn][9];//前缀和,因为一共最多可能有8个,所以开个8的数组足够了,0的位置为空 int pr[100000];//保存素数 void init() { memset(prime, true, sizeof(prime)); prime[1] = prime[0] = false; for (int i = 2; i * i <= 1000000; i++)//素数筛选 { if (prime[i]) for (int j = i + i; j <= 1000000; j += i) prime[j] = false; } int tot = 0; for (int i = 2; i <= 1000000; i++) if (prime[i]) pr[tot++] = i;//将筛选出来的素数放到pr中 for (int i = 0; i < tot; i++)//求F(x),这里很关键,不能放到素数筛选的过程当中去,因为有大素数 { for (int j = pr[i]; j <= 1000000; j += pr[i])//所有包含pr[i]这个素因子的都要加1 f[j]++; } for (int i = 2; i <= 1000000; i++) { for (int j = 1; j <= 8; j++) { S[i][j] = (j == f[i]) ? S[i - 1][j] + 1 : S[i - 1][j];//求出前缀和 } } } int main() { init(); int T, L, R; scanf("%d", &T); while (T--) { scanf("%d %d", &L, &R); int ans = 1; for (int i = 8; i > 0; i--) { if (S[R][i] - S[L - 1][i] > 1)//找出这个区间最大的来 { ans = i; break; } } printf("%d\n", ans); } return 0; }
1003.The Goddess Of The Moon (hdu5318)
给出n条链子,让你选择m个,问一共方案有多少种。因为m比较大(1e9),所以用矩阵快速幂来解。构造一个矩阵表示任意两个之间是否可以链接,如果可以链接的话就是1,不能链接的话就是0,这个题有点模糊。大致知道一点意思,但是细推的话,状态转移方程不太清楚。。。看题解上写S[i][j]表示前i个,以j结尾的方案数,估计意思就是当链接i个的时候,最后以j结尾的方案数,他就应该等于前i - 1个所有能链接j,并且j链接在后面的个数,对于每一个j,对应一列,所以,就是一个矩阵,而S[i][j]可以由S[i - 1][]来推,那么就构成了矩阵连乘的条件,所以就能用矩阵快速幂了。这个题还是不是完全透彻的理解。如果哪位大牛路过,恳请指点。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include<iostream> #include <cstdio> #include <cstring> #include <vector> #include <algorithm> using namespace std; typedef long long LL; const int maxn = 52; const int mod = 1000000007; int sz; struct Mat{ int mat[maxn][maxn]; void init() { memset(mat, 0, sizeof(mat)); } }; Mat operator * (const Mat &a, const Mat &b)//矩阵乘法 { Mat ret; ret.init(); for (int i = 0; i < sz; i++) { for (int j = 0; j < sz; j++) { ret.mat[i][j] = 0; for (int k = 0; k < sz; k++) { ret.mat[i][j] += 1LL * a.mat[i][k] * b.mat[k][j] % mod; ret.mat[i][j] %= mod; } } } return ret; } Mat operator ^ (Mat a, int b)//矩阵快速幂 { Mat ans; for (int i = 0; i < sz; i++) for (int j = 0; j < sz; j++) ans.mat[i][j] = (i == j ? 1 : 0); while (b) { if (b & 1) ans = ans * a; a = a * a; b >>= 1; } return ans; } bool connect(int a, int b)//判断a链和b链是否能连接 { int dig1[maxn], dig2[maxn]; int tot1 = 0, tot2 = 0; for (; a; a /= 10) dig1[tot1++] = a % 10; for (; b; b /= 10) dig2[tot2++] = b % 10; int ans = 1; for (int len = 1; len <= min(tot1, tot2); len++) { bool flag = false; for (int i = len - 1; i >= 0; i--) { if (dig1[i] != dig2[tot2 - (len - i)]) { flag = true; break; } } if (!flag) ans = len; } if (ans > 1) return true; return false; } int solve(vector <int> num, int n) { vector<int> tmp = num; sort(tmp.begin(), tmp.end()); tmp.erase(unique(tmp.begin(), tmp.end()), tmp.end());//去重 int m = (int)tmp.size(); Mat A, B; sz = m; for (int i = 0; i < m; i++) { for (int j = 0; j < m; j++) B.mat[i][j] = connect(tmp[i], tmp[j]);//构造转移矩阵 } A.init(); for (int i = 0; i < m; i++) A.mat[0][i] = 1; //初始第一行全为1 B = B ^ (n - 1); A = A * B; int ans = 0; for (int i = 0; i < m; i++) { ans += A.mat[0][i]; ans %= mod; } return ans; } int main() { int T, n, m; scanf("%d", &T); while (T--) { scanf("%d%d", &n, &m); vector<int> v(n); int tmp; for (int i = 0; i < n; i++) { scanf("%d", &v[i]); } printf("%d\n", solve(v, m)); } return 0; }
1004.Painter (hdu5319)
这个题属于模拟题,红色只能正向斜着刷(\),蓝色只能反向斜着刷(/),被正向反向刷了两次的点是绿色,给出最终状态求出最少经过多少步能到这种状态。直接枚举每一个点就行。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include <cstdio> #include <cstring> const int maxn = 60; char a[maxn][maxn]; int mp[maxn][maxn]; int m; int ans; int n; const int dir[2][2] = {1, 1, 1, -1}; void work(int x, int y, int color) { mp[x][y] -= color; while (true) { x += dir[color - 1][0]; y += dir[color - 1][1]; if (x >= 0 && y >= 0 && x < n && y < m && (mp[x][y] == color || mp[x][y] == 3)) mp[x][y] -= color; else break; } } int main() { int T; scanf("%d", &T); while (T--) { scanf("%d", &n); for (int i = 0; i < n; i++) { scanf("%s", a[i]); m = strlen(a[i]); for (int j = 0; j < m; j++) { if (a[i][j] == 'R')//转化成数字便于计算 mp[i][j] = 1; else if (a[i][j] == 'B') mp[i][j] = 2; else if (a[i][j] == 'G') mp[i][j] = 3; else mp[i][j] = 0; } } ans = 0; for (int i = 0; i < n; i++) { for (int j = 0; j < m; j++) { if (mp[i][j] == 0) continue; if (mp[i][j] == 1) work(i, j, 1); else if (mp[i][j] == 2) work(i, j, 2); else { work(i, j, 1); work(i, j, 2); ans++; } ans++; } } printf("%d\n", ans); } return 0; }
1008.Solve this interesting problem (hdu5323)
此题给出线段树的区间定义,给出一个节点的左区间端点和右区间端点,求最小n,其中n为根节点的右端点。
从当前节点(L, R)往他的父节点走的话有四种路线,分别是:当他为左孩子的时候父节点的区间端点(L, R + (R - L + 1))或者(L, R + (R - L + 1) - 1),当他为右孩子的时候父节点的区间为(L - (R - L + 1), R)或者(L - (R - L + 1) - 1, R),因为线段树节点的左右孩子区间长度要不 相等,要不 左孩子比右孩子大1, 所以根据这个结论所以就可以得出上面四种方式,其中(R - L + 1)为区间长度,题目中还给了一个关键的就是L / (R - L + 1) <= 2015,这个就说明 L <= 2015 * len, 其中len为区间长度,线段树中len越小,也就是左右端点靠的越近,他的位置越深,就比如叶子节点的len为0,所以他在最下面,这就说明了一个问题就是当这个树取最深的顶点的时候,那么len取1,所以L<=2015, 并且从2015开始往上找到0的时候最多22层,当是左孩子的时候11层,右孩子11层。所以搜索这四种路径。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include<iostream> #include <cstdio> using namespace std; typedef long long LL; const LL inf = 1LL << 60; LL n; void search(LL L, LL R) { if (L < 0 || L > R) return; if (L == 0) { n = min(n, R); return ; } int len = R - L + 1; if (L < len)//剪枝,因为如果l小于区间长度的话,那么他的父节点的左端点一定为负值,所以不符合 return; search(L, R + len);//左孩子 if (len != 1) search(L, R + len - 1);//左孩子 search(L - len, R);//右孩子 search(L - len - 1, R);//右孩子 } LL work(int L, int R) { n = inf; search(L, R); return n == inf ? -1 : n; } int main() { int L, R; while (~scanf("%d %d", &L, &R)) { printf("%lld\n", work(L, R)); } return 0; }
1011. Work (hdu5326)
官方签到题。直接dfs即可
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include <cstdio> #include <cstring> #include <vector> using namespace std; const int maxn = 110; vector<int> mp[maxn]; int in[maxn]; int n, k; int siz[maxn]; int dfs(int u) { siz[u] = 1; int len = mp[u].size(); for (int i = 0; i < len; i++) { siz[u] += dfs(mp[u][i]); } return siz[u]; } int main() { while (~scanf("%d%d", &n, &k)) { memset(siz, 0, sizeof(siz)); memset(in, 0, sizeof(in)); for (int i = 0; i <= n; i++) mp[i].clear(); int a, b; for (int i = 1; i < n; i++) { scanf("%d%d", &a, &b); mp[a].push_back(b); in[b]++; } int root = 0; for (int i = 1; i <= n; i++) if (!in[i]) root = i; dfs(root); int ans = 0; for (int i = 1; i <= n; i++) if (siz[i] - 1 == k) ans++; printf("%d\n", ans); } return 0; }
其它题目未补完。。。