一、【P1469】找筷子(快读+位运算)
我们考虑异或的两个小小的性质:
- k 个相同的数的异或和,当 k 为奇数时,结果是这个数本身,否则结果是 0。
- 任何数与 0 的异或值是它本身。
然后注意到题目。题目需要求 n 个数中出现奇数次的那个数,且保证这个数存在且只有一个。于是我们根据上面两个性质得出,答案就是这 n 个数的异或和。
原理:根据性质1,成对的筷子异或后就变成 00 了,只剩下落单的那一根。把这些 00 和落单的那一根的长度异或起来,根据性质2,结果显然就是落单的那一根的长度。
1.1 快读模板
inline int read()
{
int data = 0, w = 1;
char c = getchar();
while (c < '0' || c>'9')
{
if (c == '-') w = -1;
c = getchar();
}
while (c >= '0' && c <= '9')
{
data = data * 10 + (c - '0');
c = getchar();
}
return data * w;
}
1.2 AC代码
#include <iostream>
#include <string>
#include <algorithm>
#include <cmath>
#include <vector>
#include <cstring>
#include <queue>
#include <map>
using namespace std;
inline int read()
{
int data = 0, w = 1;
char c = getchar();
while (c < '0' || c>'9')
{
if (c == '-') w = -1;
c = getchar();
}
while (c >= '0' && c <= '9')
{
data = data * 10 + (c - '0');
c = getchar();
}
return data * w;
}
int main()
{
int n = read();
int ans = 0;
for (int i = 1; i <= n; i++)
{
int a = read();
ans = ans ^ a;
}
cout << ans;
}
二、【P1029】最大公约数和最小公倍数问题
2.1 最大公约数模板
递归方法:
ll gcd(ll x, ll y)//辗转相除法求公因子
{
if (y == 0) return x;
return gcd(y, x % y);
}
最优美方法:
int gcd(int x, int y)
{
while(y^=x^=y^=x%=y);
return x;
}
2.2 AC代码
最大公约数和最小公倍数的乘积就是满足条件的两个数的积。
注意爆 int 和 x * y 是完全平方数的情况。
#include <iostream>
#include <string>
#include <algorithm>
#include <cmath>
#include <vector>
#include <cstring>
#include <queue>
#include <map>
using namespace std;
typedef long long ll;
ll gcd(ll x, ll y)//辗转相除法求公因子
{
if (y == 0) return x;
return gcd(y, x % y);
}
int main()
{
ll x, y, ans = 0, flag = 0;
cin >> x >> y;
for (int i = 1; i <= sqrt(1ll * x * y); i++)
{
if ((1ll * x * y) % i == 0 && gcd(i, (1ll * x * y) / i) == x)
{
ans++;
if (1ll * i * i == 1ll * x * y) flag = 1;
}
}
cout << ans * 2 - flag; //最后乘以二是因为只遍历了一半
}
三、【P3383】线性筛素数(埃式筛)
2.1 一些简单筛法
1. 埃式筛:可以一边确认质数,一边筛出合数
//生成不大于 n 的所有质数
bool numlist[100000001];
int prime[20000001], cnt;
void work(int n)
{
for(int i=2; i<=sqrt(n); i++)
{
if(numlist[i]==false)
{
prime[++cnt] = i ;
for(int j=i; i*j<=n; j++)
numlist[i*j] = true;
}
}
return;
}
2. 欧拉筛:避免重复筛选浪费时间
bool numlist[100000001];
int prime[20000001], cnt;
void work(int n)
{
for(int i=2; i<=n; i++)
{
if(numlist[i]==false)
prime[++cnt] = i ;
for(int j=1; j<=cnt && i*prime[j]<=n; j++)
{
numlist[i*prime[j]] = true ;
if(i%prime[j]==0)
break;
}
}
return;
}
2.2 AC代码(埃式筛实现)
#include <iostream>
#include <string>
#include <algorithm>
#include <cmath>
#include <vector>
#include <cstring>
#include <queue>
#include <map>
using namespace std;
inline int read()
{
int data = 0, w = 1;
char ch = getchar();
while (ch < '0' || ch>'9')
{
if (ch == '-') w = -1;
ch = getchar();
}
while (ch >= '0' && ch <= '9')
{
data = 10 * data + ch - '0';
ch = getchar();
}
return data * w;
}
bool check[100000005];
int prime[10000005];
int sift(int n)
{
memset(check, true, sizeof(check));
check[0] = false, check[1] = false;
for (int i = 2; i <= sqrt(n); i++)
{
if (check[i])
{
for (int j = 2 * i; j <= n; j += i)
check[j] = false;
}
}
int cnt = 1;
for (int i = 1; i <= n; i++)
{
if (check[i])
{
prime[cnt++] = i;
}
}
return cnt - 1;
}
int main()
{
int n = read();
int index = sift(n);
int q = read();
for (int i = 1; i <= q; i++)
{
int a = read();
cout << prime[a] << endl;
}
}
四、【P1246】编码(组合数问题)
2.1 组合数模板
组合数计算公式:
组合数模板:
int C(int m, int n) //组合数计算
{
if (m == 0) return 1;
int res = 1;
for (int i = n; i > n - m; i--)
res *= i;
for (int i = m; i >= 1; i--)
res /= i;
return res;
}
2.2 AC代码及思路
本题中“按字典序升序排列”意为:每个单词只能出现一次,并且后一个单词的字典序必须大于前一个单词的字典序。
#include <iostream>
#include <string>
#include <algorithm>
#include <cmath>
#include <vector>
#include <cstring>
#include <queue>
#include <map>
using namespace std;
int C(int m, int n) //组合数计算
{
if (m == 0) return 1;
int res = 1;
for (int i = n; i > n - m; i--)
res *= i;
for (int i = m; i >= 1; i--)
res /= i;
return res;
}
int main()
{
string s; cin >> s;
int ans = 0;
int n = s.length();
for (int i = 1; i < n; i++) //如果不符合字典序
{
if (s[i] <= s[i - 1])
{
cout << "0";
return 0;
}
}
for (int i = 1; i < n; i++) //位数少于s的串一定比s小
ans += C(i, 26);
for (int i = 0; i < n; i++) //枚举相同位数的每种情况
{
for (char j = (i == 0 ? 'a' : s[i - 1] + 1); j < s[i]; j++)
{
ans += C(n - 1 - i, 'z' - j); //因为字符串下标从0开始,所以是n-i-1而不是n-i
}
}
cout << ans + 1; //加上自己
return 0;
}
五、【P1835】素数密度(欧式筛+枚举)
由于本题数据量极大(20亿),所以不能用简单的线性筛法来完成,我们可以先打出一些小素数,然后用这些素数进行二次筛选即可。
2.1 素数密度求解思路
题目要我们筛出L-R范围内的素数,那么我们只要将这个区间中的合数踢出去就可以了。
恰巧有一个关于判断合数的性质:对于一个合数n,一定可以在1 ~ sqrt(n)范围内找到一个质因子。那么对于量级为20亿的合数,一定可以找到一个小于等于sqrt(20亿)的质因子,约为50000。
大致流程如下:
- 筛出1-50000中的所有质数,并且对合数打上标记。
- 在L-R的范围内用我们已求出的质数筛出其中的合数(设p为质数,则i*p一定不为质数),并对其打上标记。
- 遍历L~R,没有打标记的元素即为我们所求的素数。
2.2 求解“最小的”“大于n”的“p的倍数”方法
start = max((n + p - 1) / p * p, 2 * p);
由于这个数肯定在[n,n+p)这个范围内,所以使用(n + p - 1) / p * p,可以轻松得到这个数,但由于用这个计算方法可能把 0 自己判断为满足条件的点(n为0时),所以使用2*p消去干扰。
2.3 AC代码
#include <iostream>
#include <string>
#include <algorithm>
#include <cmath>
#include <vector>
#include <cstring>
#include <queue>
#include <map>
using namespace std;
typedef long long ll;
bool numlist[1000005];
int prime[1000005];
int selt(int n)
{
memset(numlist, true, sizeof(numlist));
numlist[0] = false, numlist[1] = false;
int cnt = 1;
for (int i = 2; i <= n; i++)
{
if (numlist[i])
{
prime[cnt++] = i;
for (int j = 1; j <= cnt && i * prime[j] <= n; j++)
{
numlist[i * prime[j]] = false;
if (i % prime[j] == 0) break;
}
}
}
return cnt - 1;
}
int main()
{
ll l, r; cin >> l >> r;
ll ans = 0;
int rev = selt(50000);
memset(numlist, true, sizeof(numlist));
for (int i = 1; i <= rev; i++)
{
ll p = prime[i];
ll start = max((l + p - 1) / p * p, 2 * p);
for (ll j = start; j <= r; j += p)
numlist[j - l + 1] = false;
}
for (int i = 1; i <= r - l + 1; i++)
if (numlist[i]) ans++;
if (l == 1) cout << ans - 1; //排除1的干扰
else cout << ans;
}