主要是对acwing平台学的题目进行一个总结
第一题数字诗意
本质是一道数学,如果一个数能够以若干个(至少两个)连续的正整数相加表示,那么它就蕴含诗意。所以题目就是要求有多少个数是可以由至少两个连续的正整数相加表示。
打表找规律:
假设有k个数分别为a, a+1, a+2, ...,a+k-1
求和后(a + a + k - 1) * k / 2 = s,2a+k-1和k一定是一奇一偶,由于要求至少两个所以k>=2,并且a>=1,所有2a+k-1>=3,所有s中应该存在大于等于1的奇数因子,所以一个数如果不包含大于等于1的奇数因子则不蕴含诗意。充分性:如果包含大于等于1的奇数因子则可以由至少两个连续的正整数相加表示,也是成立的;
令s = pm,其中m是2的整数次幂,p是剩余的因数
1、p > m,令奇数p/2作为中心界,两边分别为a, b,且a + b = p, a<p/2,b>p/2,这样的一共有m组
2、p < m,令偶数m作为中心,两边是m - 1,m + 1,这样的对称一共有p - 1组
综上,如果p不是大于等于1的奇数因子,那么就不蕴含诗意,就不是题目要找的数。
#include<iostream>
#include<cstdio>
using namespace std;
typedef long long LL;
int main()
{
int n;
cin>>n;
LL res = 0;
while(n --)
{
LL x;
scanf("%lld", &x);
//删去偶数因子
while(x % 2 == 0) x /= 2;
if(x == 1) res ++;
}
cout<<res<<endl;
return 0;
}
如果想判断一个数是不是2的整数次幂可以使用位运算降低复杂度,不用一直除2处理
if(x & (x - 1) == 0) res ++;
因为对于2的整数次幂的数的二进制表达为10000,如果x - 1则为1111,所以10000 & 1111 = 0
第二题封闭图形的个数
就是双关键字排序
代码如下
#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
const int N = 200010;
//使用数组记录每个数字封闭图形的数目
int cnt[] = {1, 0, 0, 0, 1, 0, 1, 0, 2, 1};
int q[N];
int n;
int get(int a)
{
int ans = 0;
while(a) ans += cnt[a % 10], a /= 10;
return ans;
}
int main()
{
cin>>n;
for(int i = 0; i < n; i ++)
scanf("%d", &q[i]);
//使用匿名函数自定义比较函数
sort(q, q + n, [&] (int a, int b)
{
int na = get(a), nb = get(b);
if(na == nb) return a < b;
else return na < nb;
});
for(int i = 0; i < n; i ++)
printf("%d ", q[i]);
return 0;
}
第三题回文数组
思路为贪心
加一和减一是相同的,给右边的数加了,相当于给对称的左边加一,所以只需要考虑加1就行
偶数数组对中间两个同时加或者减少是没有效果的,如果奇数中心也没有效果,如果对奇数中心和后边一点减少1,相当于对前一个点和奇数中心加1。所以只考虑加法
统计每个数需要加多少次才能变成回文数组,枚举每对数,将较小的数提高
相邻两个数如果可以同时加,效果一定好过单次加,结果一定更好
#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
const int N = 100010;
int q[N], n;
int main()
{
cin>>n;
for(int i = 0; i < n; i ++)
scanf("%d", &q[i]);
for(int i = 0, j = n - 1; i <= j; i ++, j --)
{
//将数组转化为每个位置需要单独加一这个操作的次数
int t = min(q[i], q[j]);
q[i] -= t;
if(i != j) q[j] -= t;
}
long long res = 0;
//如果可以相邻的同时加1就合并操作
for(int i = 0; i < n; i ++)
{
int t = q[i];
res += t;
q[i + 1] -= min(t, q[i + 1]);
}
printf("%lld", res);
return 0;
}
第四题商品库存管理
差分数组模板题目
如果想知道某个区间的操作取消后,能有多少为库存为0的商品,相当于求这段区间有多少库存为1的商品,可以用前缀和解决区间有多少为1的商品,因为商品数量不是1就是0
由于这个题目起始商品数量都是0,相当于简化了,直接操作差分数组b就行。
#include<iostream>
#include<cstdio>
using namespace std;
const int N = 300010;
int l[N], r[N];//保存每个操作区间的左右端点
int b[N], s[N];//分别为差分数组和前缀和数组
int n, m;
int main()
{
cin>>n>>m;
//记录所有操作的同时对差分数组进行操作
for(int i = 1; i <= m; i ++)
{
scanf("%d%d", &l[i], &r[i]);
b[l[i]] ++, b[r[i] + 1] --;
}
int zero = 0;
//将b[i]数组求前缀和操作,得出现在的商品库存数
for(int i = 1; i <= n; i ++)
{
b[i] += b[i - 1];
if(!b[i]) zero ++;
else if(b[i] == 1) s[i] ++;//维护数组s
//原地对s[i]求前缀和,表示1~i区间的库存为1的商品数
s[i] += s[i - 1];
}
//处理每个操作取消的情况,区间内为库存1的数量,加上所有库存为0的数量
for(int i = 1; i <= m; i ++)
printf("%d\n", zero + s[r[i]] - s[l[i] - 1]);
return 0;
}
考虑是否存在区间包含库存0,撤销这个区间的操作后库存变为-1的情况?
实际不会因为如果对这个区间操作后库存还是0,只能说明之前库存为-1,而题目说初始库存都是0,所以对区间操作撤销前,这个区间的库存只会大于等于1。