【思维专练】专题训练 1
前言
思维训练 1,几乎没有什么算法,枚举 or 搜索 or 二分
- CF1681D Required Length
:暴搜 + 剪枝
前置函数
下文中称:
First. 为什么是搜索
开始看到这道题想到了贪心:每次找出最大的一个数乘上去。
但是很显然:可以先乘一个小的数,使得其中某一位增大大,再乘上最大值是优于直接每次都乘最大值的。例如:
所以,贪心无法做。
再看一眼数据范围,发现
每次都只
所以可以尝试一下搜索和剪枝。
Second.如何剪枝
-
可行性剪枝:如果一个数中没有任何一个数字
,且位数 ,那么该数一定不能再增加,所以可以剪掉。 -
最优化剪枝:如果当前到达
的次数再加上 (即要使 的长度到达 最少需要的步数), 那么,一定不是最优解。 -
最优化剪枝2:如果当前到达
的步数 之前到达 的步数,那么此时也不可能是最优解。
Third. 无解
如果
Code
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
#define int long long
bool check (int x) {
for (int p = x; p; p /= 10) {
if(p % 10 > 1) return 1;
}
return 0;
}
int getlen (int x) {
int len = 0;
for (int p = x; p; p /= 10) len ++;
return len;
}
int n, x;
int ans = 1e18;
map<int, int> res;
void dfs (int x, int cnt) {
int len = getlen(x);
if(!check(x) && len != n) return;
if(!res[x]) res[x] = 1e18;
if(cnt >= res[x] || cnt + (n - len) >= ans) return;
res[x] = cnt;
if(getlen(x) == n) {
ans = min(ans, cnt);
return;
}
for (int p = x; p; p /= 10) {
int num = p % 10;
if(num == 1 || num == 0) continue;
dfs(x * num, cnt + 1);
}
}
signed main() {
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
cin >> n >> x;
dfs(x, 0);
if(ans == 1e18) cout << -1;
else cout << ans << endl;
return 0;
}
- CF1469D Ceil Divisions
: 构造
First. 的位置
首先,题目要求操作次数在
由于序列是一个排列,为了减少次数,不妨让初始的
Second. 如何次数更小地形成
从最大数
设
当
所以,对于
我们只需要一直进行这个操作,使得
而每次操作,量级缩小至
Code
#include <bits/stdc++.h>
#include <bits/stdc++.h>
using namespace std;
const int N = 2e5 + 10;
int n;
signed main() {
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
int t;
cin >> t;
while(t --) {
cin >> n;
vector< pair<int, int> > ans;
for (int i = n; i >= 3; i --) {
int div = ceil(sqrt(i));
for (int j = div + 1; j < i; j ++) ans.push_back({j, i});
ans.push_back({i, div}), ans.push_back({i, div});
i = div + 1;
}
cout << ans.size() << endl;
for (auto opt : ans) cout << opt.first << ' ' << opt.second << endl;
}
return 0;
}
- CF1620C BA-String
:枚举
First. 缩点
对于一段长度为 *
,它的生成方案数不是
于是,不妨将所有连续的 *
所为一个可以生成 *
,后面成为
Second. 定位
考虑字典序最小,肯定是优先展开靠后的 *
。
对于后 *
,他们可以生成的字符串的种类数为
找到第一个
而对于 b
,就会增加
对于剩下 *
所形成的字符串种第 *
需要展开的次数。随后,输出即可。
Tips
可能爆 long long 所以求到第一个 的 就可以停下了,而前面的*
肯定不能展开。
Code
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int , int> pi;
#define endl '\n'
#define int long long
const int N = 2000 + 10;
int n , k , x , tot , tmp;
string s;
int len[N] , cnt[N] , p[N] , fac[N] , to[N];
bool type[N];
int ans[N];
signed main() {
ios::sync_with_stdio(0) , cin.tie(0) , cout.tie(0);
int _;
cin >> _;
while (_--) {
cin >> n >> k >> x >> s;
x--;
s = '.' + s;
tot = tmp = 0;
memset(type, 0, sizeof type), memset(len, 0, sizeof len), memset(fac, 0, sizeof fac);
memset(cnt, 0, sizeof cnt), memset(ans, 0, sizeof ans);
for (int i = 1; i <= n; i++) {
if (s[i] != s[i - 1]) len[++tot] = 1 , type[tot] = (s[i] == '*');
else len[tot]++;
}
for (int i = 1; i <= tot; i++) {
if (type[i]) to[i] = ++tmp , cnt[tmp] = len[i] * k + 1;
}
fac[tmp + 1] = 1;
for (int i = tmp; i >= 1; i--) {
if(fac[i + 1] * 1.0 > (x * 1.0 / (cnt[i] * 1.0))) break; // 防止爆 Long long
fac[i] = fac[i + 1] * cnt[i];
}
for (int i = 2; i <= tmp + 1; i++) {
if(fac[i] == 0) continue;
ans[i - 1] = x / fac[i];
x %= fac[i];
}
for (int i = 1; i <= tot; i++) {
if (!type[i]) {
for (int j = 1; j <= len[i]; j++) cout << 'a'; // 对于连续段 a
} else {
for (int j = 1; j <= ans[to[i]]; j++) cout << 'b'; // 对于连续段 b
}
}
cout << endl;
}
return 0;
}
- CF1550C Manhattan Subarrays
:暴力、BIT、组合数学 一开始震惊到我了,
过了,然后一算发现是 的。
First. 解析坏的三元组
画个图发现,当且仅当
回到题目上,由于每个数的横坐标是下标,所以坏三元组就转化为了一个长度为
Second. 没有坏三元组的区间
首先不妨进行最暴力的想法,枚举每个区间,看它存不存在坏三元组。
而没有坏三元组的子数组的最长长度为
证明:
如图,当两个同幅度的子序列相错的时候不能再添加任何一个点。
补充:正确证明是代入 Erdős–Szekeres 定理
Erdős–Szekeres 定理:对于
的偏序集 ,一定存在一条长度为 的链或长度为 的反链。 然后代入
,得 。
所以要是其不存在长度为的链,那么 ,所以最大为 。
所以对于每个左端点,我们至多会枚举
时间复杂度只有
Third. 判三元组
蒟蒻太蒟了,看到这种偏序形式就用树状数组了。
先离散化,建
然后就判断点
其实暴力更快。
Code
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int , int> pi;
#define endl '\n'
const int N = 2e5 + 10;
int n;
int a[N] , b[N];
struct bit {
#define lowbit(x) (x & -x)
int c[N];
void add(int x) {
for (int i = x; i <= n; i += lowbit(i)) c[i]++;
}
void erase(int x) {
for (int i = x; i <= n; i += lowbit(i)) c[i]--;
}
int sum(int x) {
int res = 0;
for (int i = x; i; i -= lowbit(i)) res += c[i];
return res;
}
};
bit t1 , t2 , t3;
signed main() {
ios::sync_with_stdio(0) , cin.tie(0) , cout.tie(0);
int t;
cin >> t;
while (t--) {
cin >> n;
for (int i = 1; i <= n; i++) cin >> a[i] , b[i] = a[i];
sort(b + 1 , b + 1 + n);
ll tmp = 0 , ans = 0;
for (int i = 1; i <= n; i++) if (b[i] != b[i - 1]) b[++tmp] = b[i];
for (int i = 1; i <= n; i++) a[i] = lower_bound(b + 1 , b + 1 + tmp , a[i]) - b;//离散化
for (int i = 1; i <= n; i++) {
bool flag = 1;
for (int j = i; j <= n; j++) {
if (t2.sum(a[j]) || t3.sum(n) - t3.sum(a[j] - 1)) {
flag = 0; // 不能暴力清空,会 TLE
for (int k = i; k < j; k++) {
t1.erase(a[k]);
if (t2.sum(a[k]) - t2.sum(a[k] - 1)) t2.erase(a[k]);
if (t3.sum(a[k]) - t3.sum(a[k] - 1)) t3.erase(a[k]);
}
break;
}
ans++;
if (t1.sum(a[j])) t2.add(a[j]);
if (t1.sum(n) - t1.sum(a[j] - 1)) t3.add(a[j]);
t1.add(a[j]);
}
if (!flag) continue;
for (int k = i; k <= n; k++) {
t1.erase(a[k]);
if (t2.sum(a[k]) - t2.sum(a[k] - 1)) t2.erase(a[k]);
if (t3.sum(a[k]) - t3.sum(a[k] - 1)) t3.erase(a[k]);
}
}
cout << ans << endl;
}
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· winform 绘制太阳,地球,月球 运作规律
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)