题目链接
本题入手的角度是将输入的 n 看成一系列素数(素因子)的乘积。
#include
using namespace std;
vector <int> ans;
int n, k, f, tmp;
// 对n进行因式分解
void primeFactor(int n) {
for(int i = 2; i * i <= n; i++) {
while(n % i == 0) {
// 将素因子储存起来
ans.push_back(i);
n /= i;
}
}
if(n != 1) {
ans.push_back(n);
}
}
int main() {
ios::sync_with_stdio(false);
cin.tie(0);
cin >> n >> k;
primeFactor(n);
f = ans.size();
// 判断无解的情况
if(f < k) {
cout << -1 << endl;
return 0;
}
// 输出前k - 1个数
for(int i = 0; i < k - 1; i++) {
cout << ans[i] << ' ';
}
tmp = 1;
for(int i = k - 1; i < f; i++) {
tmp = tmp * ans[i];
}
// 最后一个数由最后几个素因子合成
if(tmp > 1) {
cout << tmp << endl;
}
return 0;
}
本题的入手点是探究奇数和的构成。
我们知道,奇数和偶数有这样的性质。
在本题中,子序列里偶数的数量是不影响子序列和的奇偶性的。为了让子序列的和尽可能大,
我们应该将所有非负偶数都包含在子序列中,负偶数可以舍弃
那么,子序列中的奇数呢?显然,子序列中的奇数必须有奇数个。(因为题目保证一定有解所以不用考虑序列中没有奇数的情况)所以我们可以
我们可以对所有的奇数从大到小排序,先选最大的奇数加入子序列,从而保证子序列的和一定是奇数。然后成对地向子序列中加入奇数,保证子序列和的奇偶性不变。
#include
using namespace std;
vector <int> vec;
int n, m, num, res;
int main() {
ios::sync_with_stdio(false);
cin.tie(0);
cin >> n;
res = 0;
for(int i = 1; i <= n; i++) {
cin >> num;
// 如果是正奇数或复奇数
if(num % 2 != 0) {
vec.push_back(num);
}
// 如果是正偶数
else if(num > 0) {
res += num;
}
}
m = vec.size();
// 排序
sort(vec.begin(), vec.end());
res += vec[m-1];
for(int i = m - 2; i > 0; i -= 2) {
// 负数就不能被加进答案了
if(vec[i] + vec[i-1] <= 0) {
break;
}
res += (vec[i] + vec[i-1]);
}
cout << res << endl;
return 0;
}
本题的入手点在对 t 这个字符串在题目描述的操作过程中的作用的认识。
因为字符串 s 的首字符在不断地被加入 t 的末尾,又从末尾取出,所以我们可以将 t 看成一个栈,其中t的末尾是栈的栈顶。在某次操作之前我们有两种选择,最优的策略如下:
#include
using namespace std;
string str;
stack <char> sta;
queue <char> q;
int n, cur, cnt[300];
// 判断s中是否还有比ch字典序小的字符
bool isGod(char ch) {
for(int i = 'a'; i < ch; i++) {
if(cnt[i] > 0) {
return false;
}
}
return true;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(0);
cin >> str;
n = str.size();
for(int i = 0; i < n; i++) {
// 统计字符出现次数,供isGod函数使用
cnt[str[i]]++;
}
cur = 0;
while(cur < n) {
// 若栈是空栈则压栈
if(sta.empty()) {
sta.push(str[cur]);
cnt[str[cur]]--;
cur++;
}
// 若当前栈顶字符已是字典序最小
else if(isGod(sta.top())) {
q.push(sta.top());
sta.pop();
}
// 否则
else {
sta.push(str[cur]);
cnt[str[cur]]--;
cur++;
}
}
// 栈内可能还有残留字符
while(!sta.empty()) {
q.push(sta.top());
sta.pop();
}
while(!q.empty()) {
cout << q.front();
q.pop();
}
return 0;
}
本题的着手点在对 BST 为何高效的认识。
在查找过程中,每到一个节点, BST 都可以将查找规模缩小一半,这就是 BST 为何高效的原因。从这句话中我们可以得知的信息是, BST 中每个节点实际上都维护这一个区间,在查找过程中我们可以保证当前查找的值一定在这个节点维护的区间内。
比方说根节点 root 的权值为 10 。那么 root 维护的区间为 [0,INF] ,其左儿子维护的区间为 [0,9] ,其右儿子维护的区间为 [11,INF] 。
于是我们可以用 DFS 序访问这棵树,同时维护当前访问的节点表示的区间。若当前节点的权值不在区间就统计进答案中。
#include
using namespace std;
const int maxn = 1e5 + 10, maxv = 1e9;
map <int, int> cnt;
bool hf[maxn];
int v[maxn], l[maxn], r[maxn];
int n, root, ans;
// DFS序遍历树的同时维护区间[a, b]
void dfs(int u, int a, int b) {
if(a > b) {
return;
}
if(a <= v[u] && v[u] <= b) {
ans += cnt[v[u]];
}
if(l[u] >= 0) {
dfs(l[u], a, min(b, v[u] - 1));
}
if(r[u] >= 0) {
dfs(r[u], max(a, v[u] + 1), b);
}
}
int main() {
// freopen("Input2.txt", "r", stdin);
ios::sync_with_stdio(false);
cin.tie(0);
cin >> n;
for(int i = 1; i <= n; i++) {
cin >> v[i] >> l[i] >> r[i];
if(l[i] >= 0) {
hf[l[i]] = true;
}
if(r[i] >= 0) {
hf[r[i]] = true;
}
cnt[v[i]]++;
}
// 寻找根节点
for(int i = 1; i <= n; i++) {
if(hf[i] == false) {
root = i;
}
}
dfs(root, 0, maxv);
cout << n - ans << endl;
return 0;
}
本题的入手点在于分别考虑解决问题的难点,以及巧妙地暴力。
但是本题的问题在于 n 和 k 的数据规模必须同时被考虑。在这种情况下,当 k 比较小的时候(比如小于 100 的时候)我们可以动态规划,当 k 比较大的时候,我们可以暴力。
#include
using namespace std;
const int maxn = 1e5 + 10;
int q, p, k, n, a[maxn], d[maxn][110];
// 暴力计算答案
int brute(int p, int k) {
int res = 0;
while(p <= n) {
p += (a[p] + k);
res++;
}
return res;
}
int main() {
// freopen("Input1.txt", "r", stdin);
ios::sync_with_stdio(false);
cin.tie(0);
cin >> n;
for(int i = 1; i <= n; i++) {
cin >> a[i];
}
// 动态规划
for(int i = n; i >= 1; i--) {
for(int j = 1; j <= 100; j++) {
if(i + a[i] + j > n) {
d[i][j] = 1;
}
else {
d[i][j] = d[i+a[i]+j][j] + 1;
}
}
}
cin >> q;
while(q--) {
cin >> p >> k;
if(k <= 100) {
cout << d[p][k] << endl;
}
else {
cout << brute(p, k) << endl;
}
}
return 0;
}
本题的入手点在于恰当地选择解决问题的顺序,能设计动态规划算法并使用数据结构进行优化。
在不考虑任何数据规模的情况下,我们显然可以对每只老鼠考虑它们在哪个洞中,或对每个洞考虑它收容哪些老鼠。如果用那么可以用搜索将问题顺利解决。
但是显然我们不能不考虑数据规模,在本题的数据规模下,我们或许能够利用以上两种顺序中的一个,通过动态规划加以解决。为了让问题更清晰,我们似乎应该对老鼠和老鼠洞按坐标排序。
考虑让 d[i][j] 表示安排了前 i 个老鼠洞,前 j 只老鼠都得到妥善安置的最小的距离和。那么状态 (i,j) 由 (i−1,j),(i−1,j−1),……,(i−1,j−c[i]) 中 d 值最小的状态转移过来(其中 c[i] 表示第 i 个老鼠洞的容量)。于是在动态规划的同时做 RMQ 即可。
在思考实现方式的过程中要注意以下几个问题:
#include
using namespace std;
typedef long long ll;
typedef pair int> p;
const int maxn = 5e3 + 5;
const ll INF = 1e13;
// 用结构体表示的单调队列
struct queue {
int l, r;
int idx[maxn];
ll val[maxn];
void init() {
l = 0;
r = -1;
}
// 单调队列弹出首元素
void pop(int k) {
while(r >= l && idx[l] <= k) {
l++;
}
}
// 单调队列插入尾元素
void insert(ll v, int i) {
while(r >= l && val[r] >= v) {
r--;
}
val[++r] = v;
idx[r] = i;
}
ll getMin() {
return val[l];
}
}q;
int n, m, last, cur, mouse[maxn];
ll sum, pre[maxn], d[2][maxn];
p hole[maxn];
int main() {
ios::sync_with_stdio(false);
cin.tie(0);
cin >> n >> m;
for(int i = 1; i <= n; i++) {
cin >> mouse[i];
}
for(int i = 1; i <= m; i++) {
cin >> hole[i].first >> hole[i].second;
sum += hole[i].second;
}
// 若老鼠洞的总容量不够则无解
if(sum < n) {
cout << -1 << endl;
return 0;
}
// 对老鼠和老鼠洞按照坐标从小到大排序
sort(mouse + 1, mouse + n + 1);
sort(hole + 1, hole + m + 1);
// DP的初始化
fill(d[0] + 1, d[0] + n + 1, INF);
cur = 1;
for(int i = 1; i <= m; i++) {
int x = hole[i].first;
int w = hole[i].second;
// 预处理距离和
for(int j = 1; j <= n; j++) {
pre[j] = pre[j-1] + abs((mouse[j] - x));
}
// 初始化单调队列
q.init();
q.insert(0, 0);
// 计算d[i][j]
for(int j = 1; j <= n; j++) {
q.insert(d[last][j] - pre[j], j);
d[cur][j] = q.getMin() + pre[j];
q.pop(j - w);
}
// 滚动数组
last = cur;
cur ^= 1;
}
cout << d[last][n] << endl;
return 0;
}