这次运气比较好,做出两题。本来是冲着第3题可以cdq分治做的,却没想出来,明天再想好了。
A. On Number of Decompositions into Multipliers
题意:n个数a1,a2, a3...an求n个数相乘与a1*a2*a3*a4...an相等的排列个数。
分析:首先应该对ai分解质因数,求出所有ai中质因数及个数,枚举排列中每个数包含的质因数个数,例如质因数qi,有ni个,相应的排列中数包含质因数qi个数设为x1,x2,....xn, x1+x2+x3..+xn = ni , 那么对于qi共有C(ni+n-1, n-1)种情况。简单来说就是将ni分成n部分,这样想:有ni个球,需要分成n部分,也就是在n个球中插入n-1根木棍,这样分成n部分就相当于ni+n-1中选n-1个位置。最后把所有质因子可能的划分情况相乘起来就行了。
注意:分解质因数最后一个质因数可能很大!
代码:
1 #include <bits/stdc++.h> 2 #define in freopen("solve_in.txt", "r", stdin); 3 #define bug(x) printf("Line %d : >>>>>>> ", (x)); 4 #define pb push_back 5 6 using namespace std; 7 typedef long long LL; 8 typedef map<int, int> Mps; 9 const int M = (int)1e9+7; 10 const int maxn = 555; 11 const int maxm = (int)1e5 + 100; 12 LL inv[maxn]; 13 int a[maxn]; 14 Mps Div; 15 vector<int> dv; 16 17 LL powmod(LL a, LL b) { 18 LL res = 1; 19 while(b) { 20 if(b&1) 21 res = (res*a)%M; 22 b >>= 1; 23 a = (a*a)%M; 24 } 25 return res; 26 } 27 void getInv(int n) { 28 for(int i = 1; i < n; i++) { 29 inv[i] = powmod(i, M-2); 30 } 31 } 32 void getDiv(int x) { 33 int m = (int)sqrt(x+.5); 34 for(int i = 2; i <= m; i++) { 35 if(x%i == 0) { 36 if(Div[i] == 0) 37 dv.pb(i); 38 while(x%i == 0) { 39 Div[i]++; 40 x/=i; 41 } 42 } 43 } 44 if(x > 1) { 45 if(Div[x] == 0) 46 dv.pb(x); 47 Div[x]++; 48 } 49 } 50 LL nCr(LL n, LL m) { 51 LL res = 1; 52 for(int i = 0; i < m; i++) { 53 res = res*(n-i)%M*inv[i+1]%M; 54 if(res < 0) 55 res += M; 56 } 57 return (res + M)%M; 58 } 59 int main() { 60 61 getInv(maxn); 62 int n; 63 scanf("%d", &n); 64 for(int i = 1; i <= n; i++) { 65 scanf("%d", a+i); 66 getDiv(a[i]); 67 } 68 LL ans = 1; 69 for(int i = 0; i < (int)dv.size(); i++) { 70 int x = dv[i]; 71 ans = ans*nCr(Div[x]+n-1, n-1)%M; 72 } 73 cout<<ans<<endl; 74 return 0; 75 }
B. On Sum of Fractions
题意:给定n, u(i)表示不超过i的最大质数, v(i)表示大于i的最小质数。求对于所有的2<= i <= n , sum{1/(u(i)*v(i)}, 最简分式结果。
分析:
列出n的前几个数的1/u(i)*v(i),发现对于两个相邻素数pi, pi+1间的数i, 1/u(i)*v(i)结果是一样的。也就是说 对于 pi <= x < pi+1, sum{1/u(x)v(x)},化简后就是, 1/u(x)-1/v(x), 得到这个结论后,每次找到大于n的一个素数pi+1,结果就变成两部分, x < pi, 和 pi <= x < n, 第一部分化简就是1/2-1/pi ,第二部分:n-pi+1/(pi*pi+1), 两者之和合并一下就会得到一个表达式:2*(n+1)-2*(pi+pi+1)-pi*pi+1/(2*pi*pi+1)。求质数,用Miller-Rabin法判断前后两个质数就可以了。
代码:
1 #include <bits/stdc++.h> 2 #define in freopen("solve_in.txt", "r", stdin); 3 #define bug(x) printf("Line %d : >>>>>>> ", (x)); 4 #define pb push_back 5 6 using namespace std; 7 typedef pair<int, int> PII; 8 typedef long long LL; 9 typedef map<int, int> Mps; 10 11 LL powmod(LL a, LL b, LL c) { 12 LL res = 1; 13 while(b) { 14 if(b&1) res = res*a%c; 15 b >>= 1; 16 a = (a*a)%c; 17 } 18 return res; 19 } 20 bool test(int n, int a, int d) { 21 if(n == 2)return true; 22 if(n == a) return true; 23 if((n&1) == 0) return false; 24 while(!(d&1)) d >>= 1; 25 int t = powmod(a, d, n); 26 while((d != n-1) && (t != 1) && (t != n-1)) { 27 t = (LL)t*t%n; 28 d <<= 1; 29 } 30 return (t == n-1) || (d&1) == 1; 31 32 } 33 bool isPrime(int n) { 34 if(n < 2) return false; 35 int a[] = {2, 3, 61}; 36 for(int i = 0; i <= 2; i++) if(!test(n, a[i], n-1)) return false; 37 return true; 38 } 39 pair<LL, LL> getPrime(LL n) { 40 LL x = n; 41 x++; 42 PII ans; 43 while(1) { 44 if(isPrime(x)) { 45 ans.first = x; 46 break; 47 } 48 x++; 49 } 50 x = n; 51 while(1) { 52 if(isPrime(x)) { 53 ans.second = x; 54 break; 55 } 56 x--; 57 } 58 return ans; 59 } 60 LL getGcd(LL a, LL b) { 61 return !b ? a : getGcd(b, a%b); 62 } 63 int main() { 64 65 int T; 66 int n; 67 for(int t = scanf("%d", &T); t <= T; t++) { 68 scanf("%d", &n); 69 if(n == 2) { 70 puts("1/6"); 71 } else { 72 PII u = getPrime(n); 73 LL x = u.first, y = u.second; 74 LL a = 2*(n+1)-2*(x+y)+x*y; 75 LL b = 2*x*y; 76 LL c = getGcd(a, b); 77 printf("%I64d/%I64d ", a/c, b/c); 78 } 79 } 80 return 0; 81 }
C. On Changing Tree
题意:一棵n个结点的树,n <= 3*10^5, 给定q个操作。两种类型:
1 v x k ,给v及v子树上结点增加x-i*k,i表示子树上离v的距离
2 v,询问v结点上的值。
分析:
经过一上午的使劲YY,终于想到了CDQ分治的做法,其实比普通树状数组做法要慢,而且难写(也可能是我写得太挫了),复杂度最坏可能达到O(n*log^2n).1200ms能过也算是走运。
简要思路:首先将所有操作按照v结点的深度由大到小拍个序,具体分治的时候,对于原始编号在[l,r]的操作,要将它分成两部分,<= m 和>m 部分,然后考虑编号<=m,的修改操作(操作1) 对编号>m的询问操作(操作2)影响,其实也就是用操作1对所有影响的的要询问的结点的值更新。但是又不能针对每一<=m的操作1,去更新可能影响到的>m操作2, 这样复杂度会很高,那么问题关键就在于怎么按照深度的顺序将操作1作用累积起来,每次更新对结点v影响时,只需要考虑v到根节点的路径上最靠近v的一个操作1。
合并两个同一棵子树但是不同深度的操作1的影响时,例如1 v1 x1 k1, 1 v2 x2 k2, dep[v1] <= dep[v2],将v1合并到v2时,新的x = x1 - (dep[v2]-dep[v1])*k1 + x2, 新的k = k1+k2。同时在询问和更新某个结点v的上方且最靠近的v的操作1对应的结点时是利用线段树维护的,具体是将子树对应的叶节点一一编号,然后看做区间,将对应结点更新时,只需要将该节点的子树包含的叶子节点的区间进行更新,例如v对应叶子节点编号[L,R]只需要将一个标记cover设置为v,表示该段叶子结点对应的子树的根为最新的。这样做的依据是树中每棵子树包含的叶子结点都不同,但是可以连续编号的。
说的有点复杂,还是普通方法简单,这个也只是为了练习CDQ分治。||--
代码:
1 /*Time 2014 09 01 , 10:22 2 3 */ 4 #include <bits/stdc++.h> 5 #define in freopen("solve_in.txt", "r", stdin); 6 #define bug(x) printf("Line %d : >>>>>>> ", (x)); 7 #define lson rt<<1, l, m 8 #define rson rt<<1|1, m+1, r 9 #define inf 0x0f0f0f0f 10 #define pb push_back 11 12 using namespace std; 13 typedef long long LL; 14 const int maxn = 3*(int)1e5 + 100; 15 const int M = (int)1e9 + 7; 16 vector<int> g[maxn]; 17 int ll[maxn], rr[maxn], dep[maxn], rk[maxn], t1[maxn], t2[maxn]; 18 int cover[maxn<<2]; 19 int ans[maxn], x[maxn], k[maxn]; 20 int qN; 21 int cnt, ct, vis[maxn]; 22 23 struct Query 24 { 25 int type; 26 int v, x, k; 27 28 } qu[maxn]; 29 bool cmp(const int &a, const int &b) 30 { 31 return dep[qu[a].v] < dep[qu[b].v]; 32 } 33 void dfs(int u) 34 { 35 if(g[u].size() == 0) 36 { 37 ll[u] = rr[u] = ++cnt; 38 return; 39 } 40 else ll[u] = inf, rr[u] = 0; 41 for(int i = 0; i < g[u].size(); i++) 42 { 43 int v = g[u][i]; 44 dep[v] = dep[u]+1; 45 dfs(v); 46 ll[u] = min(ll[u], ll[v]); 47 rr[u] = max(rr[u], rr[v]); 48 } 49 } 50 void pushDown(int rt) 51 { 52 if(cover[rt] != -1) 53 { 54 cover[rt<<1] = cover[rt<<1|1] = cover[rt]; 55 cover[rt] = -1; 56 } 57 } 58 void update(int rt, int l, int r, int L, int R, int u) 59 { 60 if(L <= l && R >= r) 61 { 62 cover[rt] = u; 63 return; 64 } 65 pushDown(rt); 66 int m = (l+r)>>1; 67 if(L <= m) 68 update(lson, L, R, u); 69 if(R > m) 70 update(rson, L, R, u); 71 } 72 void query(int rt, int l, int r, int L, int R) 73 { 74 if(cover[rt] != -1) 75 { 76 qN = cover[rt]; 77 return; 78 } 79 pushDown(rt); 80 int m = (l+r)>>1; 81 if(L <= m) 82 query(lson, L, R); 83 if(qN == -1 && R > m) 84 query(rson, L, R); 85 return; 86 } 87 void solve(int l, int r) 88 { 89 if(l >= r) 90 { 91 return; 92 } 93 int m = (l+r)>>1; 94 t1[0] = t2[0] = 0; 95 for(int i = l; i <= r; i++) 96 { 97 if(rk[i] <= m) 98 { 99 t1[++t1[0]] = rk[i]; 100 } 101 else 102 { 103 t2[++t2[0]] = rk[i]; 104 } 105 } 106 queue<int> q1, q2; 107 for(int i = l; i <= r; i++) 108 { 109 if(i <= m) 110 { 111 rk[i] = t1[i-l+1]; 112 if(qu[rk[i]].type == 1) 113 { 114 q1.push(rk[i]); 115 } 116 } 117 else 118 { 119 rk[i] = t2[i-m]; 120 if(qu[rk[i]].type == 2) 121 q2.push(rk[i]); 122 } 123 } 124 cover[1] = 0; 125 ct++; 126 while(!q2.empty()) 127 { 128 int xx = q2.front(); 129 q2.pop(); 130 int v1 = qu[xx].v; 131 while(!q1.empty() && dep[qu[q1.front()].v] <= dep[v1]) 132 { 133 int yy = q1.front(); 134 q1.pop(); 135 int v2 = qu[yy].v; 136 int x2 = qu[yy].x; 137 int k2 = qu[yy].k; 138 qN = -1; 139 query(1, 1, cnt, ll[v2], rr[v2]); 140 x[v2] = (x2+x[qN])%M-(LL)(dep[v2]-dep[qN])%M*(LL)k[qN]%M; 141 if(x[v2] < 0) x[v2] += M; 142 k[v2] = (k2+k[qN])%M; 143 update(1, 1, cnt, ll[v2], rr[v2], v2); 144 } 145 qN = -1; 146 query(1, 1, cnt, ll[v1], rr[v1]); 147 ans[xx] = ((ans[xx]+x[qN])%M-(LL)(dep[v1]-dep[qN])%M*(LL)k[qN]%M+M)%M; 148 } 149 solve(l, m); 150 solve(m+1, r); 151 } 152 int main() 153 { 154 155 int n, q; 156 scanf("%d", &n); 157 for(int i = 1; i <= n-1; i++) 158 { 159 int u = i+1, v; 160 scanf("%d", &v); 161 g[v].pb(u); 162 } 163 dep[1] = 1; 164 dfs(1); 165 scanf("%d", &q); 166 for(int i = 1; i <= q; i++) 167 { 168 rk[i] = i; 169 scanf("%d", &qu[i].type); 170 if(qu[i].type == 1) 171 { 172 scanf("%d%d%d", &qu[i].v, &qu[i].x, &qu[i].k); 173 } 174 else scanf("%d", &qu[i].v); 175 } 176 sort(rk+1, rk+q+1, cmp); 177 // for(int i = 1; i <= q; i++) 178 // cout<<qu[rk[i]].v<<endl; 179 solve(1, q); 180 for(int i = 1; i <= q; i++) 181 { 182 if(qu[i].type == 2) 183 { 184 printf("%d ", ans[i]); 185 } 186 } 187 return 0; 188 }