算两次、贡献法

算两次与贡献法详解

目录

一,算两次(富比尼原理)

1,多对多匹配问题

2,平面剖分问题

3,多重计数问题

4,等价个体互换

力扣 1503. 所有蚂蚁掉下来前的最后一刻

力扣 2731. 移动机器人

二,贡献法

CSU 1799 小Z的黑白棋

力扣 1863. 找出所有子集的异或总和再求和

力扣 2527. 查询数组 Xor 美丽值

力扣 1835. 所有数对按位与结果的异或和

力扣 477. 汉明距离总和

力扣 2063. 所有子字符串中的元音


一,算两次(富比尼原理)

算两次是指,对同一个组合计数,算出2个不同的表达式,从而推导出2个表达式相等。

1,多对多匹配问题

2,平面剖分问题

3,多重计数问题

4,等价个体互换

力扣 1503. 所有蚂蚁掉下来前的最后一刻

 有一块木板,长度为 n 个 单位 。一些蚂蚁在木板上移动,每只蚂蚁都以 每秒一个单位 的速度移动。其中,一部分蚂蚁向 左 移动,其他蚂蚁向 右 移动。

当两只向 不同 方向移动的蚂蚁在某个点相遇时,它们会同时改变移动方向并继续移动。假设更改方向不会花费任何额外时间。

而当蚂蚁在某一时刻 t 到达木板的一端时,它立即从木板上掉下来。

给你一个整数 n 和两个整数数组 left 以及 right 。两个数组分别标识向左或者向右移动的蚂蚁在 t = 0 时的位置。请你返回最后一只蚂蚁从木板上掉下来的时刻。

示例 1:

输入:n = 4, left = [4,3], right = [0,1]
输出:4
解释:如上图所示:
-下标 0 处的蚂蚁命名为 A 并向右移动。
-下标 1 处的蚂蚁命名为 B 并向右移动。
-下标 3 处的蚂蚁命名为 C 并向左移动。
-下标 4 处的蚂蚁命名为 D 并向左移动。
请注意,蚂蚁在木板上的最后时刻是 t = 4 秒,之后蚂蚁立即从木板上掉下来。(也就是说在 t = 4.0000000001 时,木板上没有蚂蚁)。
示例 2:

输入:n = 7, left = [], right = [0,1,2,3,4,5,6,7]
输出:7
解释:所有蚂蚁都向右移动,下标为 0 的蚂蚁需要 7 秒才能从木板上掉落。
示例 3:

输入:n = 7, left = [0,1,2,3,4,5,6,7], right = []
输出:7
解释:所有蚂蚁都向左移动,下标为 7 的蚂蚁需要 7 秒才能从木板上掉落。
示例 4:

输入:n = 9, left = [5], right = [4]
输出:5
解释:t = 1 秒时,两只蚂蚁将回到初始位置,但移动方向与之前相反。
示例 5:

输入:n = 6, left = [6], right = [0]
输出:6
 

提示:

1 <= n <= 10^4
0 <= left.length <= n + 1
0 <= left[i] <= n
0 <= right.length <= n + 1
0 <= right[i] <= n
1 <= left.length + right.length <= n + 1
left 和 right 中的所有值都是唯一的,并且每个值 只能出现在二者之一 中。

class Solution {
public:
	int getLastMoment(int n, vector<int>& left, vector<int>& right) {
		int ans = 0;
		for (int i = 0; i < left.size(); i++)ans = max(ans, left[i]);
		for (int i = 0; i < right.size(); i++)ans = max(ans, n-right[i]);
		return ans;
	}
};

力扣 2731. 移动机器人

有一些机器人分布在一条无限长的数轴上,他们初始坐标用一个下标从 0 开始的整数数组 nums 表示。当你给机器人下达命令时,它们以每秒钟一单位的速度开始移动。

给你一个字符串 s ,每个字符按顺序分别表示每个机器人移动的方向。'L' 表示机器人往左或者数轴的负方向移动,'R' 表示机器人往右或者数轴的正方向移动。

当两个机器人相撞时,它们开始沿着原本相反的方向移动。

请你返回指令重复执行 d 秒后,所有机器人之间两两距离之和。由于答案可能很大,请你将答案对 109 + 7 取余后返回。

注意:

  • 对于坐标在 i 和 j 的两个机器人,(i,j) 和 (j,i) 视为相同的坐标对。也就是说,机器人视为无差别的。
  • 当机器人相撞时,它们 立即改变 它们的前进方向,这个过程不消耗任何时间。
  • 当两个机器人在同一时刻占据相同的位置时,就会相撞。

    • 例如,如果一个机器人位于位置 0 并往右移动,另一个机器人位于位置 2 并往左移动,下一秒,它们都将占据位置 1,并改变方向。再下一秒钟后,第一个机器人位于位置 0 并往左移动,而另一个机器人位于位置 2 并往右移动。

    • 例如,如果一个机器人位于位置 0 并往右移动,另一个机器人位于位置 1 并往左移动,下一秒,第一个机器人位于位置 0 并往左行驶,而另一个机器人位于位置 1 并往右移动。

示例 1:

输入:nums = [-2,0,2], s = "RLL", d = 3
输出:8
解释:
1 秒后,机器人的位置为 [-1,-1,1] 。现在下标为 0 的机器人开始往左移动,下标为 1 的机器人开始往右移动。
2 秒后,机器人的位置为 [-2,0,0] 。现在下标为 1 的机器人开始往左移动,下标为 2 的机器人开始往右移动。
3 秒后,机器人的位置为 [-3,-1,1] 。
下标为 0 和 1 的机器人之间距离为 abs(-3 - (-1)) = 2 。
下标为 0 和 2 的机器人之间的距离为 abs(-3 - 1) = 4 。
下标为 1 和 2 的机器人之间的距离为 abs(-1 - 1) = 2 。
所有机器人对之间的总距离为 2 + 4 + 2 = 8 。

示例 2:

输入:nums = [1,0], s = "RL", d = 2
输出:5
解释:
1 秒后,机器人的位置为 [2,-1] 。
2 秒后,机器人的位置为 [3,-2] 。
两个机器人的距离为 abs(-2 - 3) = 5 。

提示:

  • 2 <= nums.length <= 105
  • -2 * 109 <= nums[i] <= 2 * 109
  • 0 <= d <= 109
  • nums.length == s.length 
  • s 只包含 'L' 和 'R' 。
  • nums[i] 互不相同。
class Solution {
public:
    int sumDistance(vector<int>& nums, string s, int d) {
        for(int i=0;i<s.length();i++)if(s[i]=='R')nums[i]+=d;else nums[i]-=d;
        sort(nums.begin(),nums.end());
        long long ans=0;
        for(int i=1;i<nums.size();i++)
            ans+=((long long)nums[i]-nums[i-1])*i%1000000007*(nums.size()-i)%1000000007;
        return ans%1000000007;
    }
};

二,贡献法

贡献法其实也蕴含了算两次的思想。

场景:要求若干项数据的和,而每个数据又可以分解成一些原子数据的和,那么就可以通过求每个原子数据在最终总和中的贡献,从而求出最终总和。

贡献法往往用于,若干项数据不太好求,但原子数据在最终总和中的贡献比较好求的场景。

CSU 1799 小Z的黑白棋

题目:


Description

小Z有一些黑白棋,他觉得黑白混杂在一起极具美感,所以他总喜欢将这些棋子排成一排序列S1,但是小Y就喜欢跟小Z作对,她会趁小Z不注意偷偷将小Z最右边的棋子拿走,往他棋子序列的最左边添加一个白色的棋子形成一个新的序列S2来破坏小Z的美感。

S2(1~n) = 白棋+S1(i=1~n-1)

小Z总相信第一感,他认为他自己最初排好的序列S1是最完美的,新的序列S2会造成一定的破坏美感指数 = damage(S1) = S1与S2有多少个位置黑色与白色互不对应

Exp:

令白棋为a,黑棋为b :S1 = ababa  S2=aabab   damage(S1)=4

因为小Z有很多种摆放序列的方式,现在他希望让你帮他求所有摆放序列的方式会造成的damage(S1)的平均值

Input

多组数据输入输出

每组数据输入一个整数n和m表示白棋和黑棋的数量 0<=n , m<=1000,000,000 , 保证n+m>=1

Output

每组输出一个平均值答案,用最简分数表示,如果可以化简到整数,就用整数表示

Sample Input

1 1
Sample Output

3/2

这个题目的思想大约就是富比尼原理了。

n个白棋和m个黑棋有C(m+n,n)种排列。

其中,第一个棋子为黑棋的,有C(m+n-1,n)种

第i(i=1,2,3......m+n-1)个棋子和第i+1个棋子颜色不同的,有2*C(m+n-2,n-1)种

2*C(m+n-2,n-1)*(m+n-1)=2*n*C(m+n-1,n)

所以,本题答案是(1+2*n)*C(m+n-1,n)/C(m+n,n)=(1+2*n)*m/(m+n)

代码:
 

#include<iostream>
#include<stdio.h>
using namespace std;
 
long long gcd(long long a,long long b)
{
    if(b==0)return a;
    return gcd(b,a%b);
}
 
int main()
{
    int m,n;
    long long a,b;
    while(scanf("%d%d",&n,&m)!=EOF)
    {
        a=n+n+1;
        a*=m;
        b=m+n;
        if(a%b==0)printf("%d\n",a/b);
        else  cout<<a/gcd(a,b)<<'/'<<b/gcd(a,b)<<endl;
    }
    return 0;
}

力扣 1863. 找出所有子集的异或总和再求和

一个数组的 异或总和 定义为数组中所有元素按位 XOR 的结果;如果数组为  ,则异或总和为 0 。

  • 例如,数组 [2,5,6] 的 异或总和 为 2 XOR 5 XOR 6 = 1 。

给你一个数组 nums ,请你求出 nums 中每个 子集 的 异或总和 ,计算并返回这些值相加之  。

注意:在本题中,元素 相同 的不同子集应 多次 计数。

数组 a 是数组 b 的一个 子集 的前提条件是:从 b 删除几个(也可能不删除)元素能够得到 a 。

示例 1:

输入:nums = [1,3]
输出:6
解释:[1,3] 共有 4 个子集:
- 空子集的异或总和是 0 。
- [1] 的异或总和为 1 。
- [3] 的异或总和为 3 。
- [1,3] 的异或总和为 1 XOR 3 = 2 。
0 + 1 + 3 + 2 = 6

示例 2:

输入:nums = [5,1,6]
输出:28
解释:[5,1,6] 共有 8 个子集:
- 空子集的异或总和是 0 。
- [5] 的异或总和为 5 。
- [1] 的异或总和为 1 。
- [6] 的异或总和为 6 。
- [5,1] 的异或总和为 5 XOR 1 = 4 。
- [5,6] 的异或总和为 5 XOR 6 = 3 。
- [1,6] 的异或总和为 1 XOR 6 = 7 。
- [5,1,6] 的异或总和为 5 XOR 1 XOR 6 = 2 。
0 + 5 + 1 + 6 + 4 + 3 + 7 + 2 = 28

示例 3:

输入:nums = [3,4,5,6,7,8]
输出:480
解释:每个子集的全部异或总和值之和为 480 。

提示:

  • 1 <= nums.length <= 12
  • 1 <= nums[i] <= 20

思路一:枚举

template<typename T>
vector<vector<T>> getAllsubStes(const vector<T>&v) //生成2^n个子集,仅限n<=30的场景
{
	vector<vector<T>>ans;
	for (int i = (1 << v.size()) - 1; i >= 0; i--) {
		vector<int>t;
		for (int j = 0; j < v.size(); j++)if (i&(1 << j))t.push_back(v[j]);
		ans.push_back(t);
	}
	return ans;
}

class Solution {
public:
	int subsetXORSum(vector<int>& nums) {
		vector<vector<int>>v = getAllsubStes(nums);
		int ans = 0;
		for (auto &vi : v) {
			int t = 0;
			for (auto x : vi)t ^= x;
			ans += t;
		}
		return ans;
	}
};

思路二:

利用组合数学的贡献法,考虑每一位的1在最终结果中的贡献,计算结果是贡献次数要么等于固定值1<< nums.size() - 1,要么等于0。

class Solution {
public:
	int subsetXORSum(vector<int>& nums) {
		int ans = 0;
		for (auto x : nums)ans |= x;
		return ans << nums.size() - 1;
	}
};

力扣 2527. 查询数组 Xor 美丽值

给你一个下标从 0 开始的整数数组 nums 。

三个下标 i ,j 和 k 的 有效值 定义为 ((nums[i] | nums[j]) & nums[k]) 。

一个数组的 xor 美丽值 是数组中所有满足 0 <= i, j, k < n  的三元组 (i, j, k) 的 有效值 的异或结果。

请你返回 nums 的 xor 美丽值。

注意:

  • val1 | val2 是 val1 和 val2 的按位或。
  • val1 & val2 是 val1 和 val2 的按位与。

示例 1:

输入:nums = [1,4]
输出:5
解释:
三元组和它们对应的有效值如下:
- (0,0,0) 有效值为 ((1 | 1) & 1) = 1
- (0,0,1) 有效值为 ((1 | 1) & 4) = 0
- (0,1,0) 有效值为 ((1 | 4) & 1) = 1
- (0,1,1) 有效值为 ((1 | 4) & 4) = 4
- (1,0,0) 有效值为 ((4 | 1) & 1) = 1
- (1,0,1) 有效值为 ((4 | 1) & 4) = 4
- (1,1,0) 有效值为 ((4 | 4) & 1) = 0
- (1,1,1) 有效值为 ((4 | 4) & 4) = 4 
数组的 xor 美丽值为所有有效值的按位异或 1 ^ 0 ^ 1 ^ 4 ^ 1 ^ 4 ^ 0 ^ 4 = 5 。

示例 2:

输入:nums = [15,45,20,2,34,35,5,44,32,30]
输出:34
解释:数组的 xor 美丽值为 34 。

提示:

  • 1 <= nums.length <= 105
  • 1 <= nums[i] <= 109

思路:

利用组合数学的贡献法,考虑每一位的1在最终结果中的贡献,显然贡献次数要么等于1,要么等于0,经过计算,这只取决于所有数中该位的1的数目的奇偶性。

class Solution {
public:
	int xorBeauty(vector<int>& nums) {
		int ans = 0;
		for (int i = 0; i < 30; i++) {
			int n = 0;
			for (auto x : nums)n += ((x >> i) & 1);
			if (n &1)ans += 1 << i;
		}
		return ans;
	}
};

力扣 1835. 所有数对按位与结果的异或和

列表的 异或和XOR sum)指对所有元素进行按位 XOR 运算的结果。如果列表中仅有一个元素,那么其 异或和 就等于该元素。

  • 例如,[1,2,3,4] 的 异或和 等于 1 XOR 2 XOR 3 XOR 4 = 4 ,而 [3] 的 异或和 等于 3 。

给你两个下标 从 0 开始 计数的数组 arr1 和 arr2 ,两数组均由非负整数组成。

根据每个 (i, j) 数对,构造一个由 arr1[i] AND arr2[j](按位 AND 运算)结果组成的列表。其中 0 <= i < arr1.length 且 0 <= j < arr2.length 。

返回上述列表的 异或和 。

示例 1:

输入:arr1 = [1,2,3], arr2 = [6,5]
输出:0
解释:列表 = [1 AND 6, 1 AND 5, 2 AND 6, 2 AND 5, 3 AND 6, 3 AND 5] = [0,1,2,0,2,1] ,
异或和 = 0 XOR 1 XOR 2 XOR 0 XOR 2 XOR 1 = 0 。

示例 2:

输入:arr1 = [12], arr2 = [4]
输出:4
解释:列表 = [12 AND 4] = [4] ,异或和 = 4 。

提示:

  • 1 <= arr1.length, arr2.length <= 105
  • 0 <= arr1[i], arr2[j] <= 109

利用组合数学的贡献法,考虑每一位的1在最终结果中的贡献。

class Solution {
public:
    int getXORSum(vector<int>& arr1, vector<int>& arr2) {
        int s1=0,s2=0;
        for(auto x:arr1)s1^=x;
        for(auto x:arr2)s2^=x;
        return s1&s2;
    }
};

力扣 477. 汉明距离总和

两个整数的 汉明距离 指的是这两个数字的二进制数对应位不同的数量。

给你一个整数数组 nums,请你计算并返回 nums 中任意两个数之间 汉明距离的总和 。

示例 1:

输入:nums = [4,14,2]
输出:6
解释:在二进制表示中,4 表示为 0100 ,14 表示为 1110 ,2表示为 0010 。(这样表示是为了体现后四位之间关系)
所以答案为:
HammingDistance(4, 14) + HammingDistance(4, 2) + HammingDistance(14, 2) = 2 + 2 + 2 = 6

示例 2:

输入:nums = [4,14,4]
输出:4

提示:

  • 1 <= nums.length <= 104
  • 0 <= nums[i] <= 109
  • 给定输入的对应答案符合 32-bit 整数范围
class Solution {
public:
	int totalHammingDistance(vector<int>& nums) {
		int mi = 1, ans = 0;
		for (int i = 0; i < 31; i++) {
			int num[2] = { 0,0 };
			for (auto x : nums)num[((x&mi) > 0)]++;
			ans += num[0] * num[1];
			mi <<= 1;
		}
		return ans;
	}
};

力扣 2063. 所有子字符串中的元音

给你一个字符串 word ,返回 word 的所有子字符串中 元音的总数 ,元音是指 'a''e''i''o' 和 'u' 。

子字符串 是字符串中一个连续(非空)的字符序列。

注意:由于对 word 长度的限制比较宽松,答案可能超过有符号 32 位整数的范围。计算时需当心。

示例 1:

输入:word = "aba"
输出:6
解释:
所有子字符串是:"a"、"ab"、"aba"、"b"、"ba" 和 "a" 。
- "b" 中有 0 个元音
- "a"、"ab"、"ba" 和 "a" 每个都有 1 个元音
- "aba" 中有 2 个元音
因此,元音总数 = 0 + 1 + 1 + 1 + 1 + 2 = 6 。

示例 2:

输入:word = "abc"
输出:3
解释:
所有子字符串是:"a"、"ab"、"abc"、"b"、"bc" 和 "c" 。
- "a"、"ab" 和 "abc" 每个都有 1 个元音
- "b"、"bc" 和 "c" 每个都有 0 个元音
因此,元音总数 = 1 + 1 + 1 + 0 + 0 + 0 = 3 。

示例 3:

输入:word = "ltcd"
输出:0
解释:"ltcd" 的子字符串均不含元音。

示例 4:

输入:word = "noosabasboosa"
输出:237
解释:所有子字符串中共有 237 个元音。

提示:

  • 1 <= word.length <= 105
  • word 由小写英文字母组成
class Solution {
public:
	long long countVowels(string word) {
		long long ans = 0, len = word.length();
		for (int i = 0; i < len; i++) {
			if (word[i] == 'a' || word[i] == 'e' || word[i] == 'i' || word[i] == 'o' || word[i] == 'u')ans += i * (len - 1 - i) + len;
		}
		return ans;
	}
};

### 关于两次遍历标签 两次遍历标签是一种常见的策略,通常用于处理特定的数据集合或图中的节点标记问题。这种方通过两次独立的遍历来完成某些操作目标,比如查找、分类或者更新状态等。 #### 1. **基本原理** 该方的核心在于利用两个阶段的遍历过程来解决问题。第一次遍历主要用于初始化或收集信息;第二次遍历时则依据前一次的结果执行进一步的操作。这种技术常被应用于图论和数组相关的场景中,能够有效降低单次遍历可能带来的复杂逻辑负担[^1]。 #### 2. **典型应用场景** ##### (1)连通分量检测 在无向图中寻找所有的连通子图是一个经典例子。可以通过如下方式实现: - 第一遍扫描所有未访问过的顶点并为其分配临时标志; - 第二轮迭代确认这些初步设定是否满足最终条件(如颜色一致性或其他属性匹配),从而调整其归属关系。 ```python def connected_components(graph): visited = set() labels = {} def dfs(node, label_id): stack = [node] while stack: current = stack.pop() if current not in visited: visited.add(current) labels[current] = label_id for neighbor in graph.get(current, []): if neighbor not in visited: stack.append(neighbor) label_counter = 0 for node in graph.keys(): if node not in visited: dfs(node, label_counter) label_counter += 1 return labels ``` 此处展示了如何运用深度优先搜索 (DFS) 来标注不同部分,并且整个流程分为明确的第一步探索与第二步总结两大部分。 ##### (2)字符串模式匹配 KMP(Knuth-Morris-Pratt) 字符串搜索内部也隐含着类似的思路——预构建失败函数表作为前期准备工作之后再正式开始逐字符对比工作。虽然严格意义上不属于传统意义上的双趟走读形式,但从宏观角度来看依然契合主题描述。 #### 3. **性能考量** 当讨论到此类方的时间消耗特性时,则需综合考虑内外层循环各自的贡献程度。如果假设输入规模为 n ,那么理想情况下总耗时应接近线性级别 O(n),因为每项元素理论上只会经历固定次数的动作触发[^2]。然而实际运行效果还会受到具体业务需求影响,例如额外增加辅助存储结构可能会稍微提升内存占用率却换来更优的整体表现速度。 另外值得注意的是快速排序过程中涉及到分区划分环节同样体现了相似的设计理念—先选定枢纽元位置后再分别对待两侧区间递归调用直至结束。尽管平均状况下的效能指标达到 O(n log n)[^3],但在极端条件下仍存在退化风险至平方级增长趋势(O(n²))。 ####
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值