A.Consecutive Points Segment-1671B
题意:
一条横轴上存在若干点,所有的点都不相同且严格递增。规定可以对每个点进行一次左移、右移或不变的操作,目标使所有点连续。
思路:
对于连续的点,当左端点或右端点移动时,所有的点需要跟随它一起移动;当两个点之间存在一个空隙,通过一次左移或右移使它们连续;当两个点之间存在两个空隙,通过左右点分别进行一次移动使它们连续。而整个轴上的空隙数量不会发生改变,也就是说空隙数量必须,才能实现。
另解:
要使最终序列连续,则点对应位置不能相差过大,首尾点间最多有两个位置可以不填入点。因此只需要计算首尾差值就可以判断能否达成目标
代码:
解法1:
#include <bits/stdc++.h>
#define int long long
const int maxn = 2e5 + 10;
using namespace std;
int t;
int a[maxn];
signed main() {
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
cin >> t;
while(t--)
{
int n; cin >> n;
int cnt = 0;
for(int i = 1; i <= n; i++){
cin >> a[i];
}
for(int i = 2; i <= n; i++){
cnt += a[i] - a[i - 1] - 1;
}
cout << (cnt > 2 ? "NO" : "YES") << endl;
}
return 0;
}
解法2:
#include <bits/stdc++.h>
#define int long long
const int maxn = 2e5 + 10;
using namespace std;
int t;
int a[maxn];
signed main() {
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
cin >> t;
while(t--)
{
int n; cin >> n;
int cnt = 0;
for(int i = 1; i <= n; i++){
cin >> a[i];
}
if(a[n] - a[1] <= n + 1){
cout << "YES" << "\n";
} else {
cout << "NO" << "\n";
}
}
return 0;
}
思考:
在考虑单个点的操作时,可以将其转化成多个点的共同操作,并且要遵守某个原则(在这道题当中是保持连续性),并且将每次操作所产生的影响,用一个更容易量化的东西来表达(这道题中就是中间空隙的个数)。
B.Make it Increasing-1667A
题意:
给定长度为n的数组a,现有一个长度为n且初始化为0的数组b。
每次操作可以选择一个下标i,使或。
求最少多少次操作可以使b严格升序。
思路:
要使b数组严格递增,那么一定使以某个位置为轴,左边全做减法,右边全做加法。
是可以接受的复杂度的,那么可以确定一个pos位置,分别计算两边需要进行操作的次数k,贪心出最小值。
对于左边(作正数考虑):
因要满足严格大于,所以左边应向下取整再+1
所以
同理可得右边应为,得
代码:
#include <bits/stdc++.h>
#define int long long
#define AC return 0;
#define endl '\n'
const int maxn = 5050;
using namespace std;
int n;
int a[maxn];
int b[maxn];
void solve() {
int ans = 1e18;
for(int pos = 1; pos <= n; pos++){
int res = 0;
for(int i = 1; i <= n; i++){
b[i] = 0;
}
for(int i = pos - 1; i >= 1; i--){
int k = (b[i + 1] + a[i]) / a[i];
b[i] = k * a[i];
res += k;
}
for(int i = pos + 1; i <= n; i++){
int k = (b[i - 1] + a[i]) / a[i];
b[i] = k * a[i];
res += k;
}
ans = min(res, ans);
}
cout << ans << endl;
}
signed main() {
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
cin >> n;
for(int i = 1; i <= n; i++) {
cin >> a[i];
}
solve();
AC
}
C.Permutation - 359B
题意:
给定n, k,构造一个长度为2n的排列,满足式子:
思路:
分析这个式子,前半部分是一个奇数和它后面的偶数作差的绝对值之和,后半部分是一个奇数和它后面的偶数作差之和的绝对值,那么我们先将一奇一偶记为一对。
怎么处理绝对值呢?正着看,奇数比偶数小1,作差得到一个负数,不便于处理。那么我们可以尝试构造逆序,让差值为正数,得到.怎么处理呢?如果能通过一次操作让式子结果+2,进行k次操作就可以了。现在只需要让前半部分得到1,后半部分得到-1即可,所以可以将一对数进行反转,前面通过绝对值得到1,后面通过绝对值得到-1.
代码:
#include <bits/stdc++.h>
#define int long long
#define AC return 0;
#define endl '\n'
const int N = 1e6 + 9;
int a[N];
using namespace std;
void solve() {
int n, k; cin >> n >> k;
for(int i = 1; i <= 2 * n; i++) a[i] = (2 * n) - i + 1;
for(int i = 1; i <= k; i++) swap(a[2 * i - 1], a[2 * i]);
for(int i = 1; i <= 2 * n; i++) cout <<a[i] << ' ';
}
signed main() {
ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
int _ = 1;
while(_--) {
solve();
}
AC
}
D.Lucky Chains-1766D
题意:
当二元组(x, y)存在,即x, y互质时,称为幸运的。
若有都是幸运的,则称为长度为的幸运链。
求给定生成的最长幸运链的长度。
思路:
从出发,直到遇到时停止。
具有一条性质:
所以终点可以化为.
是一个确定的常数,所以问题转换成求的最小值。
如果,那么必然存在至少一个公共的质因子,所以可以从入手,对分解质因数后进行枚举,设枚举数为,那么一个可能的取值就是最小的满足,即:
对于所有可能的取小得到的最小值,幸运链的长度从长度为,则就是答案。
要注意的问题是,每次询问进行一次的质因数分解显然会超时,所以需要用线性筛筛出,随后进行的枚举。
代码:
#include <bits/stdc++.h>
#define int long long
#define AC return 0;
#define endl '\n'
using namespace std;
const int maxn = 1e7 + 9, inf = 2e9;
int minp[maxn];
//欧拉筛
void euler(int n) {
bitset<maxn> vis;
vector<int> primes;
vis[0] = vis[1] = true;
minp[1] = 1;
for(int i = 2; i <= n; ++i){
if(!vis[i])primes.push_back(i), minp[i] = i;
for(int j = 0; j < primes.size() && i * primes[j] <= n; ++ j){
vis[i * primes[j]] = true;
minp[i * primes[j]] = primes[j];
if(i % primes[j] == 0)break;
}
}
}
void solve() {
int x, y;
cin >> x >> y;
//特判,当y-x为1时,链长为无穷
if(y - x == 1) cout << -1 << endl;
else{
int ans = inf;
y -= x;
while(y > 1){
int p = minp[y];
ans = min(ans, ((-x) % p + p) % p);
while(y % p == 0) y /= p;
}
cout << ans << endl;
}
}
signed main() {
ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
euler(1e7);
int _; cin >> _;
while(_--)solve();
AC
}
E.Binary String-1680C
题意:
有一个01串,你可以从串头或串尾删去一些字符(可以是0个),操作后的字符串可以为空。
要求开销(串中剩余0的数量,移除的1的数量)的最小值
思路:
1.双指针
去掉头尾的字符串,相当于在中间寻找一段满足要求的串。
设置c0记录剩余0的个数,c1记录移除1的个数,只需要维护这两个值,找到最小值作为答案。
我们不知道要截取多长的串,所以通过双指针来遍历整个串。
随着串的长度增加,c0会不变或增加,c1会不变或减少(二者不会同时发生),如果看作一个以右指针r为自变量的函数,这两个值会在某处相交而改变大小关系,而即使上半段函数的最小值处。
接下来要验证双指针的正确性,即左指针l右移时,右指针r必须同时右移。左指针移动会导致c0不变或减少,c1不变或增加,即一定会变成,会远离最佳答案,所以正确性得证。
2.前缀和
其实可以思考,需要截取的这段串的长度是否是确定的。
要让留在外面的1足够少,最理想的状态就是所有的1都是连续排列的,而这一段中每出现一个0,就意味着这一段外出现了一个1,所以确定了串的长度就是1的个数,c0和c1其实是等价的。
只需通过前缀和确定0的个数,再遍历长度为1的个数的区间,找出最小值即可。
从数学的角度来解释一下
当取得最小值时,两者相等
化简后有
代码:
#include <bits/stdc++.h>
#define int long long
#define AC return 0;
#define endl '\n'
const int maxn = 2e5+10;
using namespace std;
char s[maxn];
void solve() {
cin >> s + 1;
int n = strlen(s + 1);
int c0 = 0, c1 = 0;
for(int i = 1; i <= n; ++i) c1 += (s[i] == '1');
int ans = n;
//一开始双指针指的是一个空区间
for(int i = 1, j = 0; i <= n; ++i){
//保持c0 < c1这个条件
while(j + 1 <= n && c0 < c1){
j++;
if(s[j] == '1') c1--;
else c0++;
}
ans = min(ans, max(c0, c1));
//左指针移动的时候也要调整c0、c1
if(s[i] == '1') c1++;
else c0--;
}
cout << ans << endl;
}
signed main() {
ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
int t; cin >> t;
while(t--){
solve();
}
AC
}
#include <bits/stdc++.h>
#define int long long
#define AC return 0;
#define endl '\n'
using namespace std;
const int maxn = 2e5+10;
int t;
int pre[maxn];
void solve() {
string s;
int cnt = 0;//统计1个数
cin >> s;
int len = s.length();
for(int i = 1; i <= len; i++){
//前缀和统计0的个数
pre[i] = pre[i - 1] + (s[i-1] == '0');
if(s[i-1] == '1') cnt++;
}
int res = cnt;
for(int i = cnt; i <= len; i++){
res = min(res, pre[i] - pre[i - cnt]);
}
cout << res << endl;
}
signed main() {
cin >> t;
while(t--){
solve();
}
AC
}