2017 Bangladesh National High School Programming Contest ( National Round, Senior Group ), NHSPC 2017 题解
【题目链接】
A. Charm Is Not Always Enough
模拟一下就可以了。
#include <bits/stdc++.h> using namespace std; int T; int main() { scanf("%d", &T); while(T --) { int n, m; long long ans = 0; scanf("%d%d", &n, &m); while(n --) { int x; scanf("%d", &x); x = x % m; if(x == 0) continue; ans = ans + 1LL * (m - x); } cout << ans << endl; } return 0; }
B. Max and Alexis Plan to Conquer the World
打表。
设比例为$h$,可以发现$x$天之后的数量等于$n$乘上一个关于$h$的某种前缀和。
$h$只有$100$种,可以把每一种的前缀和都计算好,每组数据二分一下即可。
#include <bits/stdc++.h> using namespace std; double h[105][4500]; void init() { for(int i = 1; i <= 100; i ++) { h[i][0] = 1.0; for(int t = 1; t < 4500; t ++) { h[i][t] = h[i][t - 1] + h[i][t - 1] * i / 100; } //printf("%lf\n", h[i][4499]); } } int main() { init(); int T ; scanf("%d", &T); int cas = 1; while(T -- > 0) { double n; scanf("%lf", &n); int r; scanf("%d", &r); double p; scanf("%lf", &p); int L = 0, R = 4499; int ans = 0; while(L <= R) { int mid = (L + R) / 2; if(n * h[r][mid] >= p) { ans = mid; R = mid - 1; } else { L = mid + 1; } } printf("Case %d: %d\n", cas, ans); cas ++; } }
C. Being Common is Too Mainstream
质因数分解,暴力。
#include <bits/stdc++.h> using namespace std; //**************************************************************** // Miller_Rabin 算法进行素数测试 //速度快,而且可以判断 <2^63的数 //**************************************************************** const int S=20;//随机算法判定次数,S越大,判错概率越小 //计算 (a*b)%c. a,b都是long long的数,直接相乘可能溢出的 // a,b,c <2^63 long long mult_mod(long long a,long long b,long long c) { a%=c; b%=c; long long ret=0; while(b) { if(b&1){ret+=a;ret%=c;} a<<=1; if(a>=c)a%=c; b>>=1; } return ret; } //计算 x^n %c long long pow_mod(long long x,long long n,long long mod)//x^n%c { if(n==1)return x%mod; x%=mod; long long tmp=x; long long ret=1; while(n) { if(n&1) ret=mult_mod(ret,tmp,mod); tmp=mult_mod(tmp,tmp,mod); n>>=1; } return ret; } //以a为基,n-1=x*2^t a^(n-1)=1(mod n) 验证n是不是合数 //一定是合数返回true,不一定返回false bool check(long long a,long long n,long long x,long long t) { long long ret=pow_mod(a,x,n); long long last=ret; for(int i=1;i<=t;i++) { ret=mult_mod(ret,ret,n); if(ret==1&&last!=1&&last!=n-1) return true;//合数 last=ret; } if(ret!=1) return true; return false; } // Miller_Rabin()算法素数判定 //是素数返回true.(可能是伪素数,但概率极小) //合数返回false; bool Miller_Rabin(long long n) { if(n<2)return false; if(n==2)return true; if((n&1)==0) return false;//偶数 long long x=n-1; long long t=0; while((x&1)==0){x>>=1;t++;} for(int i=0;i<S;i++) { long long a=rand()%(n-1)+1;//rand()需要stdlib.h头文件 if(check(a,n,x,t)) return false;//合数 } return true; } //************************************************ //pollard_rho 算法进行质因数分解 //************************************************ long long factor[100];//质因数分解结果(刚返回时是无序的) int tol;//质因数的个数。数组小标从0开始 long long gcd(long long a,long long b) { if(a==0)return 1;//??????? if(a<0) return gcd(-a,b); while(b) { long long t=a%b; a=b; b=t; } return a; } long long Pollard_rho(long long x,long long c) { long long i=1,k=2; long long x0=rand()%x; long long y=x0; while(1) { i++; x0=(mult_mod(x0,x0,x)+c)%x; long long d=gcd(y-x0,x); if(d!=1&&d!=x) return d; if(y==x0) return x; if(i==k){y=x0;k+=k;} } } //对n进行素因子分解 void findfac(long long n) { if(Miller_Rabin(n))//素数 { factor[tol++]=n; return; } long long p=n; while(p>=n)p=Pollard_rho(p,rand()%(n-1)+1); findfac(p); findfac(n/p); } const long long mod = 1000000001LL; const int maxn = 1e5 + 10; long long a[maxn]; vector<long long> fac[maxn]; bool prime(long long x) { if(x == 1) return 0; for(long long i = 2; i * i <= x; i ++) { if(x % i == 0) return 0; } return 1; } int main() { srand(time(NULL)); int n; scanf("%d", &n); for(int i = 1; i <= n; i ++) { scanf("%lld", &a[i]); } long long ans = 1; if(n == 1) { ans = a[1] % mod; } else if(n == 2) { long long g = gcd(a[1], a[2]); for(int i = 1; i <= n; i ++) { a[i] /= g; ans = ans * a[i] % mod; } } else if(n == 3) { long long g; g = gcd(a[1], gcd(a[2], a[3])); for(int i = 1; i <= n; i ++) { a[i] /= g; } g = gcd(a[1], a[2]); a[1] /= g; a[2] /= g; g = gcd(a[2], a[3]); a[2] /= g; a[3] /= g; g = gcd(a[1], a[3]); a[1] /= g; a[3] /= g; for(int i = 1; i <= n; i ++) { ans = ans * a[i] % mod; } } else if(n <= 1000) { for(int i = 1; i <= n; i ++) { if(a[i] == 1) continue; tol = 0; findfac(a[i]); for(int j = 0; j < tol; j ++) { fac[i].push_back(factor[j]); } } for(int i = 1; i <= n; i ++) { for(int j = 0; j < fac[i].size(); j ++) { if(a[i] % fac[i][j]) continue; int num = 0; for(int k = 1; k <= n; k ++) { if(a[k] % fac[i][j] == 0) num ++; } if(num < 2) continue; for(int k = 1; k <= n; k ++) { if(a[k] % fac[i][j] == 0) { a[k] /= fac[i][j]; } } } } for(int i = 1; i <= n; i ++) { ans = ans * a[i] % mod; } } else { for(long long x = 2; x <= 601; x ++) { if(!prime(x)) continue; while(1) { int num = 0; for(int i = 1; i <= n; i ++) { if(a[i] % x == 0) num ++; } if(num < 2) break; for(int i = 1; i <= n; i ++) { if(a[i] % x == 0) a[i] /= x; } } } for(int i = 1; i <= n; i ++) { ans = ans * a[i] % mod; } } printf("%lld\n", ans); return 0; } /* 10 1 2 3 4 5 6 7 8 9 10 */
D. Shaat Chara
对于第$i$堆石头,要使得拿走第$i$堆的若干颗石头变成必胜态,也就是要使得剩下的所有石头异或和为$0$。
#include <bits/stdc++.h> using namespace std; const long long mod = 1000000007LL; const int maxn = 2e5 + 10; int T, n; int a[maxn]; int main() { scanf("%d", &T); int cas = 1; while(T --) { scanf("%d", &n); int Xor = 0; for(int i = 1; i <= n; i ++) { scanf("%d", &a[i]); Xor = Xor ^ a[i]; } int ans = 0; for(int i = 1; i <= n; i ++) { Xor = Xor ^ a[i]; if(Xor < a[i]) ans ++; Xor = Xor ^ a[i]; } printf("Case %d: %d\n", cas ++, ans); } return 0; }
E. Just One Swap
如果每个数字都不一样,答案就是$C_n^2$。
否则,相同的数字交换有$1$种情况,再计算不同数字交换的方案数。
#include <bits/stdc++.h> using namespace std; const int maxn = 1e5 + 10; int T; int a[maxn]; int main() { scanf("%d", &T); while(T --) { memset(a, 0, sizeof a); int n; scanf("%d", &n); int y = n; while(n --) { int x; scanf("%d", &x); a[x] ++; } int ok = 1; for(int i = 1; i <= 100000; i ++) { if(a[i] > 1) ok = 0; } long long ans = 0; if(ok) { ans = 1LL * y * (y - 1) / 2; } else { ans = 1LL; long long sum = 0; for(int i = 1; i <= 100000; i ++) { ans = ans + sum * a[i]; sum = sum + a[i]; } } printf("%lld\n", ans); } return 0; }
F. Halum and Candies
贪心,二分。
这题最直观的做法是每次将最大的$k$个数字减$1$,直到不能操作为止,但是在题目的数据规模下容易超时。
较为容易的写法是二分答案+验证,假设二分到$x$个人,只要看$\sum\limits_{i = 1}^n {\min (a[i],x)}$和$x*k$的大小关系即可。
#include <bits/stdc++.h> using namespace std; int T, n, k; const int maxn = 1e5 + 10; long long a[maxn]; int check(long long x) { long long p = 0; for(int i = 1; i <= n; i ++) { p = p + min(x, a[i]); } if(p >= x * k) return 1; return 0; } int main() { int cas = 1; scanf("%d", &T); while(T --) { scanf("%d%d", &n, &k); for(int i = 1; i <= n; i ++) { scanf("%lld", &a[i]); } long long L = 0; long long R = 1e12; long long ans = 0; while(L <= R) { long long mid = (L + R) / 2; if(check(mid)) ans = mid, L = mid + 1; else R = mid - 1; } printf("Case %d: %lld\n", cas ++, ans); } return 0; } /* 3 3 3 1 2 3 3 1 1 2 3 3 2 3 2 4 */
G. XOR 'em all!
线段树。
每个节点存储每一种$1$的个数的最小的位置,以及转换后的即可。
#include <bits/stdc++.h> using namespace std; const int maxn = 1e6 + 10; int T, n, q; int a[maxn], cnt[2 * maxn]; int s[maxn * 4][2][25]; int p[maxn * 4], f[maxn * 4]; int ans, B, v; int lowbit(int x) { return x & (-x); } void init() { for(int i = 1; i < (1 << 20); i ++) { cnt[i] = cnt[i - lowbit(i)] + 1; } } void pushUp(int rt) { for(int i = 0; i < 21; i ++) { s[rt][0][i] = min(s[2 * rt][p[2 * rt]][i], s[2 * rt + 1][p[2 * rt + 1]][i]); s[rt][1][i] = min(s[2 * rt][p[2 * rt] ^ 1][i], s[2 * rt + 1][p[2 * rt + 1] ^ 1][i]); } p[rt] = 0; } void pushDown(int rt) { if(f[rt] == 0) return; p[2 * rt] = (p[2 * rt] + f[rt]) % 2; f[2 * rt] = (f[2 * rt] + f[rt]) % 2; p[2 * rt + 1] = (p[2 * rt + 1] + f[rt]) % 2; f[2 * rt + 1] = (f[2 * rt + 1] + f[rt]) % 2; f[rt] = 0; } void build(int l, int r, int rt) { p[rt] = 0; f[rt] = 0; if(l == r) { for(int t = 0; t < 2; t ++) { for(int i = 0; i < 21; i ++) { s[rt][t][i] = n + 1; } } s[rt][0][a[l]] = l; s[rt][1][20 - a[l]] = l; return; } int mid = (l + r) / 2; build(l, mid, 2 * rt); build(mid + 1, r, 2 * rt + 1); pushUp(rt); } void update(int L, int R, int l, int r, int rt) { if(L <= l && r <= R) { p[rt] = (p[rt] + 1) % 2; f[rt] = (f[rt] + 1) % 2; return; } pushDown(rt); int mid = (l + r) / 2; if(L <= mid) update(L, R, l, mid, 2 * rt); if(R > mid) update(L, R, mid + 1, r, 2 * rt + 1); pushUp(rt); } void query(int L, int R, int l, int r, int rt) { if(L <= l && r <= R) { for(int i = 0; i < 21; i ++) { if(s[rt][p[rt]][i] > n) continue; if(abs(i - v) < B) { B = abs(i - v); ans = s[rt][p[rt]][i]; } else if(abs(i - v) == B) { ans = min(ans, s[rt][p[rt]][i]); } } return; } pushDown(rt); int mid = (l + r) / 2; if(L <= mid) query(L, R, l, mid, 2 * rt); if(R > mid) query(L, R, mid + 1, r, 2 * rt + 1); pushUp(rt); } int main() { init(); scanf("%d", &T); int cas = 1; while(T --) { printf("Case %d:\n", cas ++); scanf("%d%d", &n, &q); for(int i = 1; i <= n; i ++) { scanf("%d", &a[i]); a[i] = cnt[a[i]]; } build(1, n, 1); while(q --) { int op, l, r; scanf("%d%d%d", &op, &l, &r); if(op == 1) { scanf("%d", &v); v = cnt[v]; B = 100; ans = n + 1; query(l, r, 1, n, 1); printf("%d\n", ans); } else { update(l, r, 1, n, 1); } } } return 0; } /* 1 10 9 47810 337106 289217 728190 763968 210307 934334 929186 401808 365768 2 8 10 1 2 10 611293 2 2 4 1 1 8 422298 2 6 8 2 2 10 1 5 6 180197 2 7 8 1 4 8 712158 */
H. Simple Path
树形$dp$。
注意点:这题数据有问题,题面上说每条边都是从$u$到$v$的,但事实上不是。
#include <bits/stdc++.h> using namespace std; const long long mod = 1000000007LL; const int maxn = 4e5 + 10; int T; int h[maxn]; int v[maxn]; long long w[maxn]; int nx[maxn]; int n; int sz[maxn]; long long ans; int cnt; int f[maxn]; void add(int a, int b, long long c) { v[cnt] = b; w[cnt] = c; nx[cnt] = h[a]; h[a] = cnt ++; } void SZ(int x) { sz[x] = 1; f[x] = 1; for(int i = h[x]; i != -1; i = nx[i]) { if(!f[v[i]]) { SZ(v[i]); sz[x] += sz[v[i]]; } } } void DP(int x, long long sum, int dep) { f[x] = 1; for(int i = h[x]; i != -1; i = nx[i]) { if(f[v[i]]) continue; // printf(" %d -> %d \n", x, v[i]); long long A = 1LL * sz[v[i]] * sum % mod; long long B = 1LL * sz[v[i]] * sz[v[i]] % mod; B = 1LL * B * dep % mod; long long C = (A - B + mod) % mod; C = 1LL * C * w[i] % mod; // cout << x << " debug " << C << endl; ans = (ans + C) % mod; long long G = 1LL * sz[v[i]]; G = (sum + G) % mod; DP(v[i], G, dep + 1); } //printf("debug %d %lld\n", x, dp[x]); } int main() { scanf("%d", &T); int cas = 1; while(T --) { scanf("%d", &n); cnt = 0; for(int i = 1; i <= n; i ++) { h[i] = -1; sz[i] = 0; f[i] = 0; } for(int i = 1; i < n; i ++) { int a, b; long long c; scanf("%d%d%lld", &a, &b, &c); add(a, b, c); add(b, a, c); } SZ(1); for(int i = 1; i <= n; i ++) { if(sz[i] <= 0) while(1) {} } for(int i = 1; i <= n; i ++) { f[i] = 0; } ans = 0; DP(1, sz[1], 1); printf("Case %d: %lld\n", cas ++, ans); } return 0; } /* 2 7 1 2 3 1 3 2 2 4 1 2 5 4 3 6 6 3 7 8 6 1 2 3 1 3 2 1 4 4 3 5 7 3 6 1 */