2013 Multi-University Training Contest 6
HDU-4655 Cut Pieces
题意:有N个格子能够被涂色,每个格子能够涂1-ai 种颜色,当N=6,涂色方案:112233 认为方案中共有3个颜色块;涂色方案:121212 认为方案中共有6个颜色块。所谓颜色块也就是整个涂色方案中相同连续的颜色被视作为一个颜色块。问如何安排 ai 的排列使得所有方案颜色块之和最大。
分析:从反面求解,首先能够产生的方案数一共有a1*a2*a3*...*an种,每种方案在没有相邻颜色认为为同一颜色块的情况下,颜色块的数量均为n。那么如何减掉重复计算的。可以考虑到任何一个k长连续颜色块,若统计其相邻格子颜色相同数量为k-1,如果减去这些相邻格子数正好满足同一颜色块只被统计一次。
#include <cstdlib> #include <cstring> #include <cstdio> #include <algorithm> using namespace std; typedef long long LL; const int N = int(1e6)+5; const int mod = int(1e9)+7; int n, seq[N]; int f[N], t[N]; void solve() { int tot = 1, rep = 0; sort(seq, seq+n); f[0] = seq[0], t[n-1] = seq[n-1], t[n] = 1; for (int i = 1; i < n; ++i) { f[i] = (1LL*f[i-1]*seq[i]) % mod; } for (int i = n-2; i >= 0; --i) { t[i] = (1LL*t[i+1]*seq[i]) % mod; } for (int i = 0; i < n; ++i) { tot = (1LL*tot*seq[i]) % mod; } for (int i = n-1; i > n/2; --i) { rep = (rep + 2LL*f[i-1]*t[i+1]) % mod; } if (!(n&1)) rep = (rep + 1LL*f[n/2-1]*t[n/2+1]) % mod; printf("%d\n", ((1LL*tot*n-rep)%mod+mod)%mod); } int main() { int T; scanf("%d", &T); while (T--) { scanf("%d", &n); for (int i = 0; i < n; ++i) { scanf("%d", &seq[i]); } solve(); } return 0; }
HDU-4657 Find Permutation
题意:给定一个序列b,求a、c两个序列,满足:(a[i]+b[i]) % n = c[i],其中a、c 数组均为[0, n-1]的排列。
分析:采用构造的方式,初始化a[i] = c[i] = i,同时维护一个记录a中元素所在位置的数组。对于枚举到 i 位置保持1 到 i-1 中(a[i]+b[i]) % n = c[i]成立。对于 i 号位置,根据b[i]和c[i]计算出所需要的a[j],通过 a 数组的索引数组快速找到所需元素的下标 j,如果 j 大于等于枚举的位置,那么继续往后枚举 i,因为该位置我们还未做处理;否则注定要破坏之前已经维护好的关系,因此需要交换一些元素,并且重新维护好原来的值。
#include <cstdlib> #include <cstring> #include <cstdio> #include <algorithm> using namespace std; const int N = int(1e5)+5; int n; int a[N], b[N], c[N], idxa[N]; int main() { int _; scanf("%d", &_); while (_--) { scanf("%d", &n); for (int i = 0; i < n; ++i) scanf("%d", &b[i]); for (int i = 0; i < n; ++i) a[i] = c[i] = idxa[i] = i; for (int i = 0; i < n; ++i) { int cur = i; while ((a[cur]+b[cur])%n != c[cur]) { // 当前位不匹配 int nxt = idxa[(c[cur]-b[cur]+n)%n]; // 获得要调整的位置 swap(a[cur], a[nxt]); // 将a数组进行数字的调整 swap(idxa[a[cur]], idxa[a[nxt]]); if (nxt > i) break; // 需要调整位置还尚未更新,否则打破了之前维护好的结果 swap(c[i+1], c[nxt]); cur = nxt; } } for (int i = 0; i < n; ++i) printf(i == 0 ? "%d" : " %d", a[i]); puts(""); for (int i = 0; i < n; ++i) printf(i == 0 ? "%d" : " %d", c[i]); puts(""); } return 0; }
HDU-4658 Integer Partition
题意:求一个整数(N<=100000)的整数分拆数,要求每一种分拆方案中不能够存在某个数出现K次或K次以上。例如N=4,K=2,那么只有4 = 4 一种分拆方式。
整数分拆:http://zh.wikipedia.org/wiki/%E6%95%B4%E6%95%B8%E5%88%86%E6%8B%86
五边形数定理:http://zh.wikipedia.org/wiki/%E4%BA%94%E9%82%8A%E5%BD%A2%E6%95%B8%E5%AE%9A%E7%90%86
欧拉函数(复变函数):http://zh.wikipedia.org/wiki/%E6%AD%90%E6%8B%89%E5%87%BD%E6%95%B8_(%E8%A4%87%E8%AE%8A%E5%87%BD%E6%95%B8)
母函数:http://zh.wikipedia.org/wiki/%E6%AF%8D%E5%87%BD%E6%95%B0
五角数:http://zh.wikipedia.org/wiki/%E5%BB%A3%E7%BE%A9%E4%BA%94%E9%82%8A%E5%BD%A2%E6%95%B8#.E5.BB.A3.E7.BE.A9.E4.BA.94.E9.82.8A.E5.BD.A2.E6.95.B8
#include <cstdlib> #include <cstdio> #include <cstring> #include <algorithm> using namespace std; const int mod = int(1e9)+7; const int N = int(1e5); int q[N+5], p[N+5]; // 其中q[i]表示欧拉函数的第i次方的系数,其值为广义五边形数 // p[i]为整数 i 的拆分方案数 int n, k; void prepare() { int a, b, sign; q[0] = 0; for (int i = 1, j = 1; ; ++i, j+=2) { sign = (i & 1) ? -1 : 1; q[j] = sign * (3*i*i-i) / 2; q[j+1] = sign * (3*i*i+i) / 2; if (abs(q[j+1]) >= N) break; } // 求出欧拉函数的指数项,并且保存好符号 p[0] = 1, p[1] = 1, p[2] = 2; for (int i = 3; i <= N; ++i) { for (int j = 1; ; ++j) { sign = (j & 1) ? 1 : -1; a = (3*j*j-j)/2, b = (3*j*j+j)/2; if (a <= i) p[i] = (p[i]+sign*p[i-a]) % mod; else break; if (b <= i) p[i] = (p[i]+sign*p[i-b]) % mod; else break; } } // O(n^1.5)计算出所有整数分拆系数p[] } int solve(int n, int k) { int sign, ret = 0; for (int i = 0; ; ++i) { // 由于欧拉函数的稀疏性,因此枚举欧拉函数的指数项与对应的整数分拆母函数结合 if (k * abs(q[i]) > n) break; sign = q[i] >= 0 ? 1 : -1; ret = (ret + sign*p[n-k*abs(q[i])]) % mod; } return (ret + mod) % mod; } int main() { prepare(); int T; scanf("%d", &T); while (T--) { scanf("%d %d", &n, &k); printf("%d\n", solve(n, k)); } return 0; }
HDU-4661 Message Passing
题意:给定一棵树,N个节点,每个节点都拥有一个唯一的信息,现在规定没一个时刻只能够由某一个人将信息传送给相邻的节点,每次传送能够传递他所收到的所有信息。问使得传递时间最短的传送方案共有多少种?
分析:很明显,当所有的信息都汇聚到一个人,然后再由这一个人传送出去是最优的,也就是每条边被传递两次。选择一个点作为根节点,然后通过树形dp得到方案数。在选定了根之后,每颗子树的根均为收到子节点的信息后再向上层传递,在统计多棵子树的时候要考虑到一些组合情况。做完之后再从根开始往下更新一次,目的是得到以每个节点为根的方案数。
#include <cstdlib> #include <cstring> #include <cstdio> #include <algorithm> #pragma comment(linker, "/STACK:1024000000,1024000000") using namespace std; typedef long long LL; const int mod = int(1e9)+7; const int N = 1000005; int n; int num[N], dp[N]; int f[N]; // 阶乘 // num[i]记录i节点在指定1为根下有多少节点 // dp[i]表示i节点在指定1为根下有多少种信息汇集的方式 struct Node { int v, next; }e[N]; int head[N], idx; void prepare() { f[0] = 1; for (int i = 1; i < N; ++i) { f[i] = (1LL*f[i-1]*i) % mod; } } void insert(int a, int b) { e[idx].v = b, e[idx].next = head[a]; head[a] = idx++; } int pow(int a, int b) { int ret = 1; while (b) { if (b & 1) ret = (1LL*ret*a) % mod; b >>= 1; a = (1LL*a*a) % mod; } return ret; } int inv(int x) { // 逆元 return pow(x, mod-2); } int C(int m, int n) { // 计算组合数c(m, n) return (1LL*f[n]*inv((1LL*f[m]*f[n-m])%mod))%mod; } void dfs1(int p, int u) { // 从下往上更新某个节点为根的信息汇集方案数 if (head[u] == -1) { dp[u] = 1; num[u] = 1; return; } int tot = 0, tmp = 1; for (int i = head[u]; ~i; i = e[i].next) { int v = e[i].v; if (v == p) continue; dfs1(u, v); tot += num[v]; tmp = (1LL*tmp*((1LL*C(num[v], tot)*dp[v])%mod))%mod; } dp[u] = tmp; num[u] = tot +1; } void dfs2(int p, int u) { for (int i = head[u]; ~i; i = e[i].next) { int v = e[i].v; if (v == p) continue; dp[v] = (1LL*(1LL*dp[u]*num[v])%mod*inv(num[u]-num[v]))%mod; num[v] = num[u]; if (head[v] != -1) dfs2(u, v); } } int main() { prepare(); int T; scanf("%d", &T); while (T--) { int a, b; scanf("%d", &n); memset(head, 0xff, sizeof (head)); idx = 0; for (int i = 1; i < n; ++i) { scanf("%d %d", &a, &b); insert(a, b), insert(b, a); } dfs1(0, 1), dfs2(0, 1); int ret = 0; for (int i = 1; i <= n; ++i) { ret = (1LL*ret+(1LL*dp[i]*dp[i]%mod))%mod; } printf("%d\n", ret); } return 0; }
HDU-4662 MU Puzzle
题意:一个字符串初始化为MI,有三种操作:1:将M之后的字符串加倍或者;2:III变成U;3:UU变为空。要求判定一个字符串能否由MI变化而来。
分析:只需要把所有的字符变为 I 之后,判定 I 是否为2的幂即可。由于UU可以变为空,因此设U的个数为A,I 的个数为B,判定3A+B+6x = 2^k是否存在解。做法是两边除2然后同时对3取模。由于2的幂的3的余数只有1、2两个,因此只要不是3的倍数都行。
#include <cstdlib> #include <cstring> #include <cstdio> #include <algorithm> using namespace std; const int N = 1000005; char str[N]; int main() { int T; scanf("%d", &T); while (T--) { scanf("%s", str); int len = strlen(str); int tot = 0, mnum = 0; if (!strcmp(str, "MI")) { puts("Yes"); continue; } for (int i = 0; i < len; ++i) { if (str[i] == 'I') tot += 1; else if (str[i] == 'U') tot += 3; else mnum++; } if (mnum > 1 || str[0] != 'M') { puts("No"); continue; } if ((tot & 1) || (tot/2) % 3 == 0) puts("No"); else puts("Yes"); } return 0; }