ACM模板(C++)
1、大数
加法,乘法模板
//题目链接 : http://poj.org/problem?id=2506
//题目大意 : 就是问你用2*1,1*2,2*2的砖拼成2*n的长方形,有多少种拼法
//解题思路 : 考虑n的时候,假设我们已经铺好了n-1块砖,第n块只能竖着放
//假设我们已经铺好了n-2块砖,最后两列有3种方式,但是其中有一种方法和上面是相同的
//所以f[n] = 2* f[n-2] + f[n-1]
#include
#include
#include
using namespace std;
const int maxn = 10000 + 10;
//加法
string bigIntegerAdd(string s1,string s2){
int a[maxn],b[maxn];
memset(a,0,sizeof(a));
memset(b,0,sizeof(b));
int len1 = s1.size(),len2 = s2.size();
int maxL = max(len1,len2);
for(int i = 0; i < len1; i++)a[i] = s1[len1-1-i]-'0';
for(int i = 0; i < len2; i++)b[i] = s2[len2-1-i]-'0';
for(int i = 0; i < maxL; i++){
if(a[i]+b[i] >= 10){
int temp = a[i]+b[i];
a[i] = temp%10;
a[i+1] += (temp/10);
}
else a[i] += b[i];
}
string c = "";
if(a[maxL] != 0)
c += a[maxL] + '0';
for(int i = maxL-1; i >= 0; i--)c += a[i] + '0';
return c;
}
//乘法
string bigIntegerMul(string s1,string s2){
int a[maxn],b[maxn],c[maxn*2 + 5];
memset(a,0,sizeof(a));
memset(b,0,sizeof(b));
memset(c,0,sizeof(c));
int len1 = s1.size(),len2 = s2.size();
for(int i = 0; i < len1; i++)a[i] = s1[len1-1-i]-'0'; //倒置
for(int i = 0; i < len2; i++)b[i] = s2[len2-1-i]-'0';
for(int i = 0; i < len1; i++){
for(int j = 0; j < len2; j++){
c[i+j] += a[i]*b[j];
}
}
for(int i = 0; i < maxn*2; i++){
if(c[i] >= 10){
c[i+1] += c[i]/10;
c[i] %= 10;
}
}
string ans = "";
int i;
for(i = maxn * 2; i >= 0; i--)
if(c[i] != 0)
break;
for(;i >= 0; i--)ans += c[i] + '0';
return ans;
}
int main(){
//freopen("in.txt","r",stdin);
int n;
string s[255];
s[0] = "1",s[1] = "1"; //注意0的时候是1
for(int i = 2;i <= 255; i++){
string temp = bigIntegerMul("2",s[i-2]);
s[i] = bigIntegerAdd(s[i-1],temp);
}
while(~scanf("%d",&n))
cout<
return 0;
}
减法模板
#include
const int maxn = 200 + 10;
using namespace std;
typedef long long LL;
//具体实现
string subInfo(char *s1,char *s2){
int a[maxn],b[maxn];
memset(a,0,sizeof(a));
memset(b,0,sizeof(b));
int len1 = strlen(s1),len2 = strlen(s2);
int maxLen = max(len1,len2);
for(int i = 0; i < len1; i++) a[i] = s1[len1 - i - 1] - '0';
for(int i = 0; i < len2; i++) b[i] = s2[len2 - i - 1] - '0';
for(int i = 0; i < maxLen; i++){
if(a[i]-b[i] < 0){
a[i] = a[i]+10-b[i];
a[i+1] -= 1;
}
else a[i] -= b[i];
}
string str = "";
int i;
for(i = maxLen-1; i >= 0; i--)if(a[i] != 0)break;
for(;i >= 0; i--)str += a[i]+'0';
return str;
}
//大数减法的模板
string bigIntegerSub(char *s1,char *s2){
if(s1 == s2)
return "0"; //相等
int len1 = strlen(s1),len2 = strlen(s2);
if(len1 > len2)
return subInfo(s1,s2);
else if(len1 < len2)
return "-" + subInfo(s2,s1); //负数
else { //长度相等时判断大小
for(int i = 0; i < len1; i++){
if(s1[i]-'0' > s2[i]-'0')
return subInfo(s1,s2);
else if(s1[i]-'0' < s2[i]-'0')
return "-" + subInfo(s2,s1);
}
}
}
int main(){
char s1[maxn],s2[maxn];
scanf("%s\n%s",s1,s2);
cout<
return 0;
}
求阶乘以及位数模板
//http://acm.nyist.edu.cn/JudgeOnline/problem.php?pid=28
//大数阶乘的模板
#include
using namespace std;
const int maxn = 100000 + 10;
//大数计算阶乘位数
//lg(N!)=[lg(N*(N-1)*(N-2)*......*3*2*1)]+1 = [lgN+lg(N-1)+lg(N-2)+......+lg3+lg2+lg1]+1;
int factorialDigit(int n){
double sum = 0;
for(int i = 1; i <= n; i++){
sum += log10(i);
}
return (int)sum+1;
}
//大数计算阶乘
string bigFactorial(int n){
int ans[maxn],digit = 1;
ans[0] = 1;
for(int i = 2; i <= n; i++){
int num = 0;
for(int j = 0; j < digit; j++){
int temp = ans[j]*i + num;
ans[j] = temp%10;
num = temp/10;
}
while(num != 0){
ans[digit] = num%10;
num /= 10;
digit++;
}
}
string str = "";
for(int i = digit-1; i >= 0; i--)
str += ans[i] + '0';
return str;
}
int main(){
int n;
while(~scanf("%d",&n)){
//cout<
cout<
}
return 0;
}
二分
二分的写法可以有很多种,这里列举几个常见的,主要是上界确定,区间以及查找元素是否重复的问题,也可以看看这篇文章。
求最小的i,使得a[i] = key,若不存在,则返回-1(lowerbound函数);
求最大的i的下一个元素的下标(c++中的upperbound函数),使得a[i] = key,若不存在,则返回-1;
求最大的i,使得a[i] = key,若不存在,则返回-1;
求最小的i,使得a[i] > key,若不存在,则返回-1;
求最大的i,使得a[i] < key,若不存在,则返回-1;
#include
using namespace std;
const int maxn = 100 + 10;
int cmp(const void *a, const void *b) {
return *(int *) a - *(int *) b;
}
//普通的二分查找
int bs(int *arr,int L,int R,int target){
while( L <= R){
int mid = (L) + (R-L)/2;
if(arr[mid] == target)
return mid;
if(arr[mid] > target)
R = mid - 1;
else
L = mid + 1;
}
return -1; // not find
}
//求最小的i,使得a[i] = target,若不存在,则返回-1
//返回 如果有重复的 下界(比如1,2,2,2,3,4)查找2,返回1
int firstEqual(int arr[], int L, int R, int target) {
while (L < R) {
int mid = L + (R - L) / 2;
if (arr[mid] < target)
L = mid + 1;
else
R = mid;
}
if (arr[L] == target)
return L;
return -1;
}
//求最大的i的下一个元素的下标(c++中的upperbound函数),使得a[i] = target,若不存在,则返回-1
int lastEqualNext(int arr[], int L, int R, int target) {
while (L < R) {
int m = L + (R - L) / 2;
if (arr[m] <= target)
L = m + 1;
else
R = m;
}
if (arr[L - 1] == target)
return L;
return -1;
}
//求最大的i,使得a[i] = target,若不存在,则返回-1
int lastEqual(int arr[], int L, int R, int target) {
while (L < R) {
int mid = L + ((R + 1 - L) >> 1);//向上取整
if (arr[mid] <= target)
L = mid;
else
R = mid - 1;
}
if (arr[L] == target)
return L;
return -1;
}
//求最小的i,使得a[i] > target,若不存在,则返回-1
int firstLarge(int arr[], int L, int R, int target) {
while (L < R) {
int m = L + ((R - L) >> 1);//向下取整
if (arr[m] <= target)
L = m + 1;
else
R = m;
}
if (arr[R] > target)
return R;
return -1;
}
//求最大的i,使得a[i] < target,若不存在,则返回-1
int lastSmall(int arr[], int L, int R, int target) {
while (L < R) {
int m = L + ((R + 1 - L) >> 1);//向上取整
if (arr[m] < target)
L = m;
else
R = m - 1;
}
if (arr[L] < target)
return L;
return -1;
}
int main() {
//freopen("in.txt", "r", stdin);
int n, a[maxn], v;
scanf("%d", &n);
for (int i = 0; i < n; i++)scanf("%d", &a[i]); //1 3 2 9 4 1 3 7 2 2
scanf("%d", &v); //input the number you need find
qsort(a, n, sizeof(a[0]), cmp); // 1 1 2 2 2 3 3 4 7 9
printf("after sorted : \n");
for (int i = 0; i < n; i++)printf("%d ", a[i]);
printf("\n-------------test----------------");
printf("\n%d\n", firstEqual(a, 0, n, v)); //output 2 第一个
printf("%d\n", lastEqualNext(a, 0, n, v)); //output 4 + 1,最后一个的下一个
printf("%d\n", lastEqual(a, 0, n, v)); //output 4 最后一个
printf("%d\n", firstLarge(a, 0, n, v)); //output 5(第一个3大于2)
printf("%d\n", lastSmall(a, 0, n, v)); //output 1(不是0)
return 0;
}
/*
测试数据:
10
1 3 2 9 4 1 3 7 2 2
2
*/
输出
枚举排列
枚举排列的算法也有几个,包括刘汝佳书上的和经典的,还有做过的几个题LeetCode47。LeetCode46。
#include
using namespace std;
const int maxn = 100 + 10;
void permutation(int *arr, int n, int cur){
if(cur == n){ // 边界
for(int i = 0; i < n; i++)
printf("%d ",arr[i]);
printf("\n");
}
else for(int i = 1; i <= n; i++){ //尝试在arr[cur]中填充各种整数
bool flag = true;
for(int j = 0; j < cur; j++)if(i == arr[j]){ // 如果i已经在arr[0]~arr[cur-1]中出现过,则不能选
flag = false;
break;
}
if(flag){
arr[cur] = i; //把i填充到当前位置
permutation(arr, n, cur+1);
}
}
}
// 求 1 ~ n 的全排列,arr数组作为中间打印数组
int main(int argc, char const **argv)
{
int a[maxn], n;
scanf("%d", &n);
permutation(a, n, 0);
return 0;
}
//可重集的全排列
#include
const int maxn = 100 + 10;
void permutation(int *arr,int *p,int n,int cur){
if(cur == n){
for(int i = 0; i < n; i++)
printf("%d ",arr[i]);
printf("\n");
}else for(int i = 0; i < n; i++)if(!i || p[i] != p[i-1]){
int c1 = 0, c2 = 0;
for(int j = 0; j < n; j++)
if(p[j] == p[i]) // 重复元素的个数
c1++;
for(int j = 0; j < cur; j++)
if(arr[j] == p[i]) // 前面已经排列的重复元素的个数
c2++;
if(c2 < c1){
arr[cur] = p[i];
permutation(arr, p, n, cur+1);
}
}
}
int main(){
int a[maxn], p[maxn] = {5, 6, 7, 5}; //可以有重复元素的全排列
std::sort(p, p+4);
permutation(a, p, 4, 0);
return 0;
}
//全排列的非去重递归算法
#include
using namespace std;
const int maxn = 100 + 10;
void permutation(int arr[], int cur, int n){
if( cur == n){
for(int i = 0; i < n; i++)
printf("%d ", arr[i]);
printf("\n");
}
else for(int i = cur; i < n; i++){
swap(arr[i], arr[cur]);
permutation(arr, cur+1, n);
swap(arr[i], arr[cur]);
}
}
int main(){
int n, a[maxn];
scanf("%d", &n);
for(int i = 0; i < n; i++)
scanf("%d", &a[i]);
permutation(a, 0, n);
return 0;
}
子集生成
增量构造法,位向量法,二进制法(常用)
#include
using namespace std;
const int maxn = 100 + 10;
//打印0~n-1的所有子集
//按照递增顺序就行构造子集 防止子集的重复
void print_subset(int *arr, int n, int cur){
for(int i = 0; i < cur; i++)
printf("%d ", arr[i]);
printf("\n");
int s = cur ? arr[cur-1] + 1 : 0; //确定当前元素的最小可能值
for(int i = s; i < n; i++){
arr[cur] = i;
print_subset(arr, n, cur+1);
}
}
int main(){
int n, arr[maxn];
scanf("%d", &n);
print_subset(arr, n, 0);
return 0;
}
这个其实很简单,就是枚举每个位置0和1两种情况即可。
// 1~n 的所有子集:位向量法
#include
const int maxn = 100 + 10;
using namespace std;
int bits[maxn];//位向量bits[i] = 1,当且仅当i在子集 A 中
void print_subset(int n,int cur){
if(cur == n){
for(int i = 0; i < cur; i++)
if(bits[i])
printf("%d ",i);
printf("\n");
return;
}
bits[cur] = 1;
print_subset(n,cur + 1);
bits[cur] = 0;
print_subset(n,cur + 1);
}
int main() {
int n;
scanf("%d", &n);
print_subset(n,0);
return 0;
}
二进制枚举子集用的多,这里举个例子 n = 3;则要枚举0 - 7 对应的是有7个子集,每个子集去找有哪些元素print_subset中的 1<< i,也就是对应的那个位置是有元素的,例如1的二进制是0001也就是代表0位置有元素,0010是2,代表第一个位置是1,0100代表第2个位置上有元素,相应的1000 = 8对应第3个位置上有元素。
总结来说也就是对应1<< i对应i上是1(从0开始),其余位置是0。看图容易理解:
// 0 ~ n-1的所有子集:二进制法枚举0 ~ n-1的所有子集
#include
const int maxn = 100 + 10;
using namespace std;
void print_subset(int n,int cur){
//这一步其实就是判断 cur 的二进制的各个位上是不是1,如果是1,就输出对应的那个位置(位置从0开始)
for(int i = 0; i < n; i++)
if(1 & (cur >> i))
printf("%d ",i);
printf("\n");
}
int main(int argc, char const** argv)
{
int n;
scanf("%d",&n);
for(int i = 0; i < (1 << n); i++)
print_subset(n,i);//枚举各子集对应的编码 0,1,2...pow(2,n) - 1
return 0;
}
n皇后回溯
可以看这篇博客。
//n皇后问题:普通回溯法
#include
const int maxn = 100 + 10;
using namespace std;
int sum,n,cnt; //解的个数,n皇后,递归次数
int C[maxn];
void Search(int cur){ //逐行放置皇后
cnt++;
if(cur == n)sum++;
else for(int i = 0; i < n; i++){ //尝试在各列放置皇后
bool flag = true;
C[cur] = i; //尝试把第cur行的皇后放在第i列//如果 等下不行的话 就下一个 i++
for(int j = 0; j < cur; j++){ //检查是否和已经放置的冲突
if(C[cur] == C[j] || C[cur] + cur == C[j] + j || cur - C[cur] == j - C[j]){//检查列,"副对角线","主对角线"
flag = false;break;
}
}
if(flag)Search(cur+1);
}
}
int main(){
scanf("%d",&n); //输入n皇后
sum = cnt = 0;//解的个数 和 递归的次数
Search(0);
printf("%d %d\n",sum,cnt);
return 0;
}
优化过的
// n皇后问题:优化的回溯法
#include
const int maxn = 100 + 10;
using namespace std;
int sum,n,cnt;
int C[maxn];
bool vis[3][maxn];
int Map[maxn][maxn];//打印解的数组
//一般在回溯法中修改了辅助的全局变量,一定要及时把他们恢复原状
void Search(int cur){ //逐行放置皇后
cnt++;
if(cur == n){
sum++;
for(int i = 0; i < cur; i++)Map[i][C[i]] = 1;//打印解
for(int i = 0; i < n; i++){
for(int j = 0; j < n; j++)printf("%d ",Map[i][j]);
printf("\n");
}
printf("\n");
memset(Map,0,sizeof(Map)); //还原
}
else for(int i = 0; i < n; i++){ //尝试在 cur行的 各 列 放置皇后
if(!vis[0][i] && !vis[1][cur+i] && !vis[2][cur-i+n]){//判断当前尝试的皇后的列、主对角线
vis[0][i] = vis[1][cur+i] = vis[2][cur-i+n] = true;
C[cur] = i;//cur 行的列是 i
Search(cur+1);
vis[0][i] = vis[1][cur+i] = vis[2][cur-i+n] = false;//切记!一定要改回来
}
}
}
int main(){
scanf("%d",&n);
memset(vis,false,sizeof(vis));
memset(Map,0,sizeof(Map));
sum = cnt = 0;
Search(0);
printf("%d %d\n",sum,cnt);//输出 解决方案 和 递归次数
return 0;
}
并查集
并查集详细讲解以及每一步的优化可以看这篇博客。
//题目连接 : http://poj.org/problem?id=1611
//题目大意 : 病毒传染,可以通过一些社团接触给出一些社团(0号人物是被感染的)问有多少人(0~n-1个人)被感染
#include
const int maxn = 100000 + 10;
int parent[maxn], rank[maxn]; //parent[]保存祖先,rank记录每个'树的高度'
void init(){
for(int i = 0; i < maxn; i++)parent[i] = i; //注意这里
for(int i = 0; i < maxn; i++)rank[i] = 1;
}
//int findRoot(int v){
// return parent[v] == v ? v : parent[v] = findRoot(parent[v]);
//}
// 非递归
int findRoot(int v){
while(parent[v] != v){
parent[v] = parent[parent[v]]; // 路径压缩
v = parent[v];
}
return v;
}
void unions(int a, int b){
int aRoot = findRoot(a);
int bRoot = findRoot(b);
if (aRoot == bRoot)
return;
if (rank[aRoot] < rank[bRoot])
parent[aRoot] = bRoot;
else if(rank[aRoot] > rank[bRoot]){
parent[bRoot] = aRoot;
}else{
parent[aRoot] = bRoot;
rank[bRoot]++;
}
}
int is_same(int x,int y){ //检查是不是在同一个集合中
return findRoot(x) == findRoot(y);
}
int main(){
int n,m,k,x,root;
while(~scanf("%d%d",&n,&m) && (n||m)){
init();
for(int i = 0; i < m; i++){
scanf("%d%d",&k,&root);
for(int j = 1; j < k; j++){
scanf("%d",&x);
unions(root,x);
}
}
int sum = 1;
for(int i = 1; i < n; i++)
if(findRoot(i) == findRoot(0))
sum++; //找和0是一个集合的
printf("%d\n",sum);
}
return 0;
}
树状数组
树状数组的主要用于需要频繁的修改数组元素,同时又要频繁的查询数组内任意区间元素之和的时候。具体一些解释看图。
所以计算2^k次方我们可以用如下代码
int lowbit(int x){
return x&(-x); //或者 return x&(x^(x-1));
}
这里给出一个例题POJ2352Stars
题目意思就是给你一些星星的坐标,每个星星的级别是他左下方的星星的数量,要你求出各个级别的星星有多少个,看样例就懂了
题目中一个重要的信息就是输入是按照y递增,如果y相同则x递增的顺序给出的,所以,对于第i颗星星,它的level就是之前出现过的星星中,横坐标小于等于i的星星的数量。这里用树状数组来记录所有星星的x值。
代码中有个小细节就是x++这是因为lowbit不能传值为0,否则会陷入死循环。
#include
#include
const int maxn = 32000 + 10;
int n,c[maxn],level[15000+10];
//计算2^k
int lowbit(int x){
return x&(-x);
}
//更新数组的值
void update(int i,int val){
while(i < maxn){ //注意这里是最大的x,没有记录所以用maxn,不能用n
c[i] += val;
i += lowbit(i); //不断的往上面更新
}
}
//查询
int sum(int i){
int s = 0;
while(i > 0){
s += c[i];
i -= lowbit(i); //不断的往下面加
}
return s;
}
int main(){
int x,y;
scanf("%d",&n);
memset(c,0,sizeof(c));
memset(level,0,sizeof(level));
for(int i = 0; i < n; i++){
scanf("%d%d",&x,&y);
x++; //加入x+1,是为了避免0,X是可能为0的,LOWBIT无法处理0的情况,因为它的结果也是0,那么最终就是一个死循环
level[sum(x)]++;
update(x,1); //上面的层都要+1个
}
for(int i = 0; i < n; i++)
printf("%d\n",level[i]);
return 0;
}
KMP,Sunday,BM
这三个算法解决的问题都是 : 有一个文本串 S,和一个模式串 P,现在要查找 P 在 S 中的位置,三种算法的思路这里限于篇幅不多说,这篇博客对着三种算法讲解的比较详细。
KMP的较直观的分析也可以看看我的另一篇博客
```cpp
//题目连接 : http://acm.hdu.edu.cn/showproblem.php?pid=1711
//题目大意 : 找第二个数组在第一个数组中出现的位置,如果不存在,输出-1
#include
const int maxn = 1000000 + 10;
int n,m,a[maxn], b[10000 + 10],nexts[10000 + 10];
void getNext(int *p,int next[]) { //优化后的求next数组的方法
int len = m;
next[0] = -1; //next 数组中的 最大长度值(前后缀的公共最大长度) 的第一个 赋值为 -1
int k = -1,j = 0;
while (j < len - 1) {
if (k == -1 || p[j] == p[k]) { //p[k]表示前缀 p[j] 表示后缀
k++; j++;
if(p[j] != p[k])next[j] = k;
else next[j] = next[k]; //因为不能出现p[j] = p[ next[j ]],所以当出现时需要继续递归,k = next[k] = next[next[k]]
}
else k = next[k];
}
}
int KMPSerach(int *s, int *p) {
int sLen = n,pLen = m;
int i = 0, j = 0;
while (i < sLen && j < pLen) {
if (j == -1 || s[i] == p[j])i++, j++;
else j = nexts[j];
}
if (j == pLen)return i - j;
else return -1;
}
int main() {
int T;
scanf("%d", &T);
while (T--) {
scanf("%d%d", &n, &m);
for(int i = 0; i < n; i++)scanf("%d", &a[i]);
for(int i = 0; i < m; i++)scanf("%d", &b[i]);
getNext(b,nexts); //获取next数组
int ans = KMPSerach(a, b);
if (ans != -1)printf("%d\n", ans + 1);
else printf("-1\n");
}
return 0;
}
**`BM`算法,主要是根据两个规则去执行**
![这里写图片描述](https://img-blog.csdn.net/20180407140141237?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3p4enh6eDAxMTk=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70)
```cpp
//题目链接:http://acm.hrbust.edu.cn/index.php?m=ProblemSet&a=showProblem&problem_id=1551
//题目大意:找一串字符中是否出现"bkpstor"这段字符
#include
#include
#include
using namespace std;
const int maxn = 1000;
int last(char *p, char c) { //找到文本串的 "坏字符" 在模式串中的位置
for (int i = strlen(p) - 1; i >= 0; i--)if (p[i] == c)return i;
return -1;
}
int BM_index(char *T, char *p) {
int n = strlen(T);
int m = strlen(p);
int i = m - 1, j = m - 1;
while (i <= n - 1) {
if (T[i] == p[j])
if (j == 0)return i;
else i--, j--;
else {
i = i + m - min(j, 1 + last(p, T[i]));//文本的不符合的那个 位置 串移动的步数
j = m - 1;//模式串的新位置
}
}
return -1;
}
int Sum(char *T, char *P, int s) {//输出文本串中包含模式串的数量
int e = BM_index(T + s, P);
return e == -1 ? 0 : 1 + Sum(T, P, s + e + 1);
}
//测试数据 : 123bkpstor456bkpstor67
int main() {
char s[maxn];
while (~scanf("%s",s)) {
int cnt = BM_index(s, "bkpstor");
//printf("%d\n",Sum(s,"bkpstor",0));出现次数输出2
if (cnt == -1)printf("Safe\n");
else printf("Warning\n");
}
return 0;
}
Sunday算法也是两个规则
//题目链接:http://acm.hrbust.edu.cn/index.php?m=ProblemSet&a=showProblem&problem_id=1551
//题目大意: 找一串字符中是否出现"bkpstor"这段字符
#include
#include
#include
const int maxn = 1000;
using namespace std;
int last(char *p, char c) {
for (int i = strlen(p) - 1; i >= 0; i--)if (p[i] == c)return i;
return -1;
}
int Sunday(char *s, char *p) {
int sLen = strlen(s);
int pLen = strlen(p);
int i = 0, j = 0;
while (i < sLen && j < pLen) {
if (s[i] == p[j])i++, j++;
else {
int index = i + pLen - j; // 字符串中右端对齐的字符
if (last(p, s[index]) == -1) { i = index + 1; j = 0; } // 没有在匹配串中出现则直接跳过
else {
i = index - last(p, s[index]); j = 0; //否则 其移动步长 = 匹配串中最 右端 的该字符到末尾的距离 + 1。
}
}
}
if (j == pLen)return i - j;
return -1;
}
int main() {
char s[maxn];
while (~scanf("%s",s)) {
int cnt = Sunday(s,"bkpstor");
if (cnt == -1)printf("Safe\n");
else printf("Warning\n");
}
return 0;
}
01背包,完全背包
背包问题可以分为很多种,这里只简单讨论01背包和完全背包,
后来改写的01背包: 博客
参考博客:
//题目连接 : http://acm.hdu.edu.cn/showproblem.php?pid=2602
#include
#include
#include
using namespace std;
const int maxn = 1000+5;
int w[maxn],v[maxn],dp[maxn][maxn],vis[maxn];
//打印解
void print(int n,int C){
for(int i = n; i > 1; i--){
if(dp[i][C] == dp[i-1][C-w[i]] + v[i]){
vis[i] = 1;
C -= w[i];
}
}
vis[1] = (dp[1][C] > 0) ? 1: 0;
}
int main(){
freopen("in.txt","r",stdin);
int n,C,T;
scanf("%d",&T);
while(T--){
scanf("%d%d",&n,&C);
for(int i = 1;i <= n; i++) scanf("%d",&v[i]);
for(int i = 1;i <= n; i++) scanf("%d",&w[i]);
memset(dp,0,sizeof(dp)); //dp[0][0~C]和dp[0~N][0]都为0,前者表示前0个物品无论装入多大的包中总价值都为0,后者表示体积为0的背包都装不进去。
memset(vis,0,sizeof(vis));
for(int i = 1; i <= n; i++){
for(int j = 0;j <= C; j++){
dp[i][j] = dp[i-1][j]; //如果j不大于v[i]的话就dp[i][j] = dp[i-1][j];
if(j >= w[i]) dp[i][j] = max(dp[i][j],dp[i-1][j-w[i]]+v[i]);
}
}
printf("%d\n",dp[n][C]); //n个物品装入C容量的包能获得的最大价值
//print(n,C);
//for(int i = 1; i <= n; i++)if(vis[i])printf("%d ",i); //输出选中的物品
}
return 0;
}
#include
#include
#include
const int maxn = 1000+5;
using namespace std;
int w[maxn],v[maxn],dp[maxn][maxn];
int main(){
int T,n,C;
scanf("%d",&T);
while(T--){
scanf("%d%d",&n,&C);
for(int i = 1; i <= n; i++)scanf("%d",&v[i]);
for(int i = 1; i <= n; i++)scanf("%d",&w[i]);
memset(dp,0,sizeof(dp)); //dp全部初始化为0
for(int i = n;i >= 1; i--){
for(int j = 0; j <= C; j++){
dp[i][j] = (i == n ? 0 : dp[i+1][j]);
if(j >= w[i])dp[i][j] = max(dp[i][j],dp[i+1][j-w[i]]+v[i]);
}
}
printf("%d\n",dp[1][C]);
}
return 0;
}
#include
#include
#include
const int maxn = 1000+5;
using namespace std;
int w[maxn],v[maxn],dp[maxn];
int main(){
int T,n,C;
scanf("%d",&T);
while(T--){
scanf("%d%d",&n,&C);
for(int i = 1; i <= n; i++)scanf("%d",&v[i]);
for(int i = 1; i <= n; i++)scanf("%d",&w[i]);
memset(dp,0,sizeof(dp));
for(int i = 1; i <= n; i++){
for(int j = C;j >= 0; j--){
if(j >= w[i])dp[j] = max(dp[j],dp[j-w[i]]+v[i]);
}
}
printf("%d\n",dp[C]);
}
return 0;
}
完全背包和01背包的差别在于每个物品的数量有无数个,在考虑第i件物品的时候,不是考虑选不选这件,而是选多少件可以达到最大价值
for (int i = 1; i < n; i++){
for (int j = 1; j <= v; j++){
for (int k = 0; k*c[i] <= j; k++){
if(c[i] <= j) dp[i][j] = max{dp[i-1][j],dp[i-1][j - k * c[i]] + k * w[i]};/*要么不取,要么取0件、取1件、取2件……取k件*/
else dp[i][j] = dp[i-1][j]/*继承前i个物品在当前空间大小时的价值*/
}
}
}
优化:
注意完全背包的顺序问题 :因为每种背包都是无限的。当我们把i从1到N循环时,dp[v]表示容量为v在前i种背包时所得的价值,这里我们要添加的不是前一个背包,而是当前背包。所以我们要考虑的当然是当前状态。为01背包中要按照v=V..0的逆序来循环。这是因为要保证第i次循环中的状态dp[i][v]是由状态dp[i-1] [v-c[i]]递推而来。这正是为了保证每件物品只选一次,保证在考虑“选入第i件物品”这件策略时,依据的是一个绝无已经选入第i件物品的 子结果dp[i-1][v-c[i]]。而现在完全背包的特点恰是每种物品可选无限件,所以在考虑“加选一件第i种物品”这种策略时,却正需要一个可能已选入第i种物品的子结果dp[i][v-c[i]],所以就可以并且必须采用v=0..V的顺序循环。
完全背包还有考虑是否要求恰好放满背包等问题,可以好好研究:
参考博客:
https://blog.csdn.net/Thousa_Ho/article/details/78156678
https://wenku.baidu.com/view/eea4a76b0b1c59eef8c7b497.html
https://www.cnblogs.com/shihuajie/archive/2013/04/27/3046458.html
#include
#include
#include
using namespace std;
const int maxn = 50000 + 10;
const int INF = -0X7ffff;
int w[maxn],v[maxn],dp[maxn];
int main(){
int T,n,C;
scanf("%d",&T);
while(T--){
scanf("%d%d",&n,&C);
for(int i = 1; i <= C; i++)dp[i] = INF;
dp[0] = 0;
for(int i = 0; i < n; i++)scanf("%d%d",&w[i],&v[i]);
for(int i = 0; i < n; i++){
for(int j = w[i]; j <= C; j++){ //从前往后递推,这样才能保证一种物品可以被使用多次
dp[j] = max(dp[j],dp[j-w[i]] + v[i]);
}
}
if(dp[C] > 0)printf("%d\n",dp[C]);
else printf("NO\n");
}
return 0;
}
最长(不)上升或下降子序列
Java编写与解释的博客。
先说O(N*N)的解法,第i个元素之前的最长递增子序列的长度要么是1(单独成一个序列),要么就是第i-1个元素之前的最长递增子序列加1,这样得到状态方程:
LIS[i] = max{1,LIS[k]+1} (∀k arr[k])
这样arr[i]才能在arr[k]的基础上构成一个新的递增子序列。
//题目连接 : http://acm.nyist.edu.cn/JudgeOnline/problem.php?pid=17
#include
#include
#include
using namespace std;
const int maxn = 10000 + 10;
int dp[maxn]; /* dp[i]记录到[0,i]数组的LIS */
int maxx;/* LIS长度,初始化为1 */
int LIS(char *arr, int n) {
for (int i = 0; i < n; i++) {
dp[i] = 1;
for (int j = 0; j < i; j++) // 注意i只遍历比它小的元素
if (arr[j] < arr[i]) dp[i] = max(dp[i], dp[j] + 1); //改成arr[j] <= arr[i]就可以求非减的
maxx = max(maxx, dp[i]);
}
return maxx;
}
/* 递归输出LIS,因为数组dp还充当了“标记”作用 */
void printLis(char *arr, int index) {
bool isLIS = false;
if (index < 0 || maxx == 0)return;
if (dp[index] == maxx) {
--maxx;
isLIS = true;
}
printLis(arr, --index);
if (isLIS) printf("%c ", arr[index + 1]);
}
int main() {
char s[maxn];
int n;
scanf("%d\n",&n);
while(n--){
maxx = 1;
scanf("%s",s);
printf("%d\n",LIS(s,strlen(s)));
//printLis(s,strlen(s)-1);printf("\n");
}
return 0;
}
//题目连接 : http://poj.org/problem?id=3903
#include
#include
#include
#include
using namespace std;
const int INF = 0x3f3f3f3f;
const int maxn = 100000 + 5;
int a[maxn], dp[maxn], pos[maxn], fa[maxn];
vector ans;
//用于最长非递减子序列种的lower_bound函数
int cmp(int a,int b){
return a <= b;
}
//最长上升子序列
//dp[i]表示长度为i+1的上升子序列的最末尾元素 找到第一个比dp末尾大的来代替
int LIS(int n){
memset(dp, 0x3f, sizeof(dp));
pos[0] = -1;
int i,lpos;
for (i = 0; i < n; ++i){
dp[lpos = (lower_bound(dp, dp + n, a[i]) - dp)] = a[i];
pos[lpos] = i; // *靠后打印
fa[i] = (lpos ? pos[lpos - 1] : -1);
}
n = lower_bound(dp, dp + n, INF) - dp;
for (i = pos[n - 1]; ~fa[i]; i = fa[i]) ans.push_back(a[i]);
ans.push_back(a[i]);
return n;
}
//非递减的LIS
int LISS(int n){
memset(dp, 0x3f, sizeof(dp));
pos[0] = -1;
int i,lpos;
for (i = 0; i < n; i++){
dp[lpos = (lower_bound(dp, dp + n, a[i],cmp) - dp)] = a[i]; //注意这里cmp
pos[lpos] = i; // *靠后打印
fa[i] = (lpos ? pos[lpos - 1] : -1);
}
n = lower_bound(dp, dp + n, INF) - dp;
for (i = pos[n - 1]; ~fa[i]; i = fa[i]) ans.push_back(a[i]);
ans.push_back(a[i]);
return n;
}
int main(){
// freopen("in.txt","r",stdin);
int n;
while(~scanf("%d",&n)){
ans.clear();
for(int i = 0;i < n; i++)scanf("%d",&a[i]);
printf("%d\n",LIS(n));
// for(int i = ans.size()-1; i >= 0; i--) printf("%d ",ans[i]);printf("\n"); //路径打印
}
return 0;
}
最长公共子序列
最长公共子序列利用动态规划的方式解决,具有最优子结构性质,和重叠子问题性质,dp[i][j]记录序列Xi和Yi的最长公共子序列的长度,当i = 0或j =0 时,空序列时Xi和Yi的最长公共子序列,其余情况如下
这里也给出一篇讲的好的博客
//题目连接 : http://poj.org/problem?id=1458
#include
#include
#include
using namespace std;
const int maxn = 1000 + 10;
int dp[maxn][maxn];
int path[maxn][maxn]; //记录路径
int LCS(char *s1,char *s2){
int len1 = strlen(s1)-1,len2 = strlen(s2)-1;//注意例如 abcfbc的strlen(s1)为7,所以是strlen(s1) - 1
for(int i = 0; i <= len1; i++) dp[i][0] = 0;
for(int i = 0; i <= len2; i++) dp[0][i] = 0;
for(int i = 1; i <= len1; i++){
for(int j = 1; j <= len2; j++)
if(s1[i] == s2[j]){
dp[i][j] = dp[i-1][j-1] + 1;
path[i][j] = 1;
}
else if(dp[i-1][j] >= dp[i][j-1]) {
dp[i][j] = dp[i-1][j];
path[i][j] = 2;
}
else {
dp[i][j] = dp[i][j-1];
path[i][j] = 3;
}
}
return dp[len1][len2];
}
//打印解
void print(int i,int j,char *s){
if(i == 0 || j == 0)return ;
if(path[i][j] == 1){
print(i-1,j-1,s);
printf("%c ",s[i]);
}else if(path[i][j] == 2){
print(i-1,j,s);
}else print(i,j-1,s);
}
int main(){
char s1[maxn],s2[maxn];
while(~scanf("%s%s",s1+1,s2+1)){ //注意s1[0]不取-注意例如 abcfbc的strlen(s1)为7
memset(path,0,sizeof(path));
printf("%d\n",LCS(s1,s2));
//print(strlen(s1)-1,strlen(s2)-1,s1);
}
return 0;
}
拓扑排序
有向无环图上的一种排序方式,我的这篇博客也讲解了一下。可以两种写法:
//题目链接 : https://vjudge.net/contest/169966#problem/O
#include
using namespace std;
const int maxn = 100 + 10;
int n, m;
int in[maxn];
vectorG[maxn];
void topo(){
queueq;
for(int i = 1; i <= n ; i++)
if(in[i] == 0)
q.push(i);
bool flag = true;
while(!q.empty()){
int cur = q.front();
q.pop();
if(flag){
printf("%d",cur);
flag = false;
}
else
printf(" %d",cur);
for(int i = 0; i < G[cur].size(); i++){
if(--in[G[cur][i]] == 0)
q.push(G[cur][i]);
}
}
}
int main(int argc, char const **argv)
{
int from, to;
while(~scanf("%d%d", &n, &m) && (n || m)){
memset(in, 0, sizeof(in));
for(int i = 1; i <= n; i++)
G[i].clear();
for(int i = 0; i < m; i++){
scanf("%d%d", &from, &to);
in[to]++; //度
G[from].push_back(to);
}
topo();
printf("\n");
}
return 0;
}
#include
#include
#include
using namespace std;
#define maxn 5005
typedef struct { ///邻接表实现
int next_arc;///下一条边
int point;
} Arc; ///边的结构体,
Arc arc[maxn];///储存每条边
int node[maxn],vis[maxn],top[maxn];///储存每个顶点,node[i]表示第i个顶点指向的第一条边在arc中的位置
int n, m, t;
void dfs(int v) {
for (int i = node[v]; i != -1; i = arc[i].next_arc) {
if (!vis[arc[i].point]) {
dfs(arc[i].point);
}
}
vis[v] = 1;
top[--t] = v;
}
int main() {
int a, b;
while (cin >> n >> m && (m || n)) {
memset(node, -1, sizeof(node));
memset(vis, 0, sizeof(vis));
for (int i = 1; i <= m; i++) {
cin >> a >> b;
arc[i].next_arc = node[a];///第一次是出发点,以后是下一个
arc[i].point = b;
node[a] = i;
vis[arc[i].point] = 0;
}
t = n;
for (int j = 1; j <= n; j++) if (!vis[j]) dfs(j);
for (int i = 0; i < n - 1; i++)
cout << top[i] << " ";
cout << top[n - 1] << endl;
}
return 0;
}
欧拉路径和回路
首先看定义 :
欧拉回路:
(1) 图G是连通的,不能有孤立点存在。
(2) 对于无向图来说度数为奇数的点个数为0,对于有向图来说每个点的入度必须等于出度。
欧拉路径:
(1) 图G是连通的,无孤立点存在。
(2) 对于无向图来说,度数为奇数的的点可以有2个或者0个,并且这两个奇点其中一个为起点另外一个为终点。对于有向图来说,可以存在两个点,其入度不等于出度,其中一个入度比出度大1,为路径的起点;另外一个出度比入度大1,为路径的终点。
判断连通的方式有DFS或者并查集,然后再判断一下是否满足条件即可,之前做的欧拉回路的几个好题在我的github仓库
#include
const int maxn = 1000 + 5;
using namespace std;
bool vis[maxn];
int in[maxn];
int n, m;
vectorG[maxn];
void dfs(int u){
vis[u] = 1;
for(int i = 0; i < G[u].size(); i++){
if(!vis[G[u][i]])
dfs(G[u][i]);
}
}
int main(){
//freopen("in.txt","r",stdin);
int from, to;
while(scanf("%d", &n) != EOF && n){
scanf("%d", &m);
memset(vis, 0, sizeof(vis));
memset(in, false, sizeof(in));
for(int i = 1; i <= n; i++)
G[i].clear();
bool flag = true;
for(int i = 0; i < m; i++){
scanf("%d%d",&from, &to);
G[from].push_back(to);
G[to].push_back(from);
in[from]++;
in[to]++;
}
dfs(1);
for(int i = 1; i <= n; i++)
if(in[i] % 2 != 0)
flag = false;
for(int i = 1; i <= n; i++)
if(!vis[i])
flag = false;
cout << (flag ? 1 : 0) << endl;
}
return 0;
}
#include
const int maxn = 1000 + 5;
using namespace std;
int n, m, parent[maxn],ranks[maxn],in[maxn];
void init(){
for(int i = 0; i < maxn; i++)
parent[i] = i;
for(int i = 0; i < maxn; i++)
ranks[i] = 1;
}
//int findRoot(int v){
// return parent[v] == v ? v : parent[v] = findRoot(parent[v]);
//}
int findRoot(int v){
while(parent[v] != v){
parent[v] = parent[parent[v]];
v = parent[v];
}
return v;
}
void unions(int a, int b){
int aRoot = findRoot(a);
int bRoot = findRoot(b);
if (aRoot == bRoot)
return;
if (ranks[aRoot] < ranks[bRoot])
parent[aRoot] = bRoot;
else if(ranks[aRoot] > ranks[bRoot]){
parent[bRoot] = aRoot;
}else {
parent[aRoot] = bRoot;
ranks[bRoot]++;
}
}
int main(){
//freopen("in.tat","r",stdin);
int u, v;
while(scanf("%d", &n) != EOF && n){
init();
scanf("%d", &m);
memset(in,0,sizeof(in));
for(int i = 0; i < m; i++){
scanf("%d%d",&u, &v);
unions(u, v);
in[u]++;
in[v]++;
}
bool flag = true;
int temp = findRoot(1);
for(int i = 1; i <= n; i++)
if(findRoot(i) != temp)
flag = false;
for(int i = 1; i <= n; i++)
if(in[i] % 2 != 0)
flag = false; //判断度
cout << (flag ? 1 : 0) << endl;
}
return 0;
}
搜索
这里举几个比较好的题目: POJ3984,这个题目要求记录BFS最短路的路径,我这里使用三种方法:
BFS+在结构体中记录路径
BFS+记录路径
DFS加回溯法
//题目链接;http://poj.org/problem?id=3984
//题目大意:给你一个5*5的矩阵,让你找一条路,从左上点,到右下点
//解题思路:利用BFS求解最短路,利用vector记录路径
//BFS+在结构体中记录路径
#include
#include
#include
#include
#include
#include
#pragma warning(disable : 4996)
using namespace std;
const int maxn = 100 + 10;
int n;
int map[maxn][maxn];
bool vis[maxn][maxn];
int dir[4][2] = { {-1,0},{0,1},{1,0},{0,-1}};//对应上,右,下,左
struct Road {//路径记录
int x,y,d;//d表示方向
Road() {}//默认的构造函数
Road(int a,int b,int c):x(a),y(b),d(c) {}//带参数的构造函数
};
struct node {//节点类型
int x,y;
vector v;//记录路径的结构体
};
bool check(node u) {
if (!vis[u.x][u.y] && u.x >= 0 && u.x < n && u.y >= 0 && u.y < n && map[u.x][u.y] != 1)
return true;
else
return false;
}
void BFS(int x, int y) {
queueq;
node now,next;
now.x = x;
now.y = y;
now.v.push_back(Road(x,y,-1));
q.push(now);
while (!q.empty()) {
now = q.front();
q.pop();
if (now.x == n - 1 && now.y == n-1) {
for(int i = 0; i < now.v.size(); i++){
printf("(%d, %d)\n",now.v[i].x,now.v[i].y);
}
break;
}
for (int i = 0; i < 4; i++) {
next.x = now.x + dir[i][0];
next.y = now.y + dir[i][1];
if (check(next)) {
vis[next.x][next.y] = true;
next.v = now.v;
next.v[now.v.size()-1].d = i;
next.v.push_back(Road(next.x,next.y,-1));
q.push(next);
}
}
}
}
int main() {
//freopen("in.txt", "r", stdin);
flag = false;
n = 5;
memset(vis, 0, sizeof(vis));
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
scanf("%d", &map[i][j]);
}
}
BFS(0, 0);
return 0;
}
//BFS+记录路径
#include
#include
#include
using namespace std;
const int maxn = 100 + 10;
int dir[4][2] = { {-1,0},{0,1},{1,0},{0,-1}};
bool vis[maxn][maxn];
int map[maxn][maxn];
struct Node {
int x,y,pre;
};
Node m_queue[maxn];
bool Check(int x,int y){
if(!map[x][y] && !vis[x][y] && x >= 0 && x < 5&&y >= 0 && y < 5)return true;
else return false;
}
void print(int u){
if(m_queue[u].pre != -1){
print(m_queue[u].pre);
printf("(%d, %d)\n",m_queue[u].x,m_queue[u].y);
}
}
void BFS(int x,int y){
int head = 0,rear = 1;
m_queue[head].x = x;
m_queue[head].y = y;
m_queue[head].pre = -1;
vis[x][y] = true;
while(head < rear){
for(int i = 0; i < 4; i++){
int xx = m_queue[head].x + dir[i][0];
int yy = m_queue[head].y + dir[i][1];
if(Check(xx,yy)){
vis[xx][yy] = 1;
m_queue[rear].x = xx;
m_queue[rear].y = yy;
m_queue[rear].pre = head;
rear++;
}
if(xx == 4&&yy == 4)print(rear - 1);
}
head++;//出队
}
}
int main(){
//freopen("in.txt","r",stdin);
int n = 5;
memset(vis,0,sizeof(vis));
for(int i = 0; i < n; i++){
for(int j = 0; j < n; j++){
scanf("%d",&map[i][j]);
}
}
printf("(0, 0)\n");
BFS(0,0);
return 0;
}
//DFS加回溯法
#include
#include
#include
#include
#include
#include
using namespace std;
const int maxn = 100+10;
const int INF = 0x3f3f3f3f;
int n,k,ans = INF;
int map[maxn][maxn];
bool vis[maxn][maxn];
int dir[4][2] = { {-1,0},{0,1},{1,0},{0,-1}};
struct node {
int x,y;
};
vectordict;
stacks;
void memcpy(stacks){
dict.clear();
while(!s.empty()){
dict.push_back(s.top());
s.pop();
}
}
bool Check(int x,int y){
if(!vis[x][y] && map[x][y] != 1 && x >= 0 && x < n && y >= 0 && y < n)return true;
else return false;
}
void DFS(int x,int y,int step){//深刻理解递归的含义
node now;
now.x = x;
now.y = y;
s.push(now);
if(now.x == n - 1 &&now.y == n - 1){
if(step < ans){//记录最短的路径
ans = step;
memcpy(s);
}
}
for(int i = 0; i < 4; i++){
int xx = now.x + dir[i][0];
int yy = now.y + dir[i][1];
if(Check(xx,yy)){
vis[xx][yy] = true;
DFS(xx,yy,step + 1);
vis[xx][yy] = false;//回溯
}
}
s.pop();//反弹的时候重新还原栈
}
int main(){
//freopen("in.txt","r",stdin);
n = 5;
memset(vis,false,sizeof(vis));
for(int i = 0; i < n; i++){
for(int j = 0; j < n; j++){
scanf("%d",&map[i][j]);
}
}
DFS(0,0,0);
for(int i = dict.size() - 1; i >= 0; i--){
printf("(%d, %d)\n",dict[i].x,dict[i].y);
}
return 0;
}
这里再给出DFS的一个好题,印象比较深刻的
//题目链接:https://vjudge.net/contest/169966#problem/V
//题目大意: 有一个正方形的 战场看,边长为1000,西南方向在坐标原点,战场上有n 个敌人,坐标x,y,攻击范围y
//要你从西边界出发,东边界离开,求西边界和东边界上的坐标,坐标要尽量靠北,如果不能到达东边界输出IMPOSSIBLE
//解题思路: 按照 刘汝佳书上的思路,先判断有无解,从上边界开始DFS如果可以一直到达下边界,则相当于堵住了,无解
//否则,也是从上边界开始,dfs和东西边界 相连的圆(如果没有就是1000),找到最南边的圆的 与边界相交的那个点就 0k
//主要还是DFS的运用
#include
#include
#include
#include
#include
const int maxn = 1000 + 10;
const double W = 1000.0;
using namespace std;
double x[maxn], y[maxn], r[maxn],Left,Right;
bool vis[maxn];
int n;
bool isinterect(int u, int v) {
return sqrt((x[u] - x[v])*(x[u] - x[v]) + (y[u] - y[v])*(y[u] - y[v])) <= r[u] + r[v];
}
void check_circle(int u) {
if (x[u] <= r[u])Left = min(Left, y[u] - sqrt(r[u] * r[u] - x[u] * x[u]));
if (x[u] + r[u] >= W)Right = min(Right, y[u] - sqrt(r[u] * r[u] - (W-x[u]) * (W-x[u])));
}
bool dfs(int u) {// 检查是否有解
if (vis[u] == true)return false;
vis[u] = true;
if (y[u] <= r[u])return true; //刚好碰到边界 以及 相切都可以
for (int i = 0; i < n; i++)if (isinterect(u,i) && dfs(i))return true;// if (&& dfs(i) && isinterect(u,i) )是错的,要先判断是否相交然后再dfs
check_circle(u);
return false;
}
int main() {
//freopen("in.txt", "r", stdin);
while (scanf("%d", &n) != EOF) {
memset(vis, false, sizeof(vis));
Left = Right = W;
bool flag = true;
for (int i = 0; i < n; i++) {
scanf("%lf%lf%lf", &x[i], &y[i], &r[i]);
}
for (int i = 0; i < n; i++) {
if (y[i] + r[i] >= W) { //从上往下 DFS
if (dfs(i)) { flag = false; break; }//如果能够从最上面 DFS 到最下面 则无解
}
}
if (flag) printf("0.00 %.2lf 1000.00 %.2lf\n", Left,Right);
else printf("IMPOSSIBLE\n");
}
return 0;
}
最小生成树
直接给出模板prim和kruskal(prim是堆优化的)
另外,我的另外一篇博客也总结了这两个算法。
//Prim模板
//题目链接 : http://poj.org/problem?id=1287
#include
#include
#include
#include
using namespace std;
const int maxn = 100 + 10;
const int INF = 1<<30;
struct Node{
int v,w;
Node (){}
Node (int v,int w):v(v),w(w){}
bool operator < (const Node&rhs ) const {
return rhs.w < w;
}
};
int n,d[maxn];
bool vis[maxn];
int Map[maxn][maxn];
void init(){
for(int i = 0; i < maxn; i++)vis[i] = false;
for(int i = 0; i < maxn; i++)d[i] = INF;
}
int Prim(int s){
priority_queueq;
q.push(Node(s,0));
int ans = 0;
while(!q.empty()){
Node Now = q.top(); q.pop();
int v = Now.v;
if(vis[v]) continue;
ans += Now.w;
vis[v] = true;
for(int i = 1; i <= n; i++){
int w2 = Map[v][i];
if(!vis[i] && d[i] > w2){
d[i] = w2;
q.push(Node(i,d[i]));
}
}
}
return ans;
}
int main(){
//freopen("in.txt","r",stdin);
int m,a,b,c;
while(~scanf("%d",&n)&&n){
scanf("%d",&m);
init();
for(int i = 1; i <= n; i++){
Map[i][i] = INF;
for(int j = 1; j < i; j++)Map[i][j] = Map[j][i] = INF;
}
for(int i = 0; i < m; i++){
scanf("%d%d%d",&a,&b,&c);
if(c < Map[a][b])Map[a][b] = Map[b][a] = c; //注意重边,取小的
}
printf("%d\n",Prim(1));
}
return 0;
}
//题目链接 : http://poj.org/problem?id=1287
//kruskal模板
#include
#include
#include
#include
using namespace std;
const int maxn = 1e5 + 10;
int Fa[maxn],Rank[maxn];
void init(){
for(int i = 0; i <= maxn; i++)Fa[i] = i;
for(int i = 0; i <= maxn; i++)Rank[i] = 1;
}
int Find(int v){
return Fa[v] == v ? v : Fa[v] = Find(Fa[v]);
}
void Union(int x, int y){
int fx = Find(x);
int fy = Find(y);
if (fx == fy)return;
if (Rank[fx] < Rank[fy])
Fa[fx] = fy;
else{
Fa[fy] = fx;
if (Rank[fx] == Rank[fy])Rank[fy]++;
}
}
struct Edge{
int u,v,w;
Edge(){}
Edge(int u,int v,int w):u(u),v(v),w(w){}
}edge[maxn*2];
int cmp(const void *a,const void *b){
Edge *aa = (Edge*)a;
Edge *bb = (Edge*)b;
return aa->w > bb->w ? 1 : -1; //降序
}
int krusal(int n,int m){
qsort(edge,m,sizeof(edge[0]),cmp);
int ans = 0;
int cnt = 0;
for(int i = 0; i < m; i++){
int u = edge[i].u;
int v = edge[i].v;
if(Find(u) != Find(v)){
Union(u,v);
cnt++;
ans += edge[i].w;
}
if(cnt == n-1)break;
}
return ans;
}
int main(){
//freopen("in.txt","r",stdin);
int n,m;
while(~scanf("%d",&n)&&n){
scanf("%d",&m);
init();
for(int i = 0; i < m; i++) scanf("%d%d%d",&edge[i].u,&edge[i].v,&edge[i].w); //注意这题有重边但是没事
printf("%d\n",krusal(n,m));
}
return 0;
}
最短路
dijkstra,bellman_ford,floyd,SPFA
最经典的Hdu1874畅通工程续
//堆优化dijkstra
#include
using namespace std;
const int maxn = 1e4 + 10;
const int INF = 1e9;
struct Node{
int v,w;
Node(int v,int w):v(v),w(w){}
bool operator < (const Node&rhs) const {
return rhs.w < w;
}
};
vectorG[maxn];
bool vis[maxn];
int d[maxn];
int n,m;
void init(){
for(int i = 0; i < maxn; i++)G[i].clear();
for(int i = 0; i < maxn; i++)vis[i] = false;
for(int i = 0; i < maxn; i++)d[i] = INF;
}
int dijkstra(int s,int e){ //传入起点终点
priority_queueq;
q.push(Node(s,0));
d[s] = 0;
while(!q.empty()){
Node now = q.top(); q.pop();
int v = now.v;
if(vis[v])continue;
vis[v] = true;
for(int i = 0; i < G[v].size(); i++){
int v2 = G[v][i].v;
int w = G[v][i].w;
if(!vis[v2] && d[v2] > w+d[v]){
d[v2] = w+d[v];
q.push(Node(v2,d[v2]));
}
}
}
return d[e];
}
int main(){
//freopen("in.txt","r",stdin);
int s,e;
while(~scanf("%d%d",&n,&m)){
init();
for(int i = 0; i < m; i++){
int x, y, z;
scanf("%d%d%d", &x, &y, &z);
G[x].push_back(Node(y,z));
G[y].push_back(Node(x,z));
}
scanf("%d%d",&s,&e);
int ans = dijkstra(s,e);
if(INF != ans)printf("%d\n",ans);
else printf("-1\n");
}
return 0;
}
#include
const int maxn = 1000;
#define INF 0x1f1f1f1f
using namespace std;
bool flag[maxn];
int graph[maxn][maxn],low[maxn];
void DIJ(int n, int s) {
memset(flag, false, sizeof(flag));
flag[s] = true;
for (int i = 0; i < n; i++)low[i] = graph[s][i];
for (int i = 1; i < n; i++) {
int min = INF, p;
for (int j = 0; j < n; j++)
if (!flag[j] && min > low[j]) {
min = low[j];
p = j;
}
flag[p] = true;
for (int j = 0; j < n; j++)
if (!flag[j] && low[j] > graph[p][j] + low[p])
low[j] = graph[p][j] + low[p];
}
}
int main() {
int n, m, begin, t;
while (~scanf("%d%d",&n,&m)) {
for (int i = 0; i < n; i++)
for (int j = 0; j < n; j++)
if (i == j)
graph[i][j] = 0;
else
graph[i][j] = INF;
for (int i = 1; i <= m; i++) {
int x, y, z;
scanf("%d%d%d", &x, &y, &z);
if (z < graph[x][y]) graph[x][y] = graph[y][x] = z;
}
cin >> begin >> t;
DIJ(n, begin);
if (low[t] < INF) cout << low[t] << endl;
else cout << "-1" << endl;
}
return 0;
}
#include
#define INF 0x1f1f1f1f
using namespace std;
const int maxn = 1000;
vector >Edge[maxn];
int n, m, dist[maxn];
void init() {
for (int i = 0; i < maxn; i++)Edge[i].clear();
for (int i = 0; i < maxn; i++)dist[i] = INF;
}
int main() {
int s, t;
while (cin >> n >> m) {
init();
for (int i = 0; i < m; i++) {
int x, y, z;
cin >> x >> y >> z;
Edge[x].push_back(make_pair(y, z));
Edge[y].push_back(make_pair(x, z));
}
cin >> s >> t;
priority_queue>q;
dist[s] = 0;
q.push(make_pair(-dist[s], s));
while (!q.empty()) {
int now = q.top().second;
q.pop();
for (int i = 0; i < Edge[now].size(); i++) {
int v = Edge[now][i].first;
if (dist[v] > dist[now] + Edge[now][i].second) {
dist[v] = dist[now] + Edge[now][i].second;
q.push(make_pair(-dist[v], v));
}
}
}
if (dist[t] < INF)cout << dist[t] << endl;
else cout << "-1" << endl;
}
return 0;
}
//(3)bellman_ford
#include
#define maxn 1000
#define INF 0x1f1f1f1f
using namespace std;
struct Edge {
int u, v;
int weight;
};
Edge edge[maxn * 2];
int dist[maxn];
void bellman_ford(int s, int n, int m) {
for (int i = 0; i < n; i++) dist[i] = INF;
dist[s] = 0;
for (int i = 0; i < n; i++)
for (int j = 0; j < 2 * m; j++)
if (dist[edge[j].u] > dist[edge[j].v] + edge[j].weight)
dist[edge[j].u] = dist[edge[j].v] + edge[j].weight;
}
int main() {
int n, m, s, t;
while (cin >> n >> m) {
for (int i = 0; i < m; i++) {
scanf("%d%d%d", &edge[i].u, &edge[i].v, &edge[i].weight);
edge[i + m].v = edge[i].u;
edge[i + m].u = edge[i].v;
edge[i + m].weight = edge[i].weight;
}
cin >> s >> t;
bellman_ford(s, n, m);
if (dist[t] < INF) cout << dist[t] << endl;
else cout << "-1" << endl;
}
return 0;
}
//(4)floyd
#include
#define maxn 1000
#define INF 0x1f1f1f1f
using namespace std;
int graph[maxn][maxn];
int low[maxn];
void floyd(int n) {
for (int k = 0; k < n; k++)
for (int i = 0; i < n; i++)
for (int j = 0; j < n; j++)
graph[i][j] = min(graph[i][j], graph[i][k] + graph[k][j]);
}
int main() {
int n, m, s, t;
while (cin >> n >> m) {
for (int i = 0; i < n; i++)
for (int j = 0; j < n; j++)
if (i == j)
graph[i][j] = 0;
else
graph[i][j] = INF;
for (int i = 1; i <= m; i++) {
int x, y, z;
scanf("%d%d%d", &x, &y, &z);
if (graph[x][y] > z) graph[x][y] = graph[y][x] = z;
}
floyd(n);
scanf("%d%d", &s, &t);
if (graph[s][t] < INF) cout << graph[s][t] << endl;
else cout << "-1" << endl;
}
return 0;
}
//(5)SPFA
#include
#define maxn 1000
#define INF 0x1f1f1f1f
using namespace std;
int graph[maxn][maxn],flag[maxn],dist[maxn];
void SPFA(int s, int n) {
queueq;
memset(flag, false, sizeof(flag));
for (int i = 0; i < n; i++) dist[i] = INF;
q.push(s);
dist[s] = 0;
flag[s] = true;
while (!q.empty()) {
int temp = q.front();
q.pop();
flag[temp] = false;
for (int i = 0; i < n; i++) {
if (dist[i] > dist[temp] + graph[temp][i]) {
dist[i] = dist[temp] + graph[temp][i];
if (!flag[i]) {
q.push(i);
flag[i] = true;
}
}
}
}
}
int main() {
int n, m, s, t;
while (cin >> n >> m) {
for (int i = 0; i < n; i++)
for (int j = 0; j < n; j++)
if (i == j)
graph[i][j] = 0;
else
graph[i][j] = INF;
for (int i = 0; i < m; i++) {
int x, y, z;
cin >> x >> y >> z;
if (z < graph[x][y]) graph[x][y] = graph[y][x] = z;
}
cin >> s >> t;
SPFA(s, n);
if (dist[t] < INF) cout << dist[t] << endl;
else cout << "-1" << endl;
}
return 0;
}
//(6)pair+SPFA
#include
#define INF 0x1f1f1f1f
using namespace std;
const int maxn = 1000;
vector >Edge[maxn];
int n, m,dist[maxn];
bool flag[maxn];
void init() {
for (int i = 0; i < maxn; i++)Edge[i].clear();
for (int i = 0; i < maxn; i++)flag[i] = false;
for (int i = 0; i < maxn; i++)dist[i] = INF;
}
int main() {
int s, t;
while (cin >> n >> m) {
init();
for (int i = 0; i < m; i++) {
int x, y, z;
cin >> x >> y >> z;
Edge[x].push_back(make_pair(y, z));
Edge[y].push_back(make_pair(x, z));
}
cin >> s >> t;
queueq;
dist[s] = 0;
flag[s] = true;
q.push(s);
while (!q.empty()) {
int now = q.front();
q.pop();
flag[now] = false;
for (int i = 0; i < Edge[now].size(); i++) {
int v = Edge[now][i].first;
if (dist[v] > dist[now] + Edge[now][i].second) {
dist[v] = dist[now] + Edge[now][i].second;
if (!flag[Edge[now][i].first]) {
q.push(Edge[now][i].first);
flag[Edge[now][i].first] = true;
}
}
}
}
if (dist[t] < INF)cout << dist[t] << endl;
else cout << "-1" << endl;
}
return 0;
}
这里给出一个比较特殊的最短路题目
1.最短路的条数
2.多条时选择消防员(最短路经过的点的那些权值)多的那一条
3.记录路径
#include
using namespace std;
const int maxn = 1e4 + 10;
const int INF = 1e9;
typedef long long LL;
struct Node{
int v,w;
Node(int v,int w):v(v),w(w){}
bool operator < (const Node&rhs) const {
return rhs.w < w;
}
};
vectorG[maxn];
bool vis[maxn];
int Pa[maxn],Weight[maxn],TotalWeight[maxn],TotalRoad[maxn];
int d[maxn];
int n,m,s,e;
void init(){
for(int i = 0; i < maxn; i++)G[i].clear();
for(int i = 0; i < maxn; i++)vis[i] = false;
for(int i = 0; i < maxn; i++)d[i] = INF;
for(int i = 0; i < maxn; i++)Pa[i] = -1; //记录路径
for(int i = 0; i < maxn; i++)TotalRoad[i] = 0;
for(int i = 0; i < maxn; i++)TotalWeight[i] = 0;
}
void PrintPath(int x){
if(Pa[x] == -1) printf("%d",x);
else {
PrintPath(Pa[x]);
printf(" %d",x);
}
}
int dijkstra(int s,int e){ //传入起点终点
priority_queueq;
q.push(Node(s,0));
d[s] = 0, Pa[s] = -1, TotalWeight[s] = Weight[s],TotalRoad[s] = 1;
while(!q.empty()){
Node now = q.top(); q.pop();
int v = now.v;
if(vis[v])continue;
vis[v] = true;
for(int i = 0; i < G[v].size(); i++){
int v2 = G[v][i].v;
int w = G[v][i].w;
if(!vis[v2] && d[v2] > w+d[v]){
d[v2] = w + d[v];
TotalWeight[v2] = TotalWeight[v] + Weight[v2];
Pa[v2] = v;
TotalRoad[v2] = TotalRoad[v];
q.push(Node(v2,d[v2]));
}
else if(!vis[v2] && d[v2] == w+d[v]){
if(TotalWeight[v2] < TotalWeight[v] + Weight[v2]){ //如果消防员个数更多
TotalWeight[v2] = TotalWeight[v] + Weight[v2];
Pa[v2] = v;
//q.push(Node(v2,d[v2]));不需要入队了
}
TotalRoad[v2] += TotalRoad[v]; //加上之前的条数
}
}
}
return d[e];
}
int main(){
//freopen("in.txt","r",stdin);
int a,b,c;
scanf("%d%d%d%d",&n,&m,&s,&e);
for(int i = 0; i < n; i++)scanf("%d",&Weight[i]);
init();
for(int i = 0; i < m; i++){
scanf("%d%d%d",&a,&b,&c);
G[a].push_back(Node(b,c));
G[b].push_back(Node(a,c));
}
int ans = dijkstra(s,e);
//if(INF != ans)printf("%d\n",ans); else printf("-1\n");
printf("%d %d\n",TotalRoad[e],TotalWeight[e]);
PrintPath(e);
printf("\n");
return 0;
}
GCD和LCM
注意一下n个数的GCD和LCM
//题目连接 : http://acm.hdu.edu.cn/showproblem.php?pid=1019
#include
using namespace std;
const int maxn = 100 + 10;
int gcd(int a,int b){
return b == 0?a:gcd(b,a%b);
}
int lcm(int a,int b){
return a/gcd(a,b)*b;
}
int ngcd(int v[],int n){
if(n == 1) return v[0];
return gcd(v[n-1],ngcd(v,n-1));
}
int nlcm(int v[],int n){
if(n == 1) return v[0];
return lcm(v[n-1],nlcm(v,n-1));
}
int main(){
int n,m,a[maxn];
scanf("%d",&n);
while(n--){
scanf("%d",&m);
for(int i = 0; i < m; i++)scanf("%d",&a[i]);
//printf("%d\n",ngcd(a,m));
printf("%d\n",nlcm(a,m));
}
return 0;
}
埃拉托斯特尼筛法
素数和分解定理等看另一篇博客总结。
比较经典的快速筛素数,给个例题,用BFS和筛素数解决
#include
#include
#include
using namespace std;
const int maxn = 10000 + 10;
int prime[maxn],s,e,vis[maxn];
bool is_prime[maxn],flag;
//素数筛选的模板 : |埃式筛法|
int Sieve(int n) {
int k = 0;
for(int i = 0; i <= n; i++)is_prime[i] = true;
is_prime[0] = false,is_prime[1] = false;
for(int i = 2; i <= n; i++) {
if(is_prime[i]) {
prime[k++] = i;
for(int j = i*i; j <= n; j += i)is_prime[j] = false; // 轻剪枝,j必定是i的倍数
}
}
return k;
}
struct Node {
int v,step;
Node(){}
Node(int v,int step):v(v),step(step){}
};
void cut(int n,int *a){ //将各位存到a数组中
int index = 3;
while(n > 0){
a[index--] = n%10;
n /= 10;
}
}
int rev(int *a){ //还原
int s = 0;
for(int i = 0; i < 4; i++)s = s*10 + a[i];
return s;
}
void BFS(int s,int step) {
queueq;
Node now,next;
q.push(Node(s,step));
vis[s] = 1;
while(!q.empty()) {
now = q.front();q.pop();
if(now.v == e) {
flag = true;
printf("%d\n",now.step);
return ;
}
int a[4],b[4];
cut(now.v,a);
for(int i = 0; i < 4; i++){
memcpy(b,a,sizeof(b));
for(int j = 0; j <= 9; j++){
b[i] = j;
next.v = rev(b);
if(next.v < 10000 && next.v > 1000 && is_prime[next.v] && !vis[next.v] && (next.v != now.v)) {
vis[next.v] = 1;
next.step = now.step+1;
q.push(next);
}
}
}
}
}
int main() {
int T;
Sieve(maxn); //先把素数筛选出来
scanf("%d",&T);
while(T--) {
flag = false;
memset(vis,0,sizeof(vis));
scanf("%d%d",&s,&e);
BFS(s,0);
if(flag == 0)printf("Impossible\n");
}
return 0;
}
唯一分定理
具体的不细说,看一个比较简单的模板题其余还有题目在我的代码仓库。
//题目连接 : https://acm.sdut.edu.cn/onlinejudge2/index.php/Home/Index/problemdetail/pid/1634.html
#include
using namespace std;
const int maxn = 30000;
int prime[maxn];
bool is_prime[maxn];
//素数筛选的模板 : |埃式筛法|
int Sieve(int n) {
int k = 0;
for(int i = 0; i <= n; i++)is_prime[i] = true;
is_prime[0] = false,is_prime[1] = false;
for(int i = 2; i <= n; i++) {
if(is_prime[i]) {
prime[k++] = i;
for(int j = i*i; j <= n; j += i)is_prime[j] = false; // 轻剪枝,j必定是i的倍数
}
}
return k;
}
int main(){
int T,n;
scanf("%d",&T);
int len = Sieve(maxn);
while(T--){
scanf("%d",&n);
int ans[maxn],k = 0;
for(int i = 0; i < len; i++){ //唯一分解定理
while(n % prime[i] == 0){
ans[k++] = prime[i];
n /= prime[i];
}
}
for(int i = 0; i < k; i++)printf(i == 0 ? "%d": "*%d",ans[i]);
printf("\n");
}
return 0;
}
扩展欧几里得
基本算法:对于不完全为 0的非负整数 a,b,gcd(a,b)表示a,b的最大公约数,必然存在整数对x,y,使得 gcd(a,b)=ax+by。,具体用处在于:
求解不定方程;
求解模线性方程(线性同余方程);
求解模的逆元;
给出一个讲的不错的博客。
这里给出一个求解不定方程组解的测试程序:
//扩展欧几里得的学习
#include
int exgcd(int a,int b,int &x,int &y){
if(b == 0){
x = 1;
y = 0;
return a;
}
int d = exgcd(b,a%b,x,y); //d 就是gcd(a,b)
int t = x;
x = y;
y = t-(a/b)*y;
return d;
}
bool linear_equation(int a,int b,int c,int &x,int &y){
int d = exgcd(a,b,x,y); //d是gcd(a,b)//求出a*x+b*y = gcd(a,b)的一组解
printf("%d*x + %d*y = %d(gcd(a,b))的一些解是\n",a,b,d);
printf("%d %d\n",x,y); //第一组
for(int t = 2; t < 10; t++){ //输出 a*x + b*y = gcd(a,b)的其他一些解
int x1 = x + b/d*t;
int y1 = y - a/d*t;
printf("%d %d\n",x1,y1); //其余组
}
printf("\n\n"); //第一组
if(c%d) return false;
int k = c/d; //上述解乘以 c/gcd(a,b)就是 a*x +b*y = c的解
x *= k; y *= k; //求得的只是其中一组解
return true;
}
int main(){
//freopen("in.txt","r",stdin);
int a = 6,b = 15,c = 9; //求解 6*x + 15*y = 9的解
int x,y;
int d = exgcd(a,b,x,y);
if(!linear_equation(a,b,c,x,y))printf("无解\n");
printf("%d*x + %d*y = %d的一些解是\n",a,b,c);
printf("%d %d\n",x,y); //第一组
for(int t = 2; t < 10; t++){ //输出其他的一些解
int x1 = x + b/d*t;
int y1 = y - a/d*t;
printf("%d %d\n",x1,y1); //其余组
}
return 0;
}
再给出一个比较简单的模板题
//题目链接:http://poj.org/problem?id=1061
//题目大意:中文题,给出青蛙A,B的起点坐标,以及跳跃距离m,n以及环的长度L,求什么时候能相遇
/*解题思路: 假设跳了T次以后,青蛙1的坐标便是x+m*T,青蛙2的坐标为y+n*T。它们能够相遇的情况为(x+m*T)-(y+n*T)==P*L,其中P为某一个整数,变形一下设经过T次跳跃相遇,得到(n-m)*T-P*L==x-y 我们设a=(n-m),b=L,c=x-y,T=x,P=y.
得到ax+by==c,直接利用欧几里得扩展定理可以得到一组x,y但是这组x,y不一定是符合条件的最优解,
首先,当gcd(a,b)不能整除c的时候是无解的,当c能整除gcd(a,b)时,把x和y都要变为原来的c/gcd(a,b)倍,
我们知道它的通解为x0+b/gcd(a,b)*t要保证这个解是不小于零的最小的数,
我们先计算当x0+b/gcd(a,b)*t=0时的t值,
此时的t记为t0=-x0/b/gcd(a,b)(整数除法),代入t0如果得到的x小于零再加上一个b/gcd(a,b)就可以了*/
#include
using namespace std;
typedef long long LL;
LL exgcd(LL a,LL b,LL &x,LL &y){
if(b == 0){
x = 1;
y = 0;
return a;
}
LL d = exgcd(b,a%b,x,y);
LL t = x;
x = y;
y = t-(a/b)*y;
return d;
}
int main(){
//freopen("in.txt","r",stdin);
LL x,y,m,n,L,T,P;
while(cin>>x>>y>>m>>n>>L){
LL a,b,c;
a = n-m;
b = L;
c = x-y;
LL d = exgcd(a,b,T,P);
if(c%d!=0){ cout<
T = T*(c/d);
P = P*(c/d);
LL k = T*d/b;
k = T-k*b/d;
if(k < 0) k = k+b/d;
printf("%lld\n",k);
}
return 0;
}
欧拉函数
欧拉函数只是工具:
提供1到N中与N互质的数;
对于一个正整数N的素数幂分解为N = P1^q1 * P2^q2 * ... * Pn^qn,则欧拉函数 γ(N) = N*(1-1/P1) * (1-1/P2) * ... * (1-1/Pn)。
这里也给出一个博客讲解。
//欧拉函数模板
//题目连接 : https://vjudge.net/contest/185827#problem/G
//题目意思 : 就是要你求1~n互素的对数(注意1~1不要重复)
#include
#include
const int maxn = 50000;
int phi[maxn+1], phi_psum[maxn+1];
int euler_phi(int n){
int m = sqrt(n+0.5);
int ans = n;
for(int i = 2; i < m; i++)if(n % i == 0){
ans = ans/i*(i-1);
while(n % i == 0)n /= i;
}
if(n > 1)ans = ans/n*(n-1);
return ans;
}
//筛素数的方法,求解1~n所有数的欧拉phi函数值
void phi_table(int n) {
phi[1] = 0; //这里不计算第一个1,1和1,1是重复的,等下直接2*phi_psum[n] + 1
for(int i = 2; i <= n; i++) if(phi[i] == 0)
for(int j = i; j <= n; j += i) {
if(phi[j] == 0) phi[j] = j;
phi[j] = phi[j] / i * (i-1);
}
}
int main() {
int n;
phi_table(maxn);
phi_psum[0] = 0;
for(int i = 1; i <= maxn; i++)phi_psum[i] = phi_psum[i-1] + phi[i];
while(scanf("%d", &n) == 1 && n) printf("%d\n",2*phi_psum[n] + 1);
return 0;
}
这里再贴上大牛的模板:
const int MAXN = 10000000;
bool check[MAXN+10];
int phi[MAXN+10];
int prime[MAXN+10];
int tot;//素数的个数
// 线性筛(同时得到欧拉函数和素表)
void phi_and_prime_table(int N) {
memset(check,false,sizeof(check));
phi[1] = 1;
tot = 0;
for(int i = 2; i <= N; i++) {
if( !check[i] ) {
prime[tot++] = i;
phi[i] = i-1;
}
for(int j = 0; j < tot; j++) {
if(i * prime[j] > N)break;
check[i * prime[j]] = true;
if( i % prime[j] == 0) {
phi[i * prime[j]] = phi[i] * prime[j];
break;
} else {
phi[i * prime[j]] = phi[i] * (prime[j] - 1);
}
}
}
}
快速幂
乘法快速幂看这里。
#include
//计算(a*b)%c
long long mul(long long a,long long b,long long mod) {
long long res = 0;
while(b > 0){
if( (b&1) != 0) // 二进制最低位是1 --> 加上 a的 2^i 倍, 快速幂是乘上a的2^i )
res = ( res + a) % mod;
a = (a << 1) % mod; // a = a * 2 a随着b中二进制位数而扩大 每次 扩大两倍。
b >>= 1; // b -> b/2 右移 去掉最后一位 因为当前最后一位我们用完了,
}
return res;
}
//幂取模函数
long long pow1(long long a,long long n,long long mod){
long long res = 1;
while(n > 0){
if(n&1)
res = (res * a)%mod;
a = (a * a)%mod;
n >>= 1;
}
return res;
}
// 计算 ret = (a^n)%mod
long long pow2(long long a,long long n,long long mod) {
long long res = 1;
while(n > 0) {
if(n & 1)
res = mul(res,a,mod);
a = mul(a,a,mod);
n >>= 1;
}
return res;
}
//递归分治法求解
int pow3(int a,int n,int Mod){
if(n == 0)
return 1;
int halfRes = pow1(a,n/2,Mod);
long long res = (long long)halfRes * halfRes % Mod;
if(n&1)
res = res * a % Mod;
return (int)res;
}
int main(){
printf("%lld\n",mul(3,4,10));
printf("%lld\n",pow1(2,5,3));
printf("%lld\n",pow2(2,5,3));
printf("%d\n",pow3(2,5,3));
return 0;
}
矩阵快速幂
这个也是比较常用的,主要还是在于常数矩阵的求解和递推公式的求解,这里给出一个博客讲解和自己写过的一道模板题,以及后面自己的总结。
转换之后,直接把问题转化为求矩阵的n-9次幂。
//题目链接:https://vjudge.net/contest/172365#problem/A
//题目大意:函数满足——If x < N f(x) = x.
//If x >= N f(x) = a0 * f(x - 1) + a1 * f(x - 2) + a2 * f(x - 3) + …… + a9 * f(x - N);
//ai(0<=i<=9) can only be 0 or 1 .
//要求f(k) % mod;
//解题思路:构造矩阵求解N*N矩阵
#include
#include
const int N = 10;
using namespace std;
int Mod;
struct Matrix {
int m[N][N];
Matrix(){}
};
void Init(Matrix &matrix){
for(int i = 0;i < N; i++)scanf("%d",&matrix.m[0][i]);
for(int i = 1;i < N; i++){
for(int j = 0;j < N; j++){
if(i == (j+1))matrix.m[i][j] = 1;
else matrix.m[i][j] = 0;
}
}
}
//矩阵相乘
Matrix Mul(Matrix &a,Matrix &b){
Matrix c;
for(int i = 0; i < N; i++){
for(int j = 0;j < N; j++){
c.m[i][j] = 0;
for(int k = 0; k < N; k++)c.m[i][j] += a.m[i][k]*b.m[k][j];
c.m[i][j] %= Mod;
}
}
return c;
}
//矩阵幂
Matrix Pow(Matrix& matrix, int k) {
Matrix res;
for (int i = 0; i < N; ++i)
for (int j = 0; j < N; ++j)
if (i == j) res.m[i][j] = 1;
else res.m[i][j] = 0;
while (k) {
if (k & 1) res = Mul(matrix,res);
k >>= 1;
matrix = Mul(matrix,matrix);
}
return res;
}
int main() {
int k,a[N];
while (~scanf("%d%d",&k,&Mod)) {
Matrix matrix;
Init(matrix);
if (k < 10) { printf("%d\n",k % Mod); continue; }
matrix = Pow(matrix,k-9);
int sum = 0;
for (int i = 0; i < N; i++)sum += matrix.m[0][i] * (9 - i) % Mod;
printf("%d\n",sum%Mod);
}
return 0;
}