7-1 整数拆分
作者 谷方明
单位 吉林大学
整数拆分是一个古老又有趣的问题。请给出将正整数 n 拆分成 k 个正整数的所有不重复方案。例如,将 5 拆分成 2 个正整数的不重复方案,有如下2组:(1,4)和(2,3)。注意(1,4) 和(4,1)被视为同一方案。每种方案按递增序输出,所有方案按方案递增序输出。
输入格式:
1行,2个整数n和k,用空格分隔, 1≤k≤n≤50.
输出格式:
若干行,每行一个拆分方案,方案中的数用空格分隔。
最后一行,给出不同拆分方案的总数。
输入样例:
在这里给出一组输入。例如:
5 2
输出样例:
在这里给出相应的输出。例如:
1 4
2 3
2
#include <iostream>
#include <vector>
using namespace std;
int n, k;
int count;
vector<int> temp;
void dfs(int sum, int start)
{
if (sum > n || (sum < n && temp.size() >= k))
return;
else if (sum == n && temp.size() == k)
{
count++;
for (int i = 0; i < temp.size() - 1; i++)
{
cout << temp[i] << " ";
}
cout << temp[temp.size() - 1] << endl;
return;
}
for (int i = start; i <= n; i++)
{
temp.push_back(i);
dfs(sum + i, i);
temp.pop_back();
}
}
int main()
{
cin >> n >> k;
dfs(0, 1);
cout << count;
return 0;
}
7-2 发红包
作者 谷方明
单位 吉林大学
新年到了,公司要给员工发红包。员工们会比较获得的红包,有些员工会有钱数的要求,例如,c1的红包钱数要比c2的多。每个员工的红包钱数至少要发888元,这是一个幸运数字。
公司想满足所有员工的要求,同时也要花钱最少,请你帮助计算。
输入格式:
第1行,两个整数n和m(n<=10000,m<=20000),用空格分隔,分别代表员工数和要求数。
接下来m行,每行两个整数c1和c2,用空格分隔,表示员工c1的红包钱数要比c2多,员工的编号1~n 。
输出格式:
一个整数,表示公司发的最少钱数。如果公司不能满足所有员工的需求,输出-1.
输入样例:
在这里给出一组输入。例如:
2 1
1 2
输出样例:
在这里给出相应的输出。例如:
1777
代码长度限制16 KB
时间限制50 ms
内存限制10 MB
实现
想了好久也没想出来哪里错了唉
#include <iostream>
#include <queue>
#include <cstring>
using namespace std;
const int N = 10005;
int h[N], e[N], ne[N], idx; // 邻接表
int count[N]; // 存储每个点的入度
long long money[N]; // 存储每个员工的红包钱数
void add(int a, int b) // 添加一条边
{
e[idx] = b;
ne[idx] = h[a];
h[a] = idx++;
}
void TopoOrder(int n) // n为员工数
{
queue<int> q; // 用队列存储入度为0的点
// 将入度为0的点入队列
for (int i = 1; i <= n; i++)
if (count[i] == 0)
{
money[i] = 888;
q.push(i);
}
while (!q.empty())
{
int t = q.front();
q.pop();
for (int i = h[t]; i != -1; i = ne[i])
{
int nextNode = e[i];
count[nextNode]--;
money[nextNode] = max(money[nextNode], money[t] + 1);
if (count[nextNode] == 0)
q.push(nextNode);
}
}
}
int main()
{
idx = 0;
memset(h, -1, sizeof(h));
memset(count, 0, sizeof(count));
memset(money, 0, sizeof(money));
int n, m;
cin >> n >> m;
if (n == 0)
{
cout << 0;
exit(0);
}
if (m == 0)
{
cout << n * 888;
exit(0);
}
while (m--)
{
int c1, c2;
scanf("%d%d", &c1, &c2);
add(c1, c2);
count[c2]++;
}
TopoOrder(n);
for (int i = 1; i <= n; i++)
{
if (count[i] != 0)
{
cout << -1;
exit(0);
}
}
long long res = 0;
for (int i = 1; i <= n; i++)
{
res += money[i];
}
printf("%lld", res);
return 0;
}
标准答案
拓扑排序
#include <iostream>
#include <queue>
#include <vector>
using namespace std;
const int INF = 1e9 + 5;
const int N = 10005;
typedef long long ll;
vector<int> h[N]; // 邻接表存储
queue<int> q; // 存储入度为0的点
int n, m;
int count[N]; // 存储入度
int money[N]; // 存储每个点的金额
ll res;
void f() // 拓扑排序
{
for (int i = 1; i <= n; i++)
{
if (count[i] == 0)
q.push(i), money[i] = 888;
}
while (!q.empty())
{
int t = q.front();
q.pop();
for (int j = 0; j < h[t].size(); j++)
{
money[h[t][j]] = max(money[h[t][j]], money[t] + 1);
count[h[t][j]]--;
if (count[h[t][j]] == 0)
q.push(h[t][j]);
}
}
}
int main()
{
scanf("%d%d", &n, &m);
if (n == 0)
{
cout << 0;
exit(0);
}
if (m == 0)
{
cout << n * 888;
exit(0);
}
int x, y;
while (m--)
{
scanf("%d%d", &x, &y);
h[y].push_back(x);
count[x]++;
}
f();
for (int i = 1; i <= n; i++)
{
if (count[i] != 0)
{
cout << -1;
exit(0);
}
}
for (int i = 1; i <= n; i++)
res += money[i];
cout << res;
return 0;
}
7-3 供电
作者 谷方明
单位 吉林大学
要给N个地区供电。每个地区或者建一个供电站,或者修一条线道连接到其它有电的地区。试确定给N个地区都供上电的最小费用。
输入格式:
第1行,两个个整数 N 和 M , 用空格分隔,分别表示地区数和修线路的方案数,1≤N≤10000,0≤M≤50000。
第2行,包含N个用空格分隔的整数P[i],表示在第i个地区建一个供电站的代价,1 ≤P[i]≤ 100,000,1≤i≤N 。
接下来M行,每行3个整数a、b和c,用空格分隔,表示在地区a和b之间修一条线路的代价为c,1 ≤ c ≤ 100,000,1≤a,b≤N 。
输出格式:
一行,包含一个整数, 表示所求最小代价。
输入样例:
在这里给出一组输入。例如:
4 6
5 4 4 3
1 2 2
1 3 2
1 4 2
2 3 3
2 4 3
3 4 4
输出样例:
在这里给出相应的输出。例如:
9
核心:将站点的代价抽象为虚拟点,点的代价抽象为点与点之间的边权值,通过最小生成树的克鲁斯卡尔算法实现
代码实现
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 10005, M = 1e5 + 10;
int n, m; // n是点数,m是边数
int p[N]; // 并查集的父节点数组
struct Edge // 存储边
{
int a, b, w;
bool operator<(const Edge &W) const
{
return w < W.w;
}
} edges[M];
int find(int x) // 并查集核心操作
{
if (p[x] != x)
p[x] = find(p[x]);
return p[x];
}
int kruskal()
{
sort(edges, edges + m + n); // 将所有边权重排序
for (int i = 1; i <= n + 1; i++)
p[i] = i; // 初始化并查集
int res = 0, cnt = 0; // res存储最小生成树的权值和,cnt存储目前最小生成树点的个数
for (int i = 0; i < m + n; i++) // 遍历每一条边
{
int a = edges[i].a, b = edges[i].b, w = edges[i].w;
a = find(a), b = find(b);
if (a != b) // 如果两个连通块不连通,则将这两个连通块合并
{
p[a] = b;
res += w;
cnt++;
}
if (cnt == n )
break;
}
return res;
}
int main()
{
cin >> n >> m;
int idx = 0;
for (int i = 1; i <= n; i++)
{
int x;
cin >> x;
edges[idx].a = i;
edges[idx].b = n + 1;
edges[idx++].w = x;
}
for (int i = 1; i <= m; i++)
{
int a, b, w;
scanf("%d%d%d", &a, &b, &w);
edges[idx].a = a;
edges[idx].b = b;
edges[idx++].w = w;
}
cout << kruskal() << endl;
return 0;
}
7-4 交换次数
作者 谷方明
单位 吉林大学
序列A中有N个整数。
求对A进行冒泡排序发生的元素交换次数。
输入格式:
第一行输入整数N(2<=N<=10^6).
接下来一行N个正整数数A[i] (1≤i≤N ,A[i]<=10^6)。
输出格式:
一行,有一个整数,表示元素交换的次数。
输入样例:
在这里给出一组输入。例如:
4
2 4 3 1
输出样例:
在这里给出相应的输出。例如:
4
代码长度限制
16 KB
时间限制
400 ms
内存限制
30 MB
错误做法
#include <iostream>
using namespace std;
const int N = 1e6 + 10;
int n;
int arr[N];
int cnt;
void bubble_sort()
{
bool flag = true;
while (flag)
{
flag = false;
for (int i = 0; i < n - 1; i++)
{
if (arr[i] > arr[i + 1])
{
cnt++;
flag = true;
int t = arr[i];
arr[i] = arr[i + 1];
arr[i + 1] = t;
}
}
}
}
int main()
{
cin >> n;
for (int i = 0; i < n; i++)
{
cin >> arr[i];
}
bubble_sort();
cout << cnt;
return 0;
}
正确写法
#include <iostream>
using namespace std;
long long merge(int arr[], int l, int mid, int r, int tmp[])
{
long long count = 0;
int i = l, j = mid + 1, k = l;
while (i <= mid && j <= r)
{
if (arr[i] <= arr[j])
{
tmp[k++] = arr[i++];
}
else
{
tmp[k++] = arr[j++];
count += mid - i + 1; // 统计逆序对的数量
}
}
while (i <= mid)
tmp[k++] = arr[i++];
while (j <= r)
tmp[k++] = arr[j++];
for (int i = l; i <= r; i++)
arr[i] = tmp[i];
return count;
}
long long mergeSort(int arr[], int l, int r, int tmp[])
{
long long count = 0;
if (l < r)
{
int mid = l + (r - l) / 2;
count += mergeSort(arr, l, mid, tmp); // 统计左半部分的逆序对数量
count += mergeSort(arr, mid + 1, r, tmp); // 统计右半部分的逆序对数量
count += merge(arr, l, mid, r, tmp); // 合并两部分并统计跨越两部分的逆序对数量
}
return count;
}
long long countInversions(int arr[], int n)
{
int tmp[n];
return mergeSort(arr, 0, n - 1, tmp);
}
int main()
{
int n;
cin >> n;
int arr[n];
for (int i = 0; i < n; i++)
cin >> arr[i];
long long inversions = countInversions(arr, n);
cout << inversions << endl;
return 0;
}