PAT 第一周(1001-1004)

 小禾很是sad,年三十编辑的一半博客竟然没发布导致这两天年味太足电脑耗电关机,内容消失术!初二闲来无事赶紧来补救一下,可能忙完这篇就出去逛街了,大家新年快乐呀!


整理这类博客的原因呢,是小禾被身边人感染到了,不管是小禾当班导的班上大一孩子还是同龄的大三佬们都在寒假刷题,我其实对自己挺没有规划的,抱着“走一步看一步”的心态,可能这也是小禾从大二以来一直绩点下滑直至大三上跌出4的一个原因吧。


PAT是一门计算机程序设计能力测试,小禾的班导说这个题库慢慢刷,应该能应付除清北外的大学机试,对于我来说也足够了,so try!

目录

1001 A+B Format

另附:高精度运算

1002 A+B for Polynomials

另附:快速排序

1003 Emergency

1004 Counting Leaves


1001 A+B Format

Calculate a+b and output the sum in standard format -- that is, the digits must be separated into groups of three by commas (unless there are less than four digits).

Input Specification:

Each input file contains one test case. Each case contains a pair of integers a and b where -10^{6}≤a,b≤10^{6}. The numbers are separated by a space.

Output Specification:

For each test case, you should output the sum of a and b in one line. The sum must be written in the standard format.

Sample Input:

-1000000 9

Sample Output:

-999,991

这道题的本质是字符串处理,本该是一道送分题,岂料我当初没注意到加数范围,以为是道高精题,傻乎乎的小禾去练了一下高精,话不多说。

【本题代码】

#include<iostream>
#include<string>
using namespace std;
void function_2()
{
	int a, b;
	cin >> a >> b;
	int c = a + b;
	if (c < 0) {
		cout << '-';
		c = 0 - c;
	}
	string r=to_string(c);
	int t = 3 - r.size() % 3;
	for (int i = 0; i < r.size(); i++)
	{
		cout << r[i];
		t++;
		if (i != r.size() - 1 && t % 3 == 0) {
			cout << ',';
			t = 0;
		}
	}
}

另附:高精度运算

#include<iostream>
#include<algorithm>
#include<cmath>
#include<vector>
using namespace std;
int char2int(char);
bool cmp(string, string);
void high_add();
void high_subtract();
void Abstract(string, string);
void high_mutiply();
void high_divide();
void high_divide_1(string a, string b);
void high_divide_2(string a, string b);
int main()
{
    high_add();
    high_subtract();
    high_mutiply();
	high_divide();
}
int char2int(char a)
{
	return a - '0';
}
bool cmp(string a, string b)
{
	if (a.size() > b.size())
		return true;
	else if (a.size() < b.size())
		return false;
	else {
		for (int i = 0; i < a.size(); i++)
			if (char2int(a[i]) > char2int(b[i]))return true;
			else if (char2int(a[i]) < char2int(b[i]))return false;
	}
	return false;
}
void high_add()
{
	vector<int> result;
	string a, b;
	cin >> a >> b;
	int m = a.size()-1, n=b.size()-1;
	int temp = 0;
	int t = 0;
	while (m >= 0 && n >= 0) {
		t = char2int(a[m]) + char2int(b[n]) + temp;
		result.push_back(t % 10);
		temp = t / 10;
		m--; n--;
	}
	while (m >= 0) {
		t = char2int(a[m]) + temp;
		result.push_back(t % 10);
		temp = t / 10;
		m--;
	}
	while (n >= 0) {
		t = char2int(b[n]) + temp;
		result.push_back(t % 10);
		temp = t / 10;
		n--;
	}
	if (temp != 0)
		result.push_back(temp);
	for (int i = result.size() - 1; i >= 0; i--)
		cout << result[i];
}
void high_subtract()
{
	string a, b;
	cin >> a >> b;
	if (cmp(a,b)) 
		Abstract(a, b);
	else {
		cout << '-';
		Abstract(b, a);
	}
}
void Abstract(string a,string b)
{	
	vector<int> result;
	int m = a.size() - 1, n = b.size() - 1;
	int temp = 0;
	int t = 0;
	while (m >= 0 && n >= 0) {
		t = char2int(a[m]) - char2int(b[n]) + temp;
		temp = 0;
		if (t < 0) {
			t += 10; temp = -1;
		}
		result.push_back(t);
		m--; n--;
	}
	while (m >= 0) {
		t = char2int(a[m]) + temp;
		temp = 0;
		if (t < 0) {
			t += 10; temp = -1;
		}
		result.push_back(t);
		m--;
	}
	while (n >= 0) {
		t = char2int(b[n]) + temp;
		temp = 0;
		if (t < 0) {
			t += 10; temp = -1;
		}
		result.push_back(t);
		n--;
	}
	temp = result.size();
	for (int i = result.size()-1; i>=0; i--)
		if (result[i] != 0)
			break;
		else
			temp = i;
	if (temp == 0)cout << 0;
	else
		for (int i = temp-1; i >=0; i--)
			cout << result[i];
}
void high_mutiply()
{
	string a, b;
	cin >> a >> b;
	int m = a.size() - 1, n = b.size() - 1;
	if ((m == 0 && a[m] == '0') || (n == 0 && b[n] == '0'))
		cout << 0;
	else {
		int* r = new int[(max(m, n) + 1) * 2];
		for (int i = 0; i < (max(m, n) + 1) * 2; i++)
			r[i] = 0;
		int t = 0, len = 0;
		int i, j;
		for (i = m; i >= 0; i--) /*用数组模拟运算*/
			for (j = n, t = m - i; j >= 0; j--)
			{
				len = max(len, t);
				r[t++] += char2int(b[j]) * char2int(a[i]);
			}
		for (i = 0; i < len; i++) {/*进位处理*/
			r[i + 1] += r[i] / 10;
			r[i] %= 10;
		}
		if (r[len] > 10) {
			r[len + 1] = r[len] / 10;
			r[len] %= 10;
			len++;
		}
		for (int i = len; i >= 0; i--)
			cout << r[i];
	}
}
void high_divide_1(string a, string b)	//高精除以高精=高精
{
	if (cmp(b, a))
		cout << 0;
	else {
		int mmax = max(a.size(), b.size());
		int* r = new int[mmax];
		for (int i = 0; i < mmax; i++)
			r[i] = 0;
		int k = 0;
		while (1) {
			r[0] = r[0] + 1;
			vector<int> result;
			int m = a.size() - 1, n = b.size() - 1;
			int temp = 0;
			int t = 0;
			while (m >= 0 && n >= 0) {
				t = char2int(a[m]) - char2int(b[n]) + temp;
				temp = 0;
				if (t < 0) {
					t += 10; temp = -1;
				}
				result.push_back(t);
				m--; n--;
			}
			while (m >= 0) {
				t = char2int(a[m]) + temp;
				temp = 0;
				if (t < 0) {
					t += 10; temp = -1;
				}
				result.push_back(t);
				m--;
			}
			while (n >= 0) {
				t = char2int(b[n]) + temp;
				temp = 0;
				if (t < 0) {
					t += 10; temp = -1;
				}
				result.push_back(t);
				n--;
			}
			temp = result.size();
			for (int i = result.size() - 1; i >= 0; i--)
				if (result[i] != 0)
					break;
				else
					temp = i;
			for (int i = 0; i < k; i++) {
				r[i + 1] += r[i] / 10;
				r[i] = r[i] % 10;
			}
			if (r[k] >= 10) {
				r[k + 1] += r[k] / 10;
				r[k] = r[k] % 10;
				k++;
			}
			a.assign("");
			if (temp == 0)
				a += '0';
			for (int i = temp - 1; i >= 0; i--)
				a += result[i] + '0';
			if (cmp(b, a))
				break;
		}
		for (int i = k; i >= 0; i--)
			cout << r[i];
	}
}

void high_divide_2(string a,string b)//高精除以高精=低精
{
	if (cmp(b, a))
		cout << 0;
	else {
		int k = 0;
		while (1) {
			k++;
			vector<int> result;
			int m = a.size() - 1, n = b.size() - 1;
			int temp = 0;
			int t = 0;
			while (m >= 0 && n >= 0) {
				t = char2int(a[m]) - char2int(b[n]) + temp;
				temp = 0;
				if (t < 0) {
					t += 10; temp = -1;
				}
				result.push_back(t);
				m--; n--;
			}
			while (m >= 0) {
				t = char2int(a[m]) + temp;
				temp = 0;
				if (t < 0) {
					t += 10; temp = -1;
				}
				result.push_back(t);
				m--;
			}
			while (n >= 0) {
				t = char2int(b[n]) + temp;
				temp = 0;
				if (t < 0) {
					t += 10; temp = -1;
				}
				result.push_back(t);
				n--;
			}
			temp = result.size();
			if (temp < b.size())
				break;
			for (int i = result.size() - 1; i >= 0; i--)
				if (result[i] != 0)
					break;
				else
					temp = i;
			int key = 0;
			if (temp < b.size())	//被除数<除数
				break;
			else if (temp == b.size()) {
				for (int i = temp - 1; i >= 0; i--)
				{
					if (result[i] < char2int(b[b.size()-1-i]))
					{
						key = -1; break;
					}
					if (result[i] > char2int(b[b.size()-1-i]))
					{
						key = 1; break;
					}
				}
				if (key == -1)	//被除数<除数
					break;
				if (key == 0)	//两数相等
				{
					k++; break;
				}
			}
			if (temp > b.size() || key == 1)	//被除数>除数
			{
				a.assign("");
				for (int i = temp - 1; i >= 0; i--)
					a += result[i] + '0';
			}
		}
		cout << k;
	}
}
void high_divide()//优化版的高精/高精=高精(低精同样适用)(防止t了)
{
	string x, y;
	cin >> x >> y;
	if (cmp(y, x))
		cout << 0;
	else {
		int la = x.size();
		int lb = y.size();
		int lans = la-lb + 1; // 商长度
		int* ans = new int[lans];
		int* a = new int[la];
		int* b = new int[lb];
		for (int i = 0; i < la; i++)
			a[i] = char2int(x[i]);//将字符串转换成数组
		for (int i = 0; i < lb; i++)
			b[i] = char2int(y[i]);//将字符串转换成数组
		for (int i = 0; i < lans; i++)
			ans[i] = 0;
		//把除法转换成减法,不停地减,每减一次,对应位置的答案加一
		for (int i = 0; i < lans; i++)//开始除法
		{
			while (1)
			{
				int j, k;
				int key = 0;
				for (j = i, k = 0; j < i + lb, k < lb; j++, k++)
				{
					if (a[j] > b[k]) //被除数>除数
						break;
					else
						if (a[j] < b[k]) {//被除数<除数
							key = 1;
							break;
						}
				}
				if (!key)//如果可以做减法
				{
					for (j = lb - 1 + i, k = lb - 1; j >= i, k >= 0; j--, k--)
					{
						a[j] -= b[k];
						if (a[j] < 0) {
							a[j] += 10;
							a[j - 1]--;
						}
					}
					ans[i]++;
				}
				else//如果不能做减法
				{
					a[i + 1] += a[i] * 10;//将最前一位的所有数全部退到下一位置的数上
					a[i] = 0;//归零(有木有都无所谓)
					break;//跳出减法循环
				}
			}
		}
		for (int i = 0; i < lans; i++)//输出
			if (i == 0 && ans[i] == 0);//当第一位是零时,不输出
			else
				cout << ans[i];
	}
}

1002 A+B for Polynomials

This time, you are supposed to find A+B where A and B are two polynomials.

Input Specification:

Each input file contains one test case. Each case occupies 2 lines, and each line contains the information of a polynomial:

K N_{1} a_{N_{1}} N_{2} a_{N_{2}} ... N_{K} a_{N_{K}}

where K is the number of nonzero terms in the polynomial, N_{i}​ and a_{N_{i}} (i=1,2,⋯,K) are the exponents and coefficients, respectively. It is given that 1\leq K\leq 10, 0\leq N_{K}<...<N_{2}<N_{1}\leq 1000.

Output Specification:

For each test case you should output the sum of A and B in one line, with the same format as the input. Notice that there must be NO extra space at the end of each line. Please be accurate to 1 decimal place.

Sample Input:

2 1 2.4 0 3.2
2 2 1.5 1 0.5

Sample Output:

3 2 1.5 1 2.9 0 3.2

这道题都算降低了难度,输入样例都是排好序的,那么只需运用一下快慢指针即可,真的很简单易懂,两个注意点,①对于相加得零时要处理不必打印,②对于系数要保留小数点后一位。

【本题代码】

#include<iostream>
#include<iomanip>
using namespace std;
int main()
{
	int a, b;
	cin >> a;
	int* aa = new int[a];//指数
	double* x = new double[a];//系数
	for (int i = 0; i < a; i++)
		cin >> aa[i]>>x[i];
	cin >> b;
	int* bb = new int[b];
	double* y = new double[b];
	for (int i = 0; i < b; i++)
		cin >> bb[i]>>y[i];
	int p = 0, q = 0, k = 0, t = 0;
	double* r = new double[2 * a + 2 * b];
	while (p < a && q < b) {
		if (aa[p] > bb[q]) {
			r[k] = aa[p]; r[k + 1] = x[p];
			p++;
		}
		else if (aa[p] < bb[q]) {
			r[k] = bb[q]; r[k + 1] = y[q];
			q++;
		}
		else {
			if (x[p] + y[q] != 0) {
				r[k] = aa[p]; r[k + 1] = x[p] + y[q];
			}
			else {k-=2; t--;}
			p++; q++;
		}
		k += 2;
		t++;
	}
	while (p < a) {
		r[k] = aa[p]; r[k + 1] = x[p];
		p++; k += 2;
		t++;
	}
	while (q < b) {
		r[k] = bb[q]; r[k + 1] = y[q];
		q++; k += 2;
		t++;
	}
	cout << t;
	for (int i = 0; i < 2 * t; i += 2)
		cout << " " << fixed << setprecision(0) << r[i] << " " << setprecision(1) << r[i + 1];
	return 0;
}

另附:快速排序

如果输入乱序,提前处理一下排好序就ok啦。

void QuickSort(int* aa, double* x, int left, int right)
{
	if (left >= right)
		return;
	int pivot = left;//基准值
	int index = pivot + 1;
	for (int i = index; i <= right; i++)
	{
		if (aa[i] < aa[pivot]) {
			swap(aa[i], aa[index]);
			swap(x[i], x[index]);
			index++;
		}
	}
	swap(aa[pivot], aa[index - 1]);//基准值的位置为index-1
	swap(x[pivot], x[index - 1]);
	QuickSort(aa, x, left, index - 2);
	QuickSort(aa, x, index, right);
}
void swap(int& a, int& b)
{
	int temp = a;
	a = b;
	b = temp;
}

1003 Emergency

As an emergency rescue team leader of a city, you are given a special map of your country. The map shows several scattered cities connected by some roads. Amount of rescue teams in each city and the length of each road between any pair of cities are marked on the map. When there is an emergency call to you from some other city, your job is to lead your men to the place a{\color{DarkGreen} L}s quickly as possible, and at the mean time, call up as many hands on the way as possible.

Input Specification:

Each input file contains one test case. For each test case, the first line contains 4 positive integers: N (≤500) - the number of cities (and the cities are numbered from 0 to N−1), M - the number of roads, {\color{DarkGreen}C_1 }and {\color{DarkGreen} C_2} - the cities that you are currently in and that you must save, respectively. The next line contains N integers, where the i-th integer is the number of rescue teams in the i-th city. Then M lines follow, each describes a road with three integers {\color{DarkGreen} c_1}​, {\color{DarkGreen} c_2} and {\color{DarkGreen} L}, which are the pair of cities connected by a road and the length of that road, respectively. It is guaranteed that there exists at least one path from {\color{DarkGreen}C_1 }​ to {\color{DarkGreen} C_2}.

Output Specification:

For each test case, print in one line two numbers: the number of different shortest paths between {\color{DarkGreen}C_1 } and {\color{DarkGreen} C_2}​, and the maximum amount of rescue teams you can possibly gather. All the numbers in a line must be separated by exactly one space, and there is no extra space allowed at the end of a line.

Sample Input:

5 6 0 2
1 2 1 5 3
0 1 1
0 2 2
0 3 1
1 2 1
2 4 1
3 4 1

Sample Output:

2 4

本题的实质为具有多条最短路径的图论问题,我们只需改造一下传统的Dijkstra算法,保留具有相同路径时的路径,同时需要在这多条最短路径中再选出节点权重之和最大的一个。

在上代码之前,简单阐述一下变量的用途,dist为Dijkstra算法的核心变量,用于计算单源到每个节点的最短路径长度,pathnum记录最短路径的条数,pre记录多个路径中的前缀顶点(不够后续并没有用到),tol计算多条最短路径中经过的节点权重之和,用于选出最终的具有最大节点权重之和的最短路径path,即在最快的抵达目的地的途中尽可能多的集结到急救队员!

#include<iostream>
#include<cstring>
#include<cmath>
#include<vector>
using namespace std;
void Dijkstra_new(int start, int end, int* dist, int* w, int** l, int* pathnum, int* path, vector<int> pre[500]);
#define Max 0x3f3f3f3f
int N, M, C1, C2, x, y, z;
int main()
{
	cin >> N >> M >> C1 >> C2;
	int** l = new int*[N];//长度
	int* w = new int[N];//急救队个数
	int* path = new int[N];
	vector<int> pre[500];
	int* dist = new int[N];
	int* pathnum = new int[N];
	for (int i = 0; i < N; i++)
	{
		cin >> w[i];
		l[i] = new int[N];
		path[i] = -1;
		pathnum[i] = 0;
		dist[i] = Max;
	}
	dist[C1]=0;
	pathnum[C1] = 1;
	for (int i = 0; i < N; i++)
		for (int j = 0; j < N; j++)
		{
			if (i == j)l[i][j] = 0;
			else l[i][j] = Max;
		}
	for (int i = 0; i < M; i++)
	{
		cin >> x >> y >> z;
		l[x][y] = z;
		l[y][x] = l[x][y];
	}
	Dijkstra_new(C1, C2, dist, w, l, pathnum, path, pre);
	return 0;
}
void Dijkstra(int start, int* dist,int** l, int* path)//传统的Dijkstra算法,单源(只记录了一条最短路径)
{
	int* vis = new int[N];
	memset(vis, 0, sizeof(vis));
	vis[start] = 1;
	for (int i = 0; i < N; i++) {
		int min = Max,temp=0;
		for (int i = 0; i < N; i++) {
			if (!vis[i]&&dist[i] < min) {
				min = dist[i];
				temp = i;
			}
		}
		vis[temp] = 1;
		for (int i = 0; i < N; i++) {
			if (dist[temp] + l[temp][i] < dist[i]) {
				dist[i] = dist[temp] + l[temp][i];
				path[i] = temp;
			}
		}
	}	
}
void Dijkstra_new(int start, int end, int* dist, int* w, int** l, int* pathnum, int* path, vector<int> pre[500])//改造后,单源(只记录了一条最短路径)
{
	int* vis = new int[N];
	int* tol = new int[N];
	for (int i = 0; i < N; i++)
		vis[i] = tol[i] = 0;

	tol[start] = w[start];
	for (int i = 0; i < N; i++) {
		int min = Max, temp = 0;
		for (int j = 0; j < N; j++) {
			if (!vis[j] && dist[j] < min) {
				min = dist[j];
				temp = j;
			}
		}
		if (temp == end)break;	//已找到终点 最短路径
		vis[temp] = 1;
		for (int j = 0; j < N; j++) {
			if (!vis[j]) {
				if (dist[temp] + l[temp][j] < dist[j]) {
					dist[j] = dist[temp] + l[temp][j];
					tol[j] = tol[temp] + w[j];
					path[j] = temp;
					pre[j].clear();
					pre[j].push_back(temp);
					pathnum[j] = pathnum[temp];
				}
				else if (dist[temp] + l[temp][j] == dist[j]) {
					pathnum[j] += pathnum[temp];
					pre[j].push_back(temp);
					if (tol[j] < tol[temp] + w[j]) {
						tol[j] = tol[temp] + w[j];
						path[j] = temp;
					}
				}
			}
		}
	}
	cout << pathnum[end] << " " << tol[end];
}

1004 Counting Leaves

A family hierarchy is usually presented by a pedigree tree. Your job is to count those family members who have no child.

Input Specification:

Each input file contains one test case. Each case starts with a line containing 0<N<100, the number of nodes in a tree, and M (<N), the number of non-leaf nodes. Then M lines follow, each in the format:

ID K ID[1] ID[2] ... ID[K]

where ID is a two-digit number representing a given non-leaf node, K is the number of its children, followed by a sequence of two-digit ID's of its children. For the sake of simplicity, let us fix the root ID to be 01.

The input ends with N being 0. That case must NOT be processed.

Output Specification:

For each test case, you are supposed to count those family members who have no child for every seniority level starting from the root. The numbers must be printed in a line, separated by a space, and there must be no extra space at the end of each line.

The sample case represents a tree with only 2 nodes, where 01 is the root and 02 is its only child. Hence on the root 01 level, there is 0 leaf node; and on the next level, there is 1 leaf node. Then we should output 0 1 in a line.

Sample Input:

2 1
01 1 02

Sample Output:

0 1

本题也是很好理解,即求树的每一层叶子节点的个数,利用递归就能解决,当然不一样的数据结构选取与不一样的递归方式决定了代码的可读性与效率,我也是许久未写,手生疏了许多,采用二维矩阵来标记并不是优解,以下放上两种数据结构选取的对比,一是数组,二是vector数据结构。

【本题代码】

#include<iostream>
#include<algorithm>
using namespace std;
int string2int(string);
int bfs(int**, int, int, int,int*);
int depth = 0;
int string2int(string s)
{
	return (s[0] - '0') * 10 + s[1] - '0';
}
int main()
{
	int m, n, K;
	string ID1, ID2;
	cin >> n >> m;//结点数+非树叶结点数
	int** s = new int* [n + 1];
	int* leafNum = new int[n];
	for (int i = 0; i <= n; i++) {
		if (i < n)leafNum[i] = 0;
		s[i] = new int[n + 1];
		for (int j = 0; j <= n; j++)
			s[i][j] = 0;
	}
	for (int i = 0; i < m; i++)
	{
		cin >> ID1;
		cin >> K;
		for (int j = 0; j < K; j++)
		{
			cin >> ID2;
			s[string2int(ID1)][string2int(ID2)] = 1;
		}
	}
	bfs(s, 1, n, 1, leafNum);//root为01
	if (n == 1)cout << 1;	//0<N<100
	else cout << 0;
	for (int i = 1; i < depth; i++)
		cout  << " "<< leafNum[i];	// 打印每一行的树叶节点个数
	return 0;
}
int bfs(int** s, int root, int n,int deep,int *leafNum)
{
	int num = 0;
	int knum = 0;
	depth = max(depth, deep);
	for (int i = 1; i <= n; i++) {
		if (i != root) {
			int isChild = s[root][i];
			if (isChild) {//子节点
				if (bfs(s, i, n, deep+1, leafNum)==0)
					num++;
				//cout << i << ","<<num<<endl;
				knum++;
			}
		}
	}
	leafNum[deep] += num;
	return knum;
}

【改编自其他博主的代码】

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

const int N = 110;
vector<int> G[N];
int leaf[N] = {0};
int max_h = 1;

void DFS(int inx, int h)
{
    max_h = max(h, max_h);
    if(G[inx].size()==0)
    {
        leaf[h]++;
        return;
    }
    for(int i=0; i<G[inx].size(); i++) DFS(G[inx][i], h+1);
}
int main()
{
    int n, m, parent, child, k;
    cin>>n>>m;
    for(int i=0; i<m; i++)
    {
        cin>>parent>>k;
        for(int j=0; j<k; j++)
        {
            cin>>child;
            G[parent].push_back(child);
        }
    }

    DFS(1,1);
    cout<<leaf[1];
    for(int i=2; i<=max_h; i++)
        cout<<" "<<leaf[i];
    return 0;
}

 ok, 刚过去的一周练习便到此结束,小禾在这里祝大家新的一年心想事成,学业顺利,虎年大吉啦!(真的一晃就离《喜羊羊与灰太狼之虎虎生威》一轮之载了,到底是好久没关注何生肖年了)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值