题意:
给定一个长度为n的序列,每次可以选择序列中的两个元素交换位置,求任意一组交换次数不超过n次的方案,使交换后的序列是按升序排列的。
n <= 3000。
题解:
我们可以使用选择排序来解决这个问题。
虽然选择排序会进行O(n^2)次比较,但是它具有一个很好的性质,我们可以使它的交换次数是O(n)的。
时间复杂度O(n^2)。
代码:
#include <cstdio> #include <algorithm> using namespace std; int n, a[3333], ans[3333][2], tot; int main() { scanf("%d", &n); for(int i = 0; i < n; ++i) scanf("%d", a + i); for(int i = 0; i < n; ++i) { int tmp = i; for(int j = i + 1; j < n; ++j) if(a[j] < a[tmp]) tmp = j; if(tmp != i) { ans[tot][0] = i; ans[tot][1] = tmp; ++tot; swap(a[i], a[tmp]); } } printf("%d\n", tot); for(int i = 0; i < tot; ++i) printf("%d %d\n", ans[i][0], ans[i][1]); return 0; }
题意:
有n个男孩、m个女孩参加舞会,每个人有一个舞蹈能力,我们约定一对男女能够一起跳舞,必须满足两人的舞蹈能力之差的绝对值不超过1,求最多能组成多少对男女跳舞。
n, m <= 100。
题解:
我们可以将男孩、女孩的舞蹈能力分别排序,这样可以使得任意一个男生可以共舞的女生是连续的一段,任意一个女生可以共舞的男生是连续的一段。
不难想到用f[i, j]表示前i个男生和前j个女生能成对的数量,将问题转化为dp。时间复杂度O(nm)。
排序后也可以用贪心的算法。时间复杂度O(nlogn)。
代码:
#include <cstdio> #include <algorithm> using namespace std; int n, m, a[233], b[233], f[233][233]; int main() { scanf("%d", &n); for(int i = 1; i <= n; ++i) scanf("%d", a + i); scanf("%d", &m); for(int i = 1; i <= m; ++i) scanf("%d", b + i); sort(a + 1, a + n + 1); sort(b + 1, b + m + 1); for(int i = 1; i <= n; ++i) for(int j = 1; j <= m; ++j) { f[i][j] = max(f[i][j - 1], f[i - 1][j]); if(abs(a[i] - b[j]) <= 1) f[i][j] = max(f[i][j], f[i - 1][j - 1] + 1); } printf("%d\n", f[n][m]); return 0; }
题意:
限定一个数的位数为m,各数位数字之和为s,求这个数可能的最小值和最大值,不存在则输出-1 -1。
m <= 100。
题解:
无解的情况:一是每个位上都是9也无法满足s,即s > 9*m;二是当m>1时s不足1,因为一个m位数最小是10^(m-1)。
这个数的最高位最少是1,其他位最少是0,可以利用贪心的思想构造出最优解,求最小值是尽量用低位去满足s,求最大值则是从高位。
时间复杂度O(m)。
代码:
#include <cstdio> int m, s, str1[233], str2[233], cnt1, cnt2; int main() { scanf("%d%d", &m, &s); if(s == 0) { if(m == 1) puts("0 0"); else puts("-1 -1"); return 0; } if(s > 9 * m) { puts("-1 -1"); return 0; } str1[m - 1] = str2[m - 1] = cnt1 = cnt2 = 1; for(int i = 0; i < m && cnt1 < s; ++i) if(cnt1 + 9 - str1[i] < s) { cnt1 += 9 - str1[i]; str1[i] = 9; } else { str1[i] += s - cnt1; cnt1 = s; } for(int i = m - 1; i >= 0 && cnt2 < s; --i) if(cnt2 + 9 - str2[i] < s) { cnt2 += 9 - str2[i]; str2[i] = 9; } else { str2[i] += s - cnt2; cnt2 = s; } for(int i = m - 1; i >= 0; --i) putchar('0' + str1[i]); putchar(' '); for(int i = m - 1; i >= 0; --i) putchar('0' + str2[i]); putchar('\n'); return 0; }
题意:
给定n个点,m条边的有向图,求满足条件"b≠d, a→b→d, a→c→d"的本质不同的四元组(a, b, c, d)的个数,其中(a, b, c, d)与(a, c, b, d)本质相同。
n <= 3000, m <= 30000。
题解:
算出每个点走两步到达另一个点的方案数,通过组合数学知识算出满足条件的(a, *, *, d)的个数。
关于走两步,可以搜索两步,因为每个点的搜索通过的边数量是O(m)的,所以整体算法是O(nm)的。
时间复杂度O(nm)。
代码:
#include <map> #include <cstdio> #include <algorithm> using namespace std; int n, m; map<int, int> g1[3010]; int g2[3010]; long long ans; int main() { scanf("%d%d", &n, &m); for(int i = 1, u ,v; i <= m; ++i) { scanf("%d%d", &u, &v); ++g1[u][v]; } for(int i = 1; i <= n; ++i) { memset(g2, 0, sizeof g2); for(map<int, int>::iterator it1 = g1[i].begin(), jt1 = g1[i].end(); it1 != jt1; ++it1) { int j = it1 -> first; for(map<int, int>::iterator it2 = g1[j].begin(), jt2 = g1[j].end(); it2 != jt2; ++it2) { int k = it2 -> first; g2[k] += (it1 -> second) * (it2 -> second); } } for(int j = 1; j <= n; ++j) if(i != j && g2[j] >= 2) ans += (long long)g2[j] * (g2[j] - 1) / 2; } printf("%I64d\n", ans); return 0; }
题意:
数轴上有n个点,依次标号1~n,每个点有两个属性,坐标x_i和到达该点可增加的好感度b_i。
规定如果从一个点到另一个点的路程是r,那么这段路程会增加\sqrt{|r - l|}点不适感,其中l为已给定的常数。
求一个运动路线使得从原点开始到达第n个点后的不适感与好感度的比值最小,只能从编号小的点向编号大的点走,保证走的方向一致。
n <= 1000, l <= 10 ^ 5, x_i <= 10 ^ 6。
题解:
经典的分数规划问题。
不妨设最优解的不适感与好感度的比值为k,走过的点数为m,每次增加不适感为p_i,好感度q_i,则\sum_{i = 1} ^ {m} {p_i} / \sum_{i = 1} ^ {m} {q_i} = k,那么可以转化为\sum_{i = 1} ^ {m} {p_i - k * q_i} = 0。如果给每种可能的路径定义权值为{p_i - k * q_i},则这个式子可以理解为从0点到第n个点的路径上每条边的权值和。则对于给定的答案,我们可以通过计算最短路算出答案是否合法。
如果存在最短路的值不小于0,则该解及比它大的k可能可行。据此可以二分答案k。
时间复杂度O(nlog\sqrt{nx_i})。
代码:
#include <stack> #include <cstdio> #include <algorithm> using namespace std; const double eps = 1e-10; int n, l, x[1010], b[1010], p[1010]; bool vis[1010]; double f[1010], L, R, M; bool judge() { for(int i = 1; i <= n; ++i) { f[i] = sqrt(abs(x[i] - l)); p[i] = 0; for(int j = 1; j < i; ++j) if(f[i] > f[j] + sqrt(abs(x[i] - x[j] - l))) { f[i] = f[j] + sqrt(abs(x[i] - x[j] - l)); p[i] = j; } f[i] -= M * b[i]; } return f[n] >= 0; } int main() { scanf("%d%d", &n, &l); for(int i = 1; i <= n; ++i) scanf("%d%d", x + i, b + i); for(M = 1; judge(); M *= 2); L = (int)M / 2; R = M; while(R - L >= eps) { M = (L + R) / 2; if(judge()) L = M; else R = M; } stack<int> s; for(int i = n; i; i = p[i]) s.push(i); while(!s.empty()) { printf("%d ", s.top()); s.pop(); } putchar('\n'); return 0; }
题意:
限定一种n*n的01矩阵,它的每行或每列数字之和为2。
现给定一个这种01矩阵的前m行,求这种矩阵有多少种可能的形态,答案可能很大,对一个数取模。
2 <= n <= 500, 0 <= m <= n, 2 <= mod <= 10 ^ 9。
题解:
我们可以统计出有多少个列还差两个1,有多少个列还差一个1,设f[i, j]表示剩i个列差两个1,剩j个列差一个1的方案数,则可以分三种情况讨论填1~2个1之后的状态,利用组合数学推出f[i, j]。
时间复杂度O(n^2)。
代码:
#include <cstdio> int n, m, mod, r[555], cnt[2], f[555][555]; char str[555]; int main() { scanf("%d%d%d", &n, &m, &mod); for(int i = 0; i < m; ++i) { scanf("%s", str); for(int j = 0; j < n; ++j) if(str[j] == '1') ++r[j]; } for(int i = 0; i < n; ++i) ++cnt[r[i]]; f[0][0] = 1; for(int i = 0; i <= cnt[0]; ++i) for(int j = 0; j <= n; ++j) { if(i >= 2 && j + 2 <= n) { f[i][j] += (long long)i * (i - 1) / 2 * f[i - 2][j + 2] % mod; if(f[i][j] >= mod) f[i][j] -= mod; } if(j >= 2) { f[i][j] += (long long)j * (j - 1) / 2 * f[i][j - 2] % mod; if(f[i][j] >= mod) f[i][j] -= mod; } if(i && j) { f[i][j] += (long long)i * j * f[i - 1][j] % mod; if(f[i][j] >= mod) f[i][j] -= mod; } } printf("%d\n", f[cnt[0]][cnt[1]]); return 0; }
考试应该戒骄戒躁;学习算法与实践缺一不可。