2017 ACM Amman Collegiate Programming Contest 题解
【题目链接】
模拟。统计一下哪个数字最多即可。
#include <bits/stdc++.h> using namespace std; const int maxn = 1e5 + 10; int T, n; char s[maxn]; int a[maxn]; int main() { scanf("%d", &T); while(T --) { scanf("%d", &n); memset(a, 0, sizeof a); for(int i = 1; i <= n; i ++) { int x; scanf("%s%d", s, &x); a[x] ++; } int mx = 0; for(int i = 11111; i <= 99999; i ++) { mx = max(mx, a[i]); } for(int i = 11111; i <= 99999; i ++) { if(a[i] == mx) { printf("%d\n", i); break; } } } return 0; }
模拟。一个串能乱变,一个串不能动,只要统计能变的那个串每个字母有几个即可,到不能动的串上来消耗。
#include <bits/stdc++.h> using namespace std; const int maxn = 1e5 + 10; int T, n; char s[maxn], t[maxn]; int cnt[500]; int main() { scanf("%d", &T); while(T --) { memset(cnt, 0, sizeof cnt); scanf("%s%s", s, t); int lens = strlen(s); int lent = strlen(t); for(int i = 0; t[i]; i ++) { cnt[t[i]] ++; } int ans = -1; for(int i = 0; i < lens; i ++) { if(cnt[s[i]] == 0) break; ans = i; cnt[s[i]] --; } printf("%d\n", ans + 1); } return 0; }
水题。
#include <bits/stdc++.h> using namespace std; const int maxn = 1e5 + 10; int T, n; int a, b, c; int main() { scanf("%d", &T); while(T --) { scanf("%d%d%d", &a, &b, &c); if(a < b && a < c) { printf("First\n"); } if(b < a && b < c) { printf("Second\n"); } if(c < a && c < b) { printf("Third\n"); } } return 0; }
组合数。通过分析可以发现答案为$2*C_{n - 1}^m$。
#include <iostream> #include <string.h> #include <stdio.h> using namespace std; typedef long long LL; LL n,m; LL p = 1e9 + 7; const int maxn = 1e5 + 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; } int main() { int T; f[0] = 1; for(long long i = 1; i < maxn; i ++) { f[i] = f[i - 1] * i % p; } scanf("%d", &T); while(T--) { scanf("%lld%lld", &n, &m); if(n == 0) { printf("0\n"); continue; } printf("%lld\n", C(n - 1, m) * 2 % p); } return 0; }
水题。
#include <bits/stdc++.h> using namespace std; const int maxn = 1e5 + 10; int T, n; char s[maxn], t[maxn]; int cnt[500]; int main() { scanf("%d", &T); while(T --) { long long a, b; cin >> a >> b; cout << a + b - 1 << endl; } return 0; }
贪心,线段树。如果满了,每次应该扔掉最晚用的那个。
#include <bits/stdc++.h> using namespace std; const int maxn = 1e5 + 10; int T, n, k; int a[maxn], b[maxn], nx[maxn], pos[maxn]; int s[4 * maxn]; int f[maxn]; int lsh(int x) { int L = 1, R = n; while(L <= R) { int mid = (L + R) / 2; if(b[mid] > x) R = mid - 1; else if(b[mid] < x) L = mid + 1; else return mid; } return 0; } void build(int l, int r, int rt) { s[rt] = 0; if(l == r) return; int mid = (l + r) / 2; build(l, mid, 2 * rt); build(mid + 1, r, 2 * rt + 1); } void update(int p, int val, int l, int r, int rt) { if(l == r) { s[rt] = val; return; } int mid = (l + r) / 2; if(p <= mid) update(p, val, l, mid, 2 * rt); else update(p, val, mid + 1, r, 2 * rt + 1); s[rt] = max(s[2 * rt], s[2 * rt + 1]); } int work(int l, int r, int rt) { if(l == r) return l; int mid = (l + r) / 2; if(s[2 * rt] > s[2 * rt + 1]) return work(l, mid, 2 * rt); else return work(mid + 1, r, 2 * rt + 1); } int main() { scanf("%d", &T); while(T --) { scanf("%d%d", &n, &k); for(int i = 1; i <= n; i ++) { scanf("%d", &a[i]); b[i] = a[i]; f[i] = 0; pos[i] = n + 1; } sort(b + 1, b + 1 + n); for(int i = 1; i <= n; i ++) { a[i] = lsh(a[i]); } for(int i = n; i >= 1; i --) { nx[i] = pos[a[i]]; pos[a[i]] = i; } build(1, n, 1); int ans = 0; int now = 0; for(int i = 1; i <= n; i ++) { if(f[a[i]]) { update(a[i], nx[i], 1, n, 1); continue; } ans ++; if(now < k) { f[a[i]] = 1; now ++; update(a[i], nx[i], 1, n, 1); } else { int del = work(1, n, 1); update(del, 0, 1, n, 1); update(a[i], nx[i], 1, n, 1); f[del] = 0; f[a[i]] = 1; } } printf("%d\n", ans); } return 0; }
区间和要能被区间内每个数都整除,就是区间和要能被区间的最小公倍数整除,因此处理出区间的和以及最小公倍数即可,注意爆long long。
#include <bits/stdc++.h> using namespace std; const int maxn = 2000 + 10; int T; int n, k; long long a[maxn]; long long sum[maxn][maxn]; long long lcm[maxn][maxn]; long long limit = 2000LL * 1e9; long long gcd(long long a, long long b) { if(b == 0) return a; return gcd(b, a % b); } long long LCM(long long a, long long b) { long long A = a / gcd(a, b); long long B = b; if(A > limit / B) return -1; return A * B; } int main() { scanf("%d", &T); while(T --) { scanf("%d", &n); for(int i = 1; i <= n; i ++) { scanf("%lld", &a[i]); } int ans = 0; for(int i = 1; i <= n; i ++) { for(int j = i; j <= n; j ++) { sum[i][j] = sum[i][j - 1] + a[j]; if(j == i) lcm[i][j] = a[j]; else lcm[i][j] = LCM(a[j], lcm[i][j - 1]); if(lcm[i][j] > limit || lcm[i][j] == -1) break; if(sum[i][j] % lcm[i][j] == 0) ans ++; } } printf("%d\n", ans); } return 0; }
构造。主要思想是能放$9$就一直放$9$,不能放$9$就放剩余的那个数,注意判断一下不存在的情况。
#include <bits/stdc++.h> using namespace std; const int maxn = 1e6 + 10; int T; int n, s; int ans[maxn]; int main() { scanf("%d", &T); while(T --) { scanf("%d%d", &n, &s); if(n % 2 == 0) { if(s % 2 || s > n * 9) printf("-1\n"); else { s = s / 2; for(int i = 0; i < n / 2; i ++) { if(s >= 9) ans[i] = 9, s -= 9; else ans[i] = s, s = 0; } for(int i = 0; i < n / 2; i ++) { printf("%d", ans[i]); } for(int i = n / 2 - 1; i >= 0; i --) { printf("%d", ans[i]); } printf("\n"); } } else { int p = -1; for(int i = 0; i <= 9; i ++) { if((s - i) % 2 != 0) continue; if((s - i) > 9 * (n - 1)) continue; p = i; break; } if(p == -1) { printf("-1\n"); continue; } s = (s - p) / 2; for(int i = 0; i < n / 2; i ++) { if(s >= 9) ans[i] = 9, s -= 9; else ans[i] = s, s = 0; } if(ans[0] == 0) { printf("-1\n"); continue; } for(int i = 0; i < n / 2; i ++) { printf("%d", ans[i]); } printf("%d", p); for(int i = n / 2 - 1; i >= 0; i --) { printf("%d", ans[i]); } printf("\n"); } } return 0; }
规律。$dp$打表找一下规律就可以了。
#include <bits/stdc++.h> using namespace std; const int maxn = 1e5 + 10; int T, n, m; int dp[1010][1010]; void init() { for(int i = 0; i <= 1000; i ++) { dp[0][i] = i % 2; dp[i][0] = i % 2; } for(int i = 1; i <= 1000; i ++) { for(int j = i; j <= 1000; j ++) { if(dp[i - 1][j] == 0 || dp[i][j - 1] == 0 || dp[i - 1][j - 1] == 0) { dp[i][j] = 1; dp[j][i] = 1; } else { dp[i][j] = 0; dp[j][i] = 0; } } } for(int i = 0; i <= 10; i ++) { for(int j = i; j <= 10; j ++) { printf("%d %d %d\n", i, j, dp[i][j]); } } } int main() { // init(); scanf("%d", &T); while(T --) { int ans; scanf("%d%d", &n, &m); if(n > m) swap(n, m); if(n % 2) ans = 1; else ans = m % 2; if(ans) printf("hasan\n"); else printf("abdullah\n"); } return 0; }
暴力。暴力枚举长度,然后验证一下即可。复杂度大约是$O(n*ln(n))$。
#include <bits/stdc++.h> using namespace std; const int maxn = 1e5 + 10; int T; char s[maxn]; int a[maxn]; int L, R; int work(int x) { int now; for(now = L + x; now <= R; now += x) { if(now > R + 1) return 0; if(now == R + 1) return 1; if(a[now - 1] == 1 && a[now] == 0) { while(a[now] == 0) now ++; } else return 0; } if(now != R + 1) return 0; return 1; } int main() { scanf("%d", &T); getchar(); while(T --) { gets(s); int len = strlen(s); for(int i = 0; i < len; i ++) { if(s[i] == ' ') a[i] = 0; else a[i] = 1; } L = 0, R = len - 1; while(a[L] == 0) L ++; while(a[R] == 0) R --; //printf("%d %d\n", L, R); if(R < L) { while(1) {} printf("YES\n"); continue; } int ans = 0; for(int i = 1; i < len; i ++) { ans = work(i); // if(ans) printf("debug %d\n", i); if(ans) break; } if(ans) printf("YES\n"); else printf("NO\n"); } return 0; }
$dp$。枚举$i$位置作为分割,那么答案可能是$[1,i]$中的最大值减去$[i + 1,n]$的最小值,也可以反过来。类似于最大子串和的思路可以搞定。
#include <bits/stdc++.h> using namespace std; const int maxn = 1e5 + 10; int T, n; long long a[maxn]; long long L[2][maxn][2]; long long R[2][maxn][2]; long long ll[2][maxn]; long long rr[2][maxn]; int main() { scanf("%d", &T); while(T --) { scanf("%d", &n); for(int i = 1; i <= n; i ++) { scanf("%lld", &a[i]); } L[0][1][0] = a[1]; L[1][1][0] = a[1]; L[0][2][0] = a[2]; L[0][2][1] = a[1] - a[2]; L[1][2][0] = a[2]; L[1][2][1] = a[1] - a[2]; for(int i = 3; i <= n; i ++) { /* min */ L[0][i][0] = min(a[i], L[0][i - 1][1] + a[i]); L[0][i][1] = L[0][i - 1][0] - a[i]; /* max */ L[1][i][0] = max(a[i], L[1][i - 1][1] + a[i]); L[1][i][1] = L[1][i - 1][0] - a[i]; } R[0][n][0] = a[n]; R[0][n][1] = -a[n]; R[1][n][0] = a[n]; R[1][n][1] = -a[n]; for(int i = n - 1; i >= 1; i --) { /* min */ R[0][i][0] = min(a[i], a[i] + R[0][i + 1][1]); R[0][i][1] = min(-a[i], -a[i] + R[0][i + 1][0]); /* max */ R[1][i][0] = max(a[i], a[i] + R[1][i + 1][1]); R[1][i][1] = max(-a[i], -a[i] + R[1][i + 1][0]); } ll[0][1] = a[1]; ll[1][1] = a[1]; for(int i = 2; i <= n; i ++) { /* min */ ll[0][i] = min(ll[0][i - 1], min(L[0][i][0], L[0][i][1])); /* max */ ll[1][i] = max(ll[1][i - 1], max(L[1][i][0], L[1][i][1])); } rr[0][n] = a[n]; rr[1][n] = a[n]; for(int i = n - 1; i >= 1; i --) { /* min */ rr[0][i] = min(rr[0][i + 1], R[0][i][0]); /* max */ rr[1][i] = max(rr[1][i + 1], R[1][i][0]); } long long ans = 0; for(int i = 2; i <= n; i ++) { ans = max(ans, abs(ll[0][i - 1] - rr[1][i])); ans = max(ans, abs(ll[1][i - 1] - rr[0][i])); } printf("%lld\n", ans); } return 0; }
$spfa$。某点入队超过$n$次就表示存在负环。某点最短路小于图中所有负边权之和,也说明存在负环。
#include <bits/stdc++.h> using namespace std; const long long INF = 1LL * 6000 * 1e6; const int maxn = 1e5 + 10; int T, n, m; int h[maxn], v[maxn], nx[maxn]; long long w[maxn]; int sz; long long dis[maxn]; int f[maxn], cnt[maxn]; long long g[2100][2100]; long long sum; long long ans; void add(int a, int b, long long c) { v[sz] = b; w[sz] = c; nx[sz] = h[a]; h[a] = sz ++; } void spfa() { int fail = 0; for(int i = 0; i <= n; i ++) { dis[i] = INF; f[i] = 0; cnt[i] = 0; } queue<int> q; dis[0] = 0; f[0] = 1; q.push(0); while(!q.empty()) { int first = q.front(); q.pop(); f[first] = 0; cnt[first] ++; if(cnt[first] == n + 1) { fail = 1; break; } if(dis[first] < sum) { fail = 1; break; } for(int i = h[first]; i != -1; i = nx[i]) { if(dis[first] + w[i] < dis[v[i]]) { dis[v[i]] = dis[first] + w[i]; if(f[v[i]] == 0) { f[v[i]] = 1; q.push(v[i]); } } } } if(fail) { printf("-inf\n"); } else { long long mn = INF; for(int i = 1; i <= n; i ++) { mn = min(mn, dis[i]); } if(mn == 0) printf("%lld\n", ans); else printf("%lld\n", min(ans, mn)); } } int main() { scanf("%d", &T); while(T --) { scanf("%d%d", &n, &m); for(int i = 0; i <= n; i ++) { h[i] = -1; } sz = 0; for(int i = 0; i <= n; i ++) { for(int j = 0; j <= n; j ++) { g[i][j] = INF; } } for(int i = 1; i <= m; i ++) { int a, b; long long c; scanf("%d%d%lld", &a, &b, &c); g[a][b] = min(c, g[a][b]); } sum = 0; ans = INF; for(int i = 1; i <= n; i ++) { for(int j = 1; j <= n; j ++) { if(g[i][j] == INF) continue; add(i, j, g[i][j]); ans = min(ans, g[i][j]); if(g[i][j] < 0) sum += g[i][j]; } } for(int i = 1; i <= n; i ++) { add(0, i, 0); } spfa(); } return 0; }
暴力。将$d$数组排序,那么最右边的那个点的坐标肯定是$d$数组最后一个值,然后枚举$d$数组倒数第二个值是放在靠左还是靠右,一直枚举下去即可。
#include <bits/stdc++.h> using namespace std; const int maxn = 1e6 + 10; int T, n, m; int d[maxn]; int ans[maxn]; int p[maxn]; int suc; void dfs(int x, int y) { if(x == n) { int ok = 1; for(int i = 0; i < m; i ++) { if(p[d[i]]) ok = 0; } if(ok) suc = 1; return; } if(p[d[y]] == 0) { dfs(x, y - 1); return; } for(int t = 0; t < 2; t ++) { int fail = 0; if(t == 0) ans[x] = d[y]; else ans[x] = ans[1] - d[y]; for(int i = 0; i < x; i ++) { if(p[abs(ans[x] - ans[i])] == 0) { fail = 1; } } if(fail) continue; for(int i = 0; i < x; i ++) { p[abs(ans[x] - ans[i])] --; } dfs(x + 1, y - 1); if(suc) return; for(int i = 0; i < x; i ++) { p[abs(ans[x] - ans[i])] ++; } } } int main() { scanf("%d", &T); while(T --) { scanf("%d", &n); m = n * (n - 1) / 2; for(int i = 0; i < m; i ++) { scanf("%d", &d[i]); p[d[i]] ++; } sort(d, d + m); ans[0] = 0; ans[1] = d[m - 1]; p[ans[1]] --; suc = 0; dfs(2, m - 2); sort(ans, ans + n); for(int i = 0; i < n; i ++) { printf("%d", ans[i]); if(i < n - 1) printf(" "); else printf("\n"); } for(int i = 0; i < m; i ++) { if(p[d[i]]) { while(1) {} } } } return 0; }