中国矿业大学数据结构实验(1)

A 找新朋友

考察欧拉函数\(\phi(n)\):\([1,n]\)中与\(n\)互质的数的个数

欧拉函数性质:

(1)当\(p\)为质数时,\(\phi(p)=p-1\)
(2)当\(p\)为质数时,$ \phi(p^k) = (p-1) p^{k-1}$

证明:考虑把整数\([1,p^{k}]\) 分解为\([1,p]+[p+1,p^2]+\dots+[p^{k-1}+1,p^k]\)\(p^{k-1}\)个区间
每个区间都有\(p-1\)个数和\(p^{k}\)互质,易得$ \phi(p^k) = (p-1) p^{k-1}$

(3)当\(n\)\(m\)互质时,\(\phi(mn)=\phi(m)*\phi(n)\)
证明:两个数互质,不存在共同的因数,结果是显然的

欧拉函数的计算

根据唯一分解定理:\(n=p_1^{a_1}\times p_2^{a_2} \times \dots p_s^{a_s}\)
结合欧拉函数的三条性质可得:\(\phi(n)= \displaystyle{\prod_{i=1}^s\phi(p_i^{a_i})=\prod_{i=1}^s(p_i-1)p^{a_i-1}=\prod_{i=1}^sp_i^{a_i}\frac{p_i-1}{p_i}=n\times\prod_{i=1}^s\frac{p_i-1}{p_i}}\)

欧拉筛

用筛法求\(n\)以内的质数,时间复杂度是线性的,保证每个合数都被他的最小质因数筛去,具体流程为

  • 遍历\([1,n]\),如果当前的\(i\)没有被打上标记,说明\(i\)是质数
  • 从小到大遍历当前找到的质数\(p[j]\in p[1,cnt]\)
  • \(p[j]\)\(i\)的因数:终止遍历,因为\(i\times p[j+1]\)的最小质因数是\(p[j] < p[j+1]\),(\(i\times p[j+1]\)会被后面更大的\(i\)\(p[j]\)筛去)
  • \(p[j]\)不是\(i\)的因数:给\(i\times p[j]\)打上标记
筛法求欧拉函数

原理同欧拉筛,因为欧拉函数就是用质因数计算的,可以找到\(n\)的欧拉函数和\(n\)的最小质因数\(p[j]\)的欧拉函数的关系

  • \(p[j]\)\(i\)的因数:终止遍历,同时\(\phi(n)=p[j]\times \phi(i)\)
  • \(p[j]\)不是\(i\)的因数:给\(i\times p[j]\)打上标记,同时\(\phi(n)=(p[j]-1)*\phi(i)\)
void Eulor(int n)
{
	phi[1] = 1;
	for(int i = 2 ; i <= n ; i ++)
	{
		if(vis[i] == 0) phi[i] = i-1,p[cnt++] = i;
		
		for(int j = 0 ; j < cnt && i*p[j] <= n ; j ++)
		{
			int m = i*p[j];
			vis[m] = 1;
			if(i % p[j] == 0)
			{
				phi[m] = p[j]*phi[i];
				break;
			}
			else
			{
				phi[m] = (p[j]-1)*phi[i];
			}
		}
	}
	
}

B 互质

A问题的规模不大,可以先离线的算出,再\(O(1)\)进行询问,B题只能用在线的试除法
试除法的原理非常简单:\(n\)若有质因数\(p\),那就一直除以\(p\),直到不能整除为止

void slove(LL n)
{
	LL res = n;
	for(int i = 2 ; i*i <= n ; i ++)
	{
		if(n%i == 0) res = res/i*(i-1);
		while(n%i == 0) n/= i;
	}
	//剩下一个大于sqrt(n)的质数
	if(n >= 2) res  = res/n*(n-1);
	cout << res << endl;	
}

C 约瑟夫问题

问题规模为\((n,m)\)的约瑟夫问题
(1)模拟法

#include<iostream>
using namespace std;
const int N = 105;
struct Node {
	int val;
	int next;
	int pre;
}Nodes[N];

int main()
{
	int n, m;
	cin >> n >> m;
	Nodes[0].next = 1;
	for (int i = 1; i <= n; i++)
	{
		Nodes[i].val = i;
		Nodes[i].next = i + 1;
	}
	Nodes[n].next = 1;
	int now = 1, preN = 1;
	while ((n--) > 1)
	{
		for (int i = 1; i < m; i++)
		{
			preN = now;
			now = Nodes[now].next;
		}
		cout << Nodes[now].val << ' ';
		Nodes[preN].next = Nodes[now].next;
		now = Nodes[preN].next;
	}
	cout << Nodes[now].val;
	return 0;
}

(2)递推法
\(J_{n,m}\)表示问题规模为\((n,m)\)的约瑟夫问题,有如下递推公式:\(J_{n,m}=(J_{n-1,m}+m)\mod n\)

int solve(int n,int m)
{
	int p=0;
	for(int i=2;i<=n;i++)
	{
		p=(p+m)%i;
	}
	return p+1;
}

D 进制转换

不会退学吧

E 整数求和

string的库函数要记得

  • 取子串:string sub = s.substr(起始下标,子串长度)
  • 查找字符串:int pos = s.find(字符串)
  • int/long long/doule -> stringto_string
  • string -> intstoi()
  • string -> long longstoll()
  • string -> doublestod()
void solve(string op)
{
    int n = op.size() - 1; // 最后一个字符是=
    int pos = op.find("+");

    string sub1 = op.substr(0, pos);
    string sub2 = op.substr(pos + 1, n - pos - 1); 

    int num1 = stoi(sub1);
    int num2 = stoi(sub2);
    int result = num1 + num2;

    cout << op << result << endl; 
}

E 波兰表达式

(1)字符串流读入数据

#include<iostream>
#include<sstream>
#include<string>
#include<stack>
#include<vector>
using namespace std;
stack<double> op1;
vector<string> v;

int main()
{
	string op, x;
	getline(cin, op);  
	istringstream ss(op);

	// 将输入分割为标记并存储到vector中
	while (ss >> x)
	{
		v.push_back(x);
	}

	// 逆序处理vector中的每个元素(从右到左)
	for (int i = v.size() - 1; i >= 0; i--)
	{
		if (v[i] == "+")
		{
			double a = op1.top(); op1.pop();
			double b = op1.top(); op1.pop();
			op1.push(a + b);
		}
		else if (v[i] == "-")
		{
			double a = op1.top(); op1.pop();
			double b = op1.top(); op1.pop();
			op1.push(a - b);  // 注意操作数顺序:a - b
		}
		else if (v[i] == "*")
		{
			double a = op1.top(); op1.pop();
			double b = op1.top(); op1.pop();
			op1.push(a * b);
		}
		else if (v[i] == "/")
		{
			double a = op1.top(); op1.pop();
			double b = op1.top(); op1.pop();
			op1.push(a / b);
		}
		else { 
			op1.push(stod(v[i]));
		}
	}

	cout << op1.top() << endl;  // 输出最终结果(小数什么的自己搞)
	return 0;
}

(2)递归解法(学长的超牛解法,原文学长的代码)

#include<bits/stdc++.h>
using namespace std;
double dp()
{
    char a[30];
    cin>>a;
    switch (a[0]){
    case '+':return dp() + dp();
    case '-':return dp() - dp();
    case '*':return dp()*dp();
    case '/':return dp() / dp();
    default:return atof(a); //atof函数能将char转换为double
    }
}
int main()
{
    printf("%.6f",dp());
}

F 合并队列

简单的二路归并,不想写了

posted @ 2025-06-03 17:00  _P_D_X  阅读(54)  评论(0)    收藏  举报