<题单>#5题解

Count The Number

给你一个长度为 n 的序列,总共 m 次询问,每次询问是三个数字 L R X,针对每次询问,你需要回答区间 [L,R] 内有多少个元素等于 X。

解析

分析题目,观察数据范围,\(x<=9\),对数组\(arr\)区间的询问,可以用前缀和思想来解决,设前缀和数组\(preSum[N][10]\)\(preSum[i][j]\)的意义为$\sum_1^i(r[i]==x) $ 即1~\(i\)数字为\(x\)的个数,假如我们要获取一段区间\([i,j]\)的数字x的个数,其值为 \(preSum[j][x]-preSum[i-1][x]\)。下面用结构体解决,思想一样。

#include<iostream>
#include<stack>
using namespace std;
typedef long long ll;
ll mod;
struct numb
{
	int r[10];
}arr[100005];
int main()
{
	int n;
	cin >> n;
	for (int i = 1; i <= n; i++)
	{
		int x;
		cin >> x;
		arr[i].r[x]++;
		for (int t = 0; t <=9; t++)
		{
			arr[i].r[t]+= arr[i - 1].r[t];
		}
	}
	int q;
	cin >> q;
	while (q--)
	{
		int l, r, x;
		cin >> l >> r >> x;
		cout << arr[r].r[x] - arr[l - 1].r[x] << endl;
	}
}

好看的字符串

罗少很喜欢字符串,他认为一个字符串相同的字符越少,这个字符串就越好看,在他心里有这样一个定义:

如果某个字符在这个字符串中只出现了一次,那么这个字符串的好看值就加一,如果某个字符在这个字符串中出现了 n(n>1) 次,那么这个字符串的好看值就要减去 (n - 1)。

解析

  1. 数据量小,因此可以双指针循环暴力获取每一个串。
  2. 问题就转化成,如何快速获取字符串\(str\)的好看值。
  3. 字符串的好看值如果用暴力时间复杂度较大,因此我们用前缀和去维护,可以快速得到一个串的好看值。
  4. 26代表26个字母,我们设置,前缀和数组a\(preSum[N][26]\),其含义为$\sum_1^i(r[i]==j) $ 即1~\(i\)字母为\(j\)的个数,假如我们要获取一段区间\([i,j]\)的字母x的个数,其值为 \(preSum[j][x-‘a']-preSum[i-1][x-'a']\)
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
int arr[305][27];
int main()
{
	int t;
	cin >> t;
	while (t--)
	{
		int n;
		string s;
		memset(arr, 0, sizeof(arr));
		cin >> n>>	s;
		s = "*" + s;
		for (int i = 1; i < s.length(); i++)
		{
			arr[i][s[i] - 'a']++;
			for (int j = 0; j < 27; j++)
			{
				arr[i][j] += arr[i - 1][j];
			}
		}
		int ans = 0;
		for (int i = 1; i < s.length(); i++)
		{
			for (int j = i; j < s.length(); j++)
			{
				int value = 0;
				for (int k = 0; k < 26; k++)
				{
					int t = arr[j][k] - arr[i - 1][k];
					if (t == 1)
					{
						value++;
					}
					else if(t>1)
					{
						value -= (t - 1);
					}
				}
				ans = max(ans, value);
			}
		}
		cout << ans << endl;
	}
}

求和

一个大小为n的数组(1<=n<=1000000),存有n个数字a[i](1<=i<=n,1<=a[i]<=1000000),然后进行Q次查询(1<=Q<=100000),每次查询提供L和R,要求输出sum=a[L]+2a[L+1]+3a[L+2]+...+(R-L+1)*a[R](1<=L<=R<=n)

解析(做法1)公式分解

  1. 观察题目,区间求和,想到用前缀和思想。

  2. 变形公式

    \[sum=a[L]+2*a[L+1]+3*a[L+2]+...+(R-L+1)*a[R] \]

    \[sum= (L-L+1)a[L] +(L+1- L+1)*a[L+1]+(L+2-L+1)*a[L+2]+...+(R-L+1)*a[R] \]

\[sum= L*a[L] +(L+1)*a[L+1]+(L+2)*a[L+2]+...+R*a[R]- (L+1) \sum_{i=L}^{R}a[i] \]

  1. 我们构造两个前缀和

    \[preSum1[n]=\sum_{i=1}^ni*a[i] \]

    \[preSum2[n]=\sum_{i=1}^na[i] \]

  2. 化简公式

\[sum=( preSum1[R]-preSum1[L-1]) - (L+1)(preSum2[R]-preSum2[L-1]) \]

这样就变成普通的前缀和了,就可以很快得到上面的结果了。套路都一样,尝试去化简公式。

        
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 1000005;
ll a[N], b[N]; //累加的过程之中会爆long long 
int main()
{
	int n, q;
	cin >> n >> q;
	for (int i = 1; i <= n; i++)
	{
		ll x;
		cin >> x;
		a[i] = a[i - 1] + x;
		b[i] = b[i - 1] + i*x;
	}
	while (q--)
	{
		int L, R;
		cin >> L >> R;
		cout << b[R] - b[L - 1] - (L - 1) * (a[R] - a[L - 1]) << endl;
	}
}

解析(做法2)双重朴素前缀和

\(pre[i]=pre[i-1]+a[i]\)//普通前缀和

\(prepre[i]=prepre[i-1]+a[i]\)//前缀和的前缀和

代码如下,本文不多做赘述,有疑问的私我。

        
#include<bits/stdc++.h>
using namespace  std;
typedef long long ll;
const ll N =1e9;
const ll Mod = 1000000009;
const ll MX = 0x3f3f3f3f;
ll a[1000001], pre[1000001], prepre[1000001];
int main() {
	int n, q;
	cin >> n >> q;
	for (int i = 1; i <= n; i++)
	{
		cin >> a[i];
		pre[i] = pre[i-1] + a[i];
		prepre[i] = prepre[i - 1] + pre[i];
	}
	while (q--)
	{
		int l, r;
		ll  ans = 0;
		cin >> l>>r;
		ans = (r - l + 1) * pre[r];
		ans -= (prepre[r - 1] - prepre[l - 2]);
		cout << ans << endl;
	}
	return 0;
}

dp怪鸽

众所周知ovo非常喜欢"dp"这个组合,某一天他得到了一个字符串s,他可以每次花费一点理智把'd'变成'p'或者把'p'变成'd',从而使得这个字符串变成前半段都是'd',后半段都是'p'的字符串

解析

  1. 遍历每个点,对于每个点,把这个点左边全部p变成d,右边变成全部p,需要耗费最小的步骤。
  2. 用两个前缀和分别维护左边的p的个数,和右边的d的个数
		for (int i = 1; i < s.length(); i++) {
			p[i] = p[i-1] + (s[i] == 'p');
		}
		for (int i = s.length() - 1; i >=1; i--) {
			d[i] = d[i+1] + (s[i] == 'd');
		}

3.当我们遍历的时候,就可以很快的计算出把左边所有p变成d,右边所有d变成p需要的步骤数。

4.但是需要注意的是,遍历到边界的时候,它的左边或者右边是没有d或p的,因此需要判断这个字符是否是d或是p,如果不是,要多算一步骤,其他情况下,遍历到的字符不需要考虑。


        
#include<bits/stdc++.h>
typedef long long ll;
using namespace std;
const int N = 100005;
int d[N];//从左到右d的个数
int p[N];//从右到左p的个数
int main() {
	int t;
	cin >> t;
	string s;
	while (t--) {
		cin >> s;
		s=' '+s;
		memset(p, 0, sizeof(p));
		memset(d, 0, sizeof(d));
		for (int i = 1; i < s.length(); i++) {
			p[i] = p[i-1] + (s[i] == 'p');
		}
		for (int i = s.length() - 1; i >=1; i--) {
			d[i] = d[i+1] + (s[i] == 'd');
		}
		int ans = N;
		for (int i = 1; i < s.length(); i++) {
			int x = d[i + 1] + p[i - 1];
			if (i == 1 && s[i] == 'p')x++;
			if (i == s.length() - 1 && s[i] == 'd')x++;
			ans = min(ans,x);
		}
		cout << ans << endl;
	}
}	

    


奇异数

如果一个数字,除了1和自身以外,质因数的个数(如果重复,只算一次,例如9的质因数只有3,个数是1个)是奇数个,则称之为奇异数。统计a~b之间(包含a和b)有多少个这样的奇异数。

解析(埃氏筛+前缀和)

  1. 发现统计一个区间已经是很基本了,经过前面的练习发现,前缀和只是一个工具了,重点是如何快速计算一个数的质因子,这边推荐的是埃氏筛,博客在这:https://www.cnblogs.com/Fanxw/p/14083097.html
  2. 唯一的技巧就是,可以一边进行筛法的处理,一边进行前缀和的累加。

        
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 1000005;
ll sum[N]={};
int pr[N];
int main()
{
	for (int i = 2; i < N; i++)//其实筛法到根号n就可以停止了,但是这边是在维护前缀和
	{
		if (pr[i] == 0)//质数
		{
			for (int j = i+i; j < N; j += i)
			{
				pr[j]++;//因子个数增加
			}
		}
		sum[i] = sum[i - 1]+(pr[i]&1);// 说明一下...  &1 如果是奇数返回就是1,位运算看起来更简洁.
	}
	int n;
	cin >> n;
	while (n--)
	{
		int a, b;
		cin >> a >> b;
		cout << sum[b] - sum[a - 1] << endl;
	}
}

tql和她的数学题

解析(埃氏筛+前缀和)

和上题一样,输入量较大,用scanf输入

#include<iostream>
#include<stack>
using namespace std;
typedef long long ll;
ll mod;
const int N = 200005;
int sum[N];
int main()
{
	for (int i = 1; i < N; i++)
	{
		for (int j = i; j < N; j += i)
		{
			sum[j]++;
		}
		sum[i]+=sum[i-1];
	}
	int t;
	cin >> t;
	while (t--)
	{
		int l, r;
		scanf("%d%d", &l, &r);
		printf("%d\n", sum[r] - sum[l - 1]);
	}
}

勤劳的园丁

未开始施肥的时候,每块土地的肥沃度都是0。当某块土地在某天被施肥后,该块土地的肥沃度会加1。问进行了d天的施肥后,肥沃度至少为c的土地有多少米?

解析

  1. 维护差分数组,差分就是和前缀和相反的一种思维,前缀和是 \(sum[i]=sum[i-1]+a[i]\),差分刚好相反\(a[i]-a[i-1]=df[i]\)
  2. 差分数组就是记录每一个数字和上一个数字差了多少,即\(a[i]-a[i-1]=df[i]\),如果我们对一段区间\([L,R]\)每一个数字都加1,对于差分数组而言,只有\(df[L]\)加一 和 \(df[R+1]\)减一。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 1000005;
int df[1000005] = {};
int main()
{
	int n;
	cin >> n;
	while (n--)
	{
		int m, c, d;
		int ans = 0;
		cin >> m>>c>>d;
		memset(df, 0, sizeof(df));
		for (int i = 1; i <=d; i++)
		{
			int a, b;
			cin >> a >> b;
			df[a]++;
			df[b + 1]--;
		}
		int x = 0;
		for (int i = 1; i <= m; i++)
		{
			x = x + df[i];
			ans +=(x>=c);
		}
		cout << ans << endl;
	}
}

淘气的宝宝

解析

  1. 我们可以用数字来表示开关,奇数代表开,偶数代表关,如果对这区间的灯泡进行操作,那么就是这段区间的数字增加1,(奇数+1=偶数)。
  2. 注意内存空间,要用short int 数组
#include<bits/stdc++.h>
typedef long long ll;
using namespace std;
const int N = 1000005;
 short int df[N];
int main() {
	int t;
	cin >> t;
	while (t--) {
		int n,q;
		cin >> n >> q;
		memset(df, 0, sizeof(df));
		for (int i = 1; i <= n; i++) {
			cin >> df[i];
		}
		ll pre = 0;
		for (int i = 1; i <= n; i++) {
			ll temp = df[i];
			df[i] = df[i] - pre;
			pre = temp;
		}
		while (q--) {
			int l, r;
			cin >> l >> r;
			df[l]++;
			df[r+1]--;
		}
		ll res = 0;
		ll sum = 0;
		for (int i = 1; i <= n; i++) {
			sum = sum + df[i];
			if (sum % 2 == 1) { res += i; }
		}
		cout << res << endl;
	}
}	
    
posted @ 2021-02-16 22:24  ouk  阅读(126)  评论(0编辑  收藏  举报