T1. 最小质因子
题意 :
给定一个数n,求n的最小质因子。 (n <= 1e16)
思路:
首先一个数如果不是质数,那么他一定可以由质数表示出来,所以任何一个数,都是由质数累乘得到的。根据这个前提条件,我们可以先预处理出前100个质数,那么由最小的质因子2累乘100次也是远远大于1e18的。所以当我们用前100个质数去判断n是否被该质数整除,如果可以就输出当前最小的质数,如果100个都不可以.那么说明该数一定是一个质数。
代码:
#include <bits/stdc++.h>
using namespace std;
const int N = 1000, M = 1000;
int prime[N], f[M], cnt;
//预处理出前100多个质数
bool init(){
for(int i = 2; i < M ; i++){
if(!f[i])prime[cnt ++] = i;
for(int j = 0; i * prime[j] < M ;j ++){
f[i * prime[j]] = 1;
if(i % prime[j] == 0)break;
}
}
}
void solve(){
long long n;
cin >> n;
for(int i = 0 ; i < cnt ; i ++ ){
if(n % prime[i] == 0){
cout << i << endl;
return;
}
}
//如果无法被前100整除 那么该数一定是一个质数
cout << n << endl;
return ;
}
int main(){
int T;
cin >> T;
init();
while(T --) solve();
return 0;
}
T2. 选择排序
题意:
给定一个排列,然后给m个询问,每次询问有一个整数x,表示第x次选择排序的结果是什么
思路:
首先,选择排序的原理是让第i小的元素放在第i位,第i次排序会处理第i小的数。然后我们需要快速的找到第i小的元素,由于题目给定的是一个排列,那么可以确定该数组的最大值在n之内,同时每个数只会出现一次。我们使用一个标记数组,一个cnt[i]数组表示i出现的下标。然后要注意每次交换(下标为i)和(元素值为i的下标)的时候需要将他们的cnt标记数组也进行交换,用于本次交换之后的操作。
代码:
#include<bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
int a[N];
int cnt[N];
int main(){
int n, m;
cin >> n >> m;
for(int i = 1; i <= n; i ++){
cin >> a[i];
cnt [a[i]] = i;
}
int pr = 1;
while(m -- ){
int x;
cin >> x;
for(int i = pr; i <= x; i++){
int t1 = cnt[i], t2 = a[i];
// 因为cnt[i] 和 a[cnt [a[i]]]会在后面进行swap,为了方便后面cnt数组的交换所以提前记录
swap(a[i], a[cnt[i]]);
cnt[i] = i;
cnt[t2] = t1;
}
for(int i = 1; i <= n; i++){
cout << a[i] << " ";
}
pr = x;//因为给定的x是单调上升的,每次只要处理上一次处理过的和当前需要的次数就可以了
cout << endl;
}
}
T3. 学习计划
题意:
给定序列 𝑎[1 ∼ 𝑛], 𝑏[1 ∼ 𝑚],你需要把 1,2, . . . , 𝑛 这个序列分成首尾相连且非空的 𝑚
段。假设每段的 𝑎 之和为 𝑠[1 ∼ 𝑚],最大化 s[i] * b[i] 之和。
思路:
O(n^3)暴力的做法: 暴力枚举出上一个任务的结束天数,然后使用前缀和算出这中间天数的代价和。然后用 d[i][j] 表示第 i 天 完成 j 的最大价值。
O(n^2)使用Dp[i][j]表示当前已经走到了第i个科目,然后已经使用了j天的状态,表示的属性为最大值。然后去思考状态转移,对于当前的确定要完成第i个科目的时候,那么他要么从上一天连续进行当前科目,要么当前天是这个科目第一天,也就是说天数只有两种情况,一种是当前任务的第一天,另一种是当前任务的第k天(k > 1)。我们对于这两种情况进行表示,第一种情况等于d[i - 1][j - 1]表示上一天还在做第 i - 1 个任务第二种情况等于d[i][j - 1] 表示前一天还在做第 i 个科目,最后输出 Dp[m][n]。
代码:
骗分代码:
暴力枚举分界点,然后通过前缀和找到这一段的全部代价和。
#include<bits/stdc++.h>
using namespace std;
const int N = 2100;
int a[N], b[N], s[N], d[N][N];
int solve() {
int n, m;
cin >> n >> m;
for(int i = 1; i <= n; i++)cin >> a[i];
for(int i = 1; i <= m; i++)cin >> b[i];
memset(d, -0x3f, sizeof d);
d[0][0] = 0;
s[0] = 0;
for (int i = 1; i <= n; ++i) {
s[i] = s[i - 1] + a[i];
}
for (int i = 1; i <= n; ++i) {
for (int j = 1; j <= min(i, m); ++j) {
int mx = -1e9;
// 枚举 l 为分界点
for (int l = j - 1; l < i; ++l) {
mx = max(mx, d[l][j - 1] + b[j] * (s[i] - s[l]));
// 用前缀和算出 分界点到i的全部精力和,然后 * 学科价值。
}
d[i][j] = max(d[i][j], mx);
}
}
cout << d[n][m] << endl;
}
int main() {
int T;
cin >> T;
while(T--)solve();
return 0;
}
#include<bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
int d[N][N];
int a[N], b[N];
void solve() {
int n, m;
cin >> n >> m;
for(int i = 1; i<=n; i++) cin >> a[i];
for(int i = 1; i<=m; i++) cin >> b[i];
memset(d, -0x3f, sizeof d);
d[0][0] = 0;
for (int i = 1; i <= m; i ++) {
for (int j = 1; j <= n; j++) {
d[i][j] = max(max(d[i][j],d[i - 1][j - 1]), d[i][j - 1]) + a[j] * b[i];
}
}
cout << d[m][n] << endl;
}
int main() {
int T;
cin >> T;
while(T--)solve();
return 0;
}
T4. 先序遍历
题意:
给一棵 𝑛 个点的二叉树(根节点为 1),你可以进行以下操作至多 1 次:
选择 1 个(除了根之外的)点 𝑢, 断开 𝑢 和其父节点之间的边;然后重新选择另一个点作为 𝑢 的父节点、
将 𝑢 接上去,需要保证操作之后仍然是一棵以 1 为根的二叉树。
你想要操作之后的二叉树有字典序最小的先序遍历序列,输出这个序列。
思路:
首先你需要了解什么是树链剖分,按照题目要求的先序遍历将该树转化成一条链,然后对于这条链维护一个后缀最小值。
然后就是 树上贪心,分为两种情况:
情况1:从前往后找到一个 移走后可以让字典序变小的。然后从x节点往后找到第一个能够找到的第一个空节点同时空位的下一个节点比当前这个值大,如果无法找到就直接丢最后
情况2:从后往前找,找到第一个空节点,同时这个节点的先序遍历的下一个不是后缀最小的,那么就把最小后缀移动到空位。
代码:
#include<bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
int l[N], r[N], a[N], out_dfn[N], mn[N], in_dfn[N], cnt;
int ans[2][N], suf[N];
int n;
// 按照 先序遍历的方式把树变成链
void dfs(int u){
if(u == 0)return ;
in_dfn[u] = ++ cnt;
a[cnt] = u;
dfs(l[u]);
dfs(r[u]);
out_dfn[u] = cnt;
}
/*
从前往后找到一个 移走后可以让字典序变小的。
然后从x节点往后找到第一个能够找到的第一个空节点同时空位的下一个节点比当前这个值大,
如果无法找到就直接丢最后
*/
void w1(){
mn[n] = a[n];
for(int i = n - 1; i >= 1; i--){
mn[i] = min(a[i], mn[i + 1]);
}// 后缀最小值
int p = 1e6;
for(int i = 1; i <= n; i++){
suf[i] = 0;//找到后驱
if(l[a[i]] == 0){
// 如果 左节点为空,那么分析当前点的后驱的值和最小值的关系
// 如果不是最小值,就将这个子树移除寻找新的父节点。
suf[i] = i + 1;
if(mn[suf[i]] < a[suf[i]]){
p = min(p, suf[i]);
}
}
// 当前已经最小
if(p == 1e6){
for(int i = 1; i <= n; i++){
ans[0][i] = a[i];
}
return ;
}
// 然后从当前点让后找,找到一个字典序小于p的,然后进行新接操作。
int L = p;
for(int i = p; i <= n;i ++){
if(a[L] > a[i]) L =i;
}
int R = out_dfn[a[L]];
int t = 0;
// 先接 1~ p
for(int i = 1; i < p; i ++) ans[0][++t] = a[i];
// 再接 p 后的
for(int i = L; i <= R; i ++) ans[0][++t] = a[i];
// 在接 p ~ n 中 没有接过的 也就是 p的子树。
for(int i = p; i <= n; i ++)
if(i < L || i > R)ans[0][++t] = a[i];
}
}
/*
从后往前找,找到第一个空节点,同时这个节点的先序遍历的下一个不是后缀最小的,那么就把最小后缀移动到空位
*/
void w2(){
int L = 0;
for(int i = 2; i <= n; i++){
// 如果当前的树的最后一个节点不是n 同时他的值大于他的下一个节点。
if(out_dfn[a[i]] < n && a[out_dfn[a[i]] + 1] < a[i]){
L = i;
break;
}
}
// 不存在
if( L == 0){
for(int i = 1; i <= n; i++){
ans[0][i] = a[i];
}
return ;
}
int R = out_dfn[a[L]];
int p = 0;
// 开始寻找 R + 1 中的 值大于 L 的 接点。
for(int i = R + 1; i <= n ; i ++){
if(l[a[i]] == 0){
if(a[i] > a[L]){
if(p == 0) p = i + 1;
break;
}
else p = i + 1;
}
}
int t = 0;
// 这下面的操作
// 先 1~ p 把 非 L~ R的树的节点的节点先放进链中
for(int i = 1; i < p; i++)
if(i < L || i > R)ans[1][++t] = a[i];
// 接这颗树
for(int i = L; i <= R; i++)ans[1][++t] = a[i];
// 树后面的
for(int i = p; i <= n; i++)ans[1][++t] = a[i];
}
// 两种情况比较
int cmp(){
for(int i = 1; i <= n; i++){
if(ans[0][i] != ans[1][i])return ans[0][i] > ans[1][i];
}
return 0;
}
void solve(){
cin >> n;
for(int i = 1; i <= n; i++){
cin >> l[i] >> r[i];
}
cnt = 0;
dfs(1);//树链剖分
w1();//情况1
w2();//情况2
int d = cmp();//情况1和情况2比较
for(int i = 1; i <= n; i++){
cout << ans[d][i] << " ";
}
cout << endl;
return;
}
int main(){
int T;
cin >> T;
while (T --) solve();
return 0;
}