2018年长沙理工大学第十三届程序设计竞赛 题解
【题目链接】
A - LL
简单题。
#include <bits/stdc++.h> using namespace std; int T; char s[2000]; int main() { while(gets(s)) { int len = strlen(s); for(int i = 0; s[i]; i ++) { if(s[i] >= 'A' && s[i] <= 'Z') { s[i] = s[i] - 'A' + 'a'; } } if(len != 8 || (strcmp("lovelive", s) != 0)) { printf("no\n"); }else { printf("yes\n"); } } return 0; }
B - 奇怪的加法
高精度加法一下,不要进位就好了。
#include <bits/stdc++.h> using namespace std; char s[20]; char t[20]; char ans[20]; int limit; int main() { while(~scanf("%s%s",s,t)) { limit = 20; int lens = strlen(s); int lent = strlen(t); for(int i = 0; i < lens / 2; i ++) { swap(s[i], s[lens - i - 1]); } for(int i = 0; i < lent / 2; i ++) { swap(t[i], t[lent - i - 1]); } //printf("%s %s\n", s, t); for(int i = lens; i < limit; i ++) { s[i] = '0'; } for(int i = lent; i < limit; i ++) { t[i] = '0'; } for(int i = 0; i < limit; i ++) { int nums = s[i] - '0'; int numt = t[i] - '0'; ans[i] = (char)('0' + ((nums + numt) % 10)); } int pos = 0; for(int i = 19; i >= 0; i --) { if(ans[i] != '0') { pos = i; break; } } for(int i = pos; i >= 0; i --) { printf("%c", ans[i]); } printf("\n"); } return 0; }
C - 取手机
总共排列方案有 ${ C }_{ a+b }^{ a }$ 种,第 $k$ 位是 b 手机的方案有 ${ C }_{ a+b-1 }^{ a }$ 种,因此概率为 $\frac { { C }_{ a+b-1 }^{ a } }{ { C }_{ a+b }^{ a } } =\frac { b }{ a+b } $。
#include <bits/stdc++.h> using namespace std; int T; int main() { scanf("%d", &T); while(T --) { long long a, b, k; scanf("%lld%lld%lld", &a,&b,&k); printf("%.3f\n", 1.0*b/(a+b)); } return 0; }
D - zzq的离散数学教室1
$b=a\times prime$,因此枚举 $prime$,看有多少 $(a,b)$ 满足即可,算一下发现方案数是 $\left\lfloor \frac { R }{ prime } \right\rfloor -L+1$。
这种做法的复杂度为 $O(P \times Q)$。其中 $P$ 为素数个数,$Q$ 为询问次数。
#include<cstdio> #include<algorithm> using namespace std; const int N = 1e6 + 10; int p[N], a[N], b[N]; int ans[N],tt=0; int f[N]; int t, u; void init(){ t = 0, u = 0; for (int i = 2;i < N;i++) { if (a[i] == 0) p[t++] = i, a[i] = i; for (int j = 0;j < t && i * p[j] < N;j++) { a[i*p[j]] = p[j]; if (i % p[j] == 0) { break; } } } //printf("%d\n", t); } int main(){ init(); int L, R; while(~scanf("%d%d", &L, &R)) { int ans = 0; for(int i = 0; i < t; i ++) { if(L * p[i] > R) break; ans = ans + R / p[i] - L + 1; } printf("%d\n", ans); } return 0; }
这题询问出到 $10^5$ 次也可以搞。
假设有一堆满足条件的二元组 $(a,b)$,对于一次询问 $(L, R)$,我们需要寻找有多少个二元组满足 $a$ 和 $b$ 均在 $[L,R]$ 内,这个就是很经典的问题了,离线很容易操作,要求在线的话就主席树。这里我们可以处理出所有满足条件的二元组,不会很多,因为 $b$ 的素因子个数就不多。
类似的题目推荐:ZOJ 4008
#include <bits/stdc++.h> using namespace std; const int N = 1e6 + 10; int p[N], a[N], b[N]; int ans[N],tt=0; int f[N]; struct point{ int l,r,id; }x[N]; int low(int x) { return x&(-x); } void add(int x) { for (int i = x;i<N;i+=low(i)) { f[i]++; } } int sum(int x) { int res = 0; for (int i = x;i > 0;i-=low(i)) { res+=f[i]; } return res; } void init(){ int t = 0, u = 0; for (int i = 2;i < N;i++) { if (a[i] == 0) p[t++] = i, a[i] = i; for (int j = 0;j < t && i * p[j] < N;j++) { a[i*p[j]] = p[j]; if (i % p[j] == 0) { break; } } int o = 0; for (int j = i;j > 1;j /= a[j]) { b[o++] = a[j]; } sort(b,b+o); o = unique(b,b+o) - b; for (int j = 0;j<o;j++) { add(i/b[j]); //cout << "debug " << i / b[j] << endl; } for (;u<tt && x[u].r <= i;u++) { if (x[u].l >= x[u].r) ans[x[u].id] = 0; else { ans[x[u].id] = sum(x[u].r) - sum(x[u].l - 1); } } if (u==tt) break; } } bool cmp(point a,point b) { return a.r < b.r; } int main(){ #ifdef ZHOUZHENTAO freopen("test.in", "r", stdin); #endif while (~scanf("%d%d",&x[tt].l,&x[tt].r)){ x[tt].id = tt; tt ++; } sort(x,x+tt,cmp); init(); for (int i = 0;i < tt;i++) { printf("%d\n",ans[i]); } return 0; }
E - 小木乃伊到我家
最短路。一开始忘记优先队列是大的排在前面了,果然是智力有缺陷。
#include <bits/stdc++.h> using namespace std; const int maxn = 4e5 + 10; long long INF = 1e9; int T; int n, m; int sz, h[maxn], nx[maxn], to[maxn]; long long val[maxn]; long long dis[maxn]; void add(int x, int y, long long z) { to[sz] = y; val[sz] = z; nx[sz] = h[x]; h[x] = sz ++; } int main() { INF = INF * INF; scanf("%d%d", &n, &m); for(int i = 1; i <= n; i ++) { h[i] = -1; dis[i] = INF; } for(int i = 1; i <= m; i ++) { int u, v; long long w; scanf("%d%d%lld", &u, &v, &w); add(u, v, w); add(v, u, w); } priority_queue<pair<long long, int> > q; q.push(make_pair(0, 1)); dis[1] = 0; while(!q.empty()) { pair<long long, int> pi = q.top(); q.pop(); if(dis[pi.second] < -pi.first) continue; for(int i = h[pi.second]; i!=-1;i=nx[i]) { if(dis[pi.second] + val[i] < dis[to[i]]) { dis[to[i]] = dis[pi.second] + val[i]; q.push(make_pair(-dis[to[i]], to[i])); } } } if(dis[n] == INF) { printf("qwb baka\n"); } else { printf("%lld\n", dis[n]); } return 0; }
F - 箱庭的股市
仔细观察一下可以发现就是求杨辉三角某一行的前缀和。
#include<cstdio> #include<algorithm> using namespace std; const int N = 1e6 + 10; const int mod = 1e9 + 7; int m,x,y,p; int f[N], g[N], inv[N]; int main(){ f[0] = g[0] = f[1] = g[1] = inv[1] = 1; for (int i = 2;i<N;i++) { f[i] = 1LL * f[i-1] * i % mod; inv[i] = 1LL * inv[mod % i] * (mod - mod /i) % mod; g[i] = 1LL * g[i-1] * inv[i] % mod; } while (~scanf("%d%d%d%d",&m,&x,&y,&p)) { int ans = 0; if (x==1||y==0) { printf("%d\n", p); continue; } x = x - 1; for (int i = 0;i<= min(y,x);i++) { ans = (ans + 1LL * f[x] * g[i] % mod * g[x-i] % mod) % mod; } ans = 1LL * p * ans % mod; printf("%d\n",ans); } }
G - 逃离迷宫
吐槽:题目里说:“现在你需要花费最少的时间拿到钥匙然后从迷宫的出口出去”。我一开始理解为首先是要求到钥匙的时间最少,然后从最少时间的钥匙那里挑一个走到出口,结果答案错误,后来改成整体最少时间就 AC 了。这个我感觉确实会引起歧义啊。
#include <bits/stdc++.h> using namespace std; int INF = 10000000; const int maxn = 550; int T; int n, m; char s[maxn][maxn]; int sx, sy; int ex, ey; int dis[2][maxn][maxn]; int dir[4][2] = { {-1, 0}, {1, 0}, {0, -1}, {0, 1} }; int out(int x, int y) { if(x < 0 || x >= n) return 1; if(y < 0 || y >= m) return 1; return 0; } void bfs(int flag) { queue<int> q; if(flag == 0) { dis[flag][sx][sy] = 0; q.push(sx * m + sy); } else { dis[flag][ex][ey] = 0; q.push(ex * m + ey); } while(!q.empty()) { int st = q.front(); q.pop(); int nx = st / m; int ny = st % m; for(int i = 0; i < 4; i ++) { int tx = nx + dir[i][0]; int ty = ny + dir[i][1]; if(out(tx, ty)) continue; if(s[tx][ty] == '#') continue; if(flag == 0 && s[tx][ty] == 'E') continue; if(dis[flag][nx][ny] + 1 < dis[flag][tx][ty]) { dis[flag][tx][ty] = dis[flag][nx][ny] + 1; q.push(tx * m + ty); } } } } int main() { scanf("%d", &T); while(T --) { scanf("%d%d", &n, &m); for(int i = 0; i < n; i ++) { scanf("%s", s[i]); } for(int i = 0; i < n; i ++) { for(int j = 0; j < m; j ++) { if(s[i][j] == 'P') sx = i, sy = j; else if(s[i][j] == 'E') ex = i, ey = j; dis[0][i][j] = dis[1][i][j] = INF; } } bfs(0); bfs(1); int ans = INF; for(int i = 0; i < n; i ++) { for(int j = 0; j < m; j ++) { if(s[i][j] != 'K') continue; if(dis[0][i][j] == INF) continue; if(dis[1][i][j] == INF) continue; ans = min(ans, dis[0][i][j] + dis[1][i][j]); } } if(ans == INF) { printf("No solution\n"); continue; } printf("%d\n", ans); } return 0; }
H - 数学考试
枚举分割线,然后左边最大值和右边最大值加一加更新答案。左右两边最大值 dp 一下就能算出来了。
#include <bits/stdc++.h> using namespace std; const int maxn = 2e5 + 10; long long INF = 1e9; int T, n, k; long long a[maxn]; long long L[maxn], R[maxn]; int main() { INF = INF * INF; scanf("%d", &T); while(T --) { scanf("%d%d", &n, &k); for(int i = 1; i <= n; i ++) { scanf("%lld", &a[i]); } long long sum = 0; for(int i = 1; i <= k; i ++) { sum = sum + a[i]; } L[k] = sum; for(int i = k + 1; i <= n; i ++) { sum = sum - a[i - k] + a[i]; L[i] = max(L[i - 1], sum); // cout << sum << endl; } sum = 0; for(int i = n; i >= n - k + 1; i --) { sum = sum + a[i]; } R[n - k + 1] = sum; for(int i = n - k; i >= 1; i --) { sum = sum - a[i + k] + a[i]; R[i] = max(R[i + 1], sum); // cout << sum << endl; } long long ans = L[k] + R[k + 1]; for(int i = k; i + k <= n; i ++) { // [, i], [i + 1, i + k] ans = max(ans, L[i] + R[i + 1]); // cout << L[i] << " " << R[i + 1] << endl; } printf("%lld\n", ans); } return 0; }
I - 连续区间的最大公约数
以每一个位置 $i$ 作为区间右端点,左端点从 $[1, i]$ 移动的过程中,区间 gcd 的种类不会超过 $log(n)$ 种,我们可以把这些处理出来,记录成四元组 $\left( { L }_{ 1 },L_{ 2 },R,g \right) $,表示区间左端点在 $[L_1,L_2]$,右端点在 $R$ 的所有区间,最大公约数均为 $g$。
所有的四元组已经 cover 了所有的子区间。那么对于每一次的询问 $(L, R, g)$,我们只需在和这一次询问拥有相同 $g$ 的四元组中寻找答案,去计算每一个四元组做出的贡献。
例如询问为 $(4, 7, 3)$,四元组 $(1, 3, 3, 3)$ 的贡献为 0,虽然 $g$ 均为 3,但是无相交部分。四元组 $(2, 5, 6, 3)$ 的贡献为 2。虽然以 6 为结尾的有 4 个区间 $g$ 为 3,但是只有 2 个在询问的区间内。
一种效率较高的做法如下:
把询问离线,询问中 $g$ 相同的归为一类,去 gcd 为 $g$ 的四元组中计算答案。也就是按 $g$ 分类来计算。
下面只考虑相同 $g$ 情况下的询问和四元组之间如何计算答案。
对于一次询问 $(L, R, g)$,就是求 $R$ 小于等于询问的 $R$ 的四元组 $[L_1,L_2]$ 和 $[L, R]$ 交集长度之和。每次暴力求的话,复杂度为 $O(n^2)$,事实上可以按 $R$ 排序,然后利用线段树区间修改、求区间和来搞。
/******************************* Judge Result : AC *******************************/ #include <bits/stdc++.h> #define rep(i,j,k) for (int i = j; i <= k; i++) #define per(i,j,k) for (int i = j; i >= k; i--) #define loop(i,j,k) for (int i = j;i != -1; i = k[i]) #define lson x << 1, l, mid #define rson x << 1 | 1, mid + 1, r #define ff first #define ss second #define mp(i,j) make_pair(i,j) #define pb push_back #define pii pair<int,LL> #define in(x) scanf("%d", &x); using namespace std; typedef long long LL; const int low(int x) { return x&-x; } const double eps = 1e-4; const int INF = 0x7FFFFFFF; const int N = 4e5 + 10; int T, n, m, l, r; int g[N],d[N]; long long s[N], k[N]; int gcd(int x, int y) { return x%y ? gcd(y, x%y) : y; } void build(int x,int l,int r) { if (l == r) { scanf("%d",&g[x]); d[r] = g[x]; } else { int mid = l + r >> 1; build(lson); build(rson); g[x] = gcd(g[x<<1], g[x<<1|1]); } } int query(int x,int l,int r,int ll,int rr) { if (ll<=l && r<=rr) return g[x]; int mid = l + r>>1; if (rr <= mid) return query(lson,ll,rr); else if (ll > mid) return query(rson,ll,rr); else return gcd(query(lson,ll,rr),query(rson,ll,rr)); } void pushUp(int rt) { s[rt] = s[2 * rt] + s[2 * rt + 1]; } void pushDown(int rt, int l, int r) { if(k[rt] == 0) return; int mid = (l + r) / 2; s[2 * rt] += (k[rt] * (mid - l + 1)); s[2 * rt + 1] += (k[rt] * (r - mid)); k[2 * rt] += k[rt]; k[2 * rt + 1] += k[rt]; k[rt] = 0; } void update(int L, int R, long long val, int l, int r, int rt) { if(L <= l && r <= R) { s[rt] += (val * (r - l + 1)); k[rt] += val; return; } int mid = (l + r) / 2; pushDown(rt, l, r); if(L <= mid) update(L, R, val, l, mid, 2 * rt); if(R > mid) update(L, R, val, mid + 1, r, 2 * rt + 1); pushUp(rt); } long long get(int L, int R, int l, int r, int rt) { if(L <= l && r <= R) return s[rt]; pushDown(rt, l, r); int mid = (l + r) / 2; long long left = 0, right = 0; if(L <= mid) left = get(L, R, l, mid, 2 * rt); if(R > mid) right = get(L, R, mid + 1, r, 2 * rt + 1); pushUp(rt); return left + right; } struct point{ int l,r,g, id; }c[N]; struct point2 { int l1, l2, r, g; }h[N]; int szh; long long ans[N]; bool cmpc(const point& a, const point& b) { if(a.g != b.g) return a.g < b.g; return a.r < b.r; } bool cmph(const point2& a, const point2& b) { if(a.g != b.g) return a.g < b.g; return a.r < b.r; } bool cmpid(const point& a, const point& b ) { return a.id < b.id; } int main() { #ifdef ZHOUZHENTAO freopen("test.in", "r", stdin); #endif scanf("%d", &T); int cas = 0; while (T--) { scanf("%d", &n); build(1, 1, n); scanf("%d", &m); printf("Case #%d:\n", ++cas); for (int i = 0;i< m ;i++) { scanf("%d%d", &c[i].l, &c[i].r); c[i].g = query(1, 1, n, c[i].l, c[i].r); c[i].id = i; } szh = 0; stack<pii> a, b; rep(i, 1, n) { a.push(mp(d[i], 1)); while (!a.empty()) b.push(mp(gcd(a.top().ff, d[i]), a.top().ss)), a.pop(); while (!b.empty()) { pii q = b.top(); b.pop(); if (!a.empty() && a.top().ff == q.ff) q.ss += a.top().ss, a.pop(); a.push(q); } int L = i; while (!a.empty()) { pii q = a.top(); a.pop(); int LL = L - q.second + 1; h[szh].l1 = LL; h[szh].l2 = L; h[szh].r = i; h[szh].g = q.first; szh ++; L = LL - 1; b.push(q); } while (!b.empty()) { a.push(b.top()); b.pop(); } } sort(c, c + m, cmpc); sort(h, h + szh, cmph); /* for(int i = 0; i < szh; i ++) { cout << h[i].l1 << " " << h[i].l2 << " " << h[i].r << " " << h[i].g << endl; } for(int i = 0; i < m; i ++) { cout << c[i].l << " " << c[i].r << " " << c[i].g << endl; } */ for(int i = 0; i < m; i ++) { ans[i] = 0; } for(int i = 0, j = 0; i < szh; i = j + 1, j = i) { while(j < szh - 1 && h[j].g == h[j + 1].g) j ++; // [i, j] int L = 0, R = m - 1, pos1 = -1, pos2 = -1; while(L <= R) { int mid = (L + R) / 2; if(c[mid].g < h[i].g) L = mid + 1; else if(c[mid].g > h[i].g) R = mid - 1; else pos1 = mid, R = mid - 1; } if(pos1 == -1) continue; L = 0, R = m - 1; while(L <= R) { int mid = (L + R) / 2; if(c[mid].g < h[i].g) L = mid + 1; else if(c[mid].g > h[i].g) R = mid - 1; else pos2 = mid, L = mid + 1; } //[pos1, pos2] /* printf("[%d, %d] [%d, %d]\n", i, j, pos1, pos2); */ int y = i; for(int k = pos1; k <= pos2; k ++) { while(y <= j && h[y].r <= c[k].r) { update(h[y].l1, h[y].l2, 1LL, 1, n, 1); y ++; } ans[c[k].id] = get(c[k].l, c[k].r, 1, n, 1); } for(int k = i; k < y; k ++) { update(h[k].l1, h[k].l2, -1LL, 1, n, 1); } } sort(c, c + m, cmpid); for(int i = 0; i < m; i ++) { printf("%d ", c[i].g); printf("%lld\n", ans[i]); } } return 0; }
J - 杯子
这题和这题完全一样。
#include <bits/stdc++.h> using namespace std; typedef long long LL; LL p = 1e9 + 7; const int maxn = 3e6 + 10; LL f[maxn]; //****************************** //返回d=gcd(a,b);和对应于等式ax+by=d中的x,y long long extend_gcd(long long a,long long b,long long &x,long long &y) { if(a==0&&b==0) return -1;//无最大公约数 if(b==0){x=1;y=0;return a;} long long d=extend_gcd(b,a%b,y,x); y-=a/b*x; return d; } //*********求逆元素******************* //ax = 1(mod n) long long mod_reverse(long long a,long long n) { long long x,y; long long d=extend_gcd(a,n,x,y); if(d==1) return (x%n+n)%n; else return -1; } LL C(LL n, LL m) { long long A = f[n]; long long B = f[n - m] * f[m] % p; long long C = mod_reverse(B, p); return A * C % p; } LL work(LL n, LL m) { if(n == m) return 1; return (C(n * 2 - m - 1, n - m) - C(n * 2 - m - 1, n - m - 1) + p) % p; } int main() { f[0] = 1; for(long long i = 1; i < maxn; i ++) { f[i] = (f[i - 1] * i) % p; } int T; scanf("%d", &T); while(T --) { LL n, m, k; scanf("%lld%lld%lld", &n, &m, &k); if(m > n || m < k) { printf("0\n"); continue; } LL A = work(m, k); LL B = work(n + k + 1 - m, k + 1); LL ans = A * B % p; printf("%lld\n", ans); } return 0; } /* A: ---+----------------------------------- m\k| 1 2 3 4 5 6 7 ---+----------------------------------- 1 | 1 2 | 1 1 3 | 2 2 1 4 | 5 5 3 1 5 | 14 14 9 4 1 6 | 42 42 28 14 5 1 7 | 132 132 90 48 20 6 1 B: n = 1 ---+----------------------------------- m\k| 1 2 3 4 5 6 7 ---+----------------------------------- 1 | 1 2 | 3 | 4 | 5 | 6 | 7 | n = 2 ---+----------------------------------- m\k| 1 2 3 4 5 6 7 ---+----------------------------------- 1 | 2 2 | 1 1 3 | 4 | 5 | 6 | 7 | n = 3 ---+----------------------------------- m\k| 1 2 3 4 5 6 7 ---+----------------------------------- 1 | 5 2 | 2 3 3 | 1 1 1 4 | 5 | 6 | 7 | n = 4 ---+----------------------------------- m\k| 1 2 3 4 5 6 7 ---+----------------------------------- 1 | 14 2 | 5 9 3 | 2 3 4 4 | 1 1 1 1 5 | 6 | 7 | n = 5 ---+----------------------------------- m\k| 1 2 3 4 5 6 7 ---+----------------------------------- 1 | 42 2 | 14 28 3 | 5 9 14 4 | 2 3 4 5 5 | 1 1 1 1 1 6 | 7 | */
K - zzq的离散数学教室2
这题看上去好像和 2017 ICPC 南宁 M 题一样,都是求最长反链长度。最长反链长度 = 最小链覆盖数,原题在 BZOJ 1143。两种做法:第一种做法传递闭包搞,第二种做法网络流上对流量做一些改造。第二种能适应这题的数据规模。
L - 仓鼠养殖计划
队友写的,我题意都不知道。
#include<cstdio> #include<algorithm> using namespace std; const int N = 1e5 + 10; int T,a,b,n, c[N]; bool check() { for (int i = 1;i<=n;i++) { while (c[i]) { if (a+b==0) break; if (a==0) { c[i] = max(0,c[i]-2); b--; } else if (b==0) { c[i]--; a--; } else if (c[i]>1) { c[i]-=2; b--; } else { c[i]--; a--; } } if (c[i]) return false; } return true; } int main(){ for (scanf("%d",&T);T--;) { scanf("%d%d%d",&a,&b,&n); for (int i = 1;i<=n;i++) { scanf("%d",&c[i]); } puts(check()?"Yes":"No"); } return 0; }