->XJOI类型训练1<-
T1-基数排序
包含知识点-基数排序法
典型题目:
无
知识点讲解:
基数排序的核心在于把所需要排序的从最高位开始,把每一个数的每一位的值放到一个桶里,然后以更新过的顺序进行取出。基数排序属于稳定排序,复杂度为O (n*log(r)*m),r为所采取的基数,m为堆数。
代码实现:
#include<bits/stdc++.h>
using namespace std;
int n;
int a[1000005];
int get_len(int n)
{
int cnt=0;
while(n!=0)
{
cnt++;
n/=10;
}
return cnt;
}
void _sort(int a[],int len, int n)
{
int *tmp =new int[n];
int *count =new int[10];
int i,j,k;
int r = 1;
for(i = 1; i <= len; i++)
{
for(j = 0; j < 10; j++)
count[j] = 0;
for(j = 0; j < n; j++)
{
k = (a[j] / r) % 10;
count[k]++;
}
for(j = 1; j < 10; j++)
count[j] = count[j - 1] + count[j];
for(j = n - 1; j >= 0; j--)
{
k = (a[j] / r) % 10;
tmp[count[k] - 1] = a[j];
count[k]--;
}
for(j = 0; j < n; j++)
a[j] = tmp[j];
r= r * 10;
}
delete[]tmp;
delete[]count;
}
int main()
{
int n;
scanf("%d",&n);
int maxnum=0;
for(int i=0; i<n; i++)
{
scanf("%d",&a[i]);
maxnum=max(maxnum,a[i]);
}
int len=get_len(maxnum);
_sort(a,len,n);
for(int i=0; i<n; i++)printf("%d ",a[i]);
}
T2-哈夫曼树
包含知识点-哈夫曼树的构造
典型题目:
洛谷:P1090 合并果子
知识点讲解:
一棵二叉树,若带权路径长度达到最小,称这样的二叉树为最优二叉树,也称为哈夫曼树。哈夫曼树的特点在于权值较大的点在离根节点较近的位置。所以我们可以用一个优先队列来解决。
先把所有的节点看成根节点,把图变成一个森林,每次从这片森林中选取最小的两个节点,合并成一棵树,将两个节点的权值之和作为新生成的树的权值,并删除原来的两个节点,直到这个森林变成一棵树为止,生成的便是哈夫曼树。
代码实现:
#include<bits/stdc++.h>
using namespace std;
#define maxn 10005
typedef long long ll;
priority_queue<ll>que;
ll n;
ll a[maxn];
ll x,y,res;
int main()
{
scanf("%lld",&n);
for(int i=1;i<=n;i++)
{
scanf("%lld",&a[i]);
a[i]=-a[i];
que.push(a[i]);
}
for(int i=1;i<n;i++)
{
x=que.top();
que.pop();
y=que.top();
que.pop();
res-=x+y;
que.push(x+y);
}
printf("%lld\n",res);
}
T3-无向图的连通分量
包含知识点-并查集
典型题目:
HDU1213:How Many Tables
知识点讲解:
在一些有N个元素的集合应用问题中,我们通常是在开始时让每个元素构成一个单元素的集合,然后按一定顺序将属于同一组的元素所在的集合合并,其间要反复查找一个元素在哪个集合中,这时候并查集就会发挥很好的作用。
主要操作:
初始化——把每个点所在集合初始化为其自身。通常来说,这个步骤在每次使用该数据结构时只需要执行一次,无论何种实现方式,时间复杂度均为O(N)。
查找——查找元素所在的集合,即根节点。
合并——将两个元素所在的集合合并为一个集合。通常来说,合并之前,应先判断两个元素是否属于同一集合,这可用上面的“查找”操作实现。
并查集还可以通过路径压缩把图压缩成菊花图,能够应用于很多题目中。
代码实现:
#include<bits/stdc++.h>
using namespace std;
const int maxn=100005;
int fa[maxn],n,m;
int sum;
void init()
{
for(int i=1;i<=n;i++)
{
fa[i]=i;
}
}
int find(int x)
{
if(fa[x]==x)return x;
else return fa[x]=find(fa[x]);
}
void unite(int x,int y)
{
x=find(x);
y=find(y);
if(x==y)return;
fa[x]=y;
sum-=1;
}
int main()
{
scanf("%d%d",&n,&m);
sum=n;
init();
for(int i=1;i<=m;i++)
{
int s,t;
scanf("%d%d",&s,&t);
unite(s,t);
}
printf("%d\n",sum);//输出的是连通分量的个数
}
T4-奇数幻方
包含知识点-幻方
典型题目:
洛谷P2615:神奇的幻方
知识点讲解:
N阶幻方要求满足在N*N的格子中填入1~N*N这些数,满足每一行,每一列,每一个对角线的和都相等。
操作的方法有很多,可以爆搜,但显然是太慢了,所以有一种可以完成奇数幻方的快速方法。现在第一行的最中间填上数字1,然后每次把指针向右上移动一格,填上下一个数。如果下一个格子超出了范围,那么就加上N然后对N去模,如果这一个格子已经有数了,就在原来格子下面填上下一个数。(但是蒟蒻不知道原理,大佬可以在下面发表~)
代码实现:
#include<bits/stdc++.h>
using namespace std;
int n;
int f[1005][1005]={0};
void get_f(int i,int j,int now)
{
f[i][j]=now;
int x=i,y=j;
if(now==n*n)return ;
if(i-1>=1)
{
i-=1;
}
else
{
i=n;
}
if(j+1<=n)
{
j+=1;
}
else
{
j=1;
}
if(f[i][j]==0)get_f(i,j,now+1);
else
{
if(x+1<=n)x+=1;
else x=1;
get_f(x,y,now+1);
}
}
int main()
{
scanf("%d",&n);
memset(f,0,sizeof f);
get_f(1,(n+1)/2,1);
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
{
printf("%d ",f[i][j]);
if(j==n)printf("\n");
}
return 0;
}
T5-最近点对
包含知识点-分治
典型题目:
洛谷P2615:平面最近点对(加强版)
知识点讲解:
在二维平面上的n个点中,如何快速的找出最近的一对点,就是最近点对问题。 一种简单的想法是暴力枚举每两个点,记录最小距离,显然,时间复杂度为O(n^2)。
其实,这里用到了分治的思想。,就可以把复杂度降到O(n*log(n)*log(n))将所给平面上n个点的集合S分成两个子集S1和S2,每个子集中约有n/2个点。然后在每个子集中递归地求最接近的点对。在这里,一个关键的问题是如何实现分治法中的合并步骤,即由S1和S2的最接近点对,如何求得原集合S中的最接近点对。如果这两个点分别在S1和S2中,问题就变得复杂了。
为了使问题变得简单,首先考虑一维的情形。此时,S中的n个点退化为x轴上的n个实数x1,x2,…,xn。最接近点对即为这n个实数中相差最小的两个实数。显然可以先将点排好序,然后线性扫描就可以了。但我们为了便于推广到二维的情形,尝试用分治法解决这个问题。
假设我们用m点将S分为S1和S2两个集合,这样一来,对于所有的p(S1中的点)和q(S2中的点),有p<q。递归地在S1和S2上找出其最接近点对{p1,p2}和{q1,q2},并设d=min{ |p1-p2| , |q1-q2| }由此易知,S中最接近点对或者是{p1,p2},或者是{q1,q2},或者是某个{q3,p3}。
如果最接近点对是{q3,p3},即|p3-q3|<d,则p3和q3两者与m的距离都不超过d,且在区间(m-d,d]和(d,m+d]各有且仅有一个点。这样,就可以在线性时间内实现合并。此时,一维情形下的最近点对时间复杂度为O(n*log(n))。
在二维情形下,类似的,利用分治法。形成宽为2d的带状区间,最多可能有n个点,合并时间最坏情况下为n^2,。但是,P1和P2中的点具有以下稀疏的性质,对于P1中的任意一点,P2中的点必定落在一个d X 2d的矩形中,且最多只需检查六个点(鸽巢原理)。
这样,先将带状区间的点按y坐标排序,然后线性扫描,这样合并的时间复杂度为O(n*log(n)),几乎为线性了。
代码实现:
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
using namespace std;
const int N = 200010;
const double EPS = 1E-10, INF = 1.0 / 0.0;
inline int Sgn(double a) {
return (a < -EPS) ? (-1) : (a > EPS);
}
inline bool Smaller(double a, double b) {
return (Sgn(fabs(a - b))) ? (a < b) : (0);
}
int n;
struct Point {
double x, y;
double Dis(Point &a) {
return sqrt(pow(x - a.x, 2) + pow(y - a.y, 2));
}
bool operator < (const Point &a) const {
return (Sgn(fabs(y - a.y))) ? (y < a.y) : (x < a.x);
}
bool operator > (const Point &a) const {
return (Sgn(fabs(y - a.y))) ? (y > a.y) : (x > a.x);
}
} p[N];
double Dis(Point &a, Point &b) {
return sqrt(pow(a.x - b.x, 2) + pow(a.y - b.y, 2));
}
bool Cmpxy(Point a, Point b) {
return (Sgn(fabs(a.x - b.x))) ? (a.x < b.x) : (a.y < b.y);
}
Point temp[N];
void Merger(int l, int m, int r) {
int i = l, j = m + 1, k = l;
while (i <= m && j <= r) {
if (p[i] < p[j]) temp[k++] = p[i++];
else temp[k++] = p[j++];
}
while (i <= m) temp[k++] = p[i++];
while (j <= r) temp[k++] = p[j++];
for (int i = l; i <= r; ++i)
p[i] = temp[i];
return;
}
int t[N];
double Divide(int l, int r) {
if (l == r) return INF;
if (l + 1 == r) {
if (p[l] > p[r]) swap(p[l], p[r]);
return Dis(p[l], p[r]);
}
int m = (l + r) >> 1;
double lx = p[m].x, d1 = Divide(l, m), d2 = Divide(m + 1, r), d = min(d1, d2);
int tk = 0;
for (int i = l; i <= r; ++i)
if (Smaller(fabs(p[i].x - lx), d)) t[++tk] = i;
for (int i = 1; i <= tk; ++i)
for (int j = i + 1; j <= tk && Smaller(p[t[j]].y - p[t[i]].y, d); ++j)
d = min(d, Dis(p[t[i]], p[t[j]]));
Merger(l, m, r);
return d;
}
int main() {
scanf("%d", &n);
for (int i = 1; i <= n; ++i)
scanf("%lf%lf", &p[i].x, &p[i].y);
sort(p + 1, p + n + 1, Cmpxy);
printf("%.2lf\n", Divide(1, n));
return 0;
}
//贴了本OJ大佬的代码~
T6-神奇的供水系统
包含知识点-深度优先搜索(DFS)
典型题目:
洛谷P1562:还是N皇后
知识点讲解:
深度优先遍历图的方法是,从图中某顶点v出发:
(1)访问顶点v;
(2)依次从v的未被访问的邻接点出发,对图进行深度优先遍历;直至图中和v有路径相通的顶点都被访问;
(3)若此时图中尚有顶点未被访问,则从一个未被访问的顶点出发,重新进行深度优先遍历,直到图中所有顶点均被访问过为止。
深度优先搜索比较的节省空间,但是相应的,会花费比较多的时间。相应的,广度优先搜索会节省一些时间,但会占用很多空间。
这题由于供水系统的集水器只有在积满水之后才会向下传水,所以是一道很经典的DFS提,只需要进行一次深搜即可。
代码实现:
#include<bits/stdc++.h>
using namespace std;
const int N=1000010;
int mx[N],sum[N],t[N],S,h,t1,n,m,s,T,k,v1;
vector <int> e[N];
void dfs(int x){
for (int i=0;i<e[x].size();++i){
int to=e[x][i]; dfs(to);
mx[x]=max(mx[x],mx[to]+sum[to]*t[to]); sum[x]+=sum[to];
}
}
int main(){
scanf("%d%d%d%d",&n,&m,&s,&T);
for (int i=1;i<=m;++i){
scanf("%d%d%d%d",&S,&h,&k,&t1);
v1=min(S*h,s*T*S); mx[k]=max(mx[k],v1*t1); sum[k]+=v1;
}
for (int i=1;i<n;++i){
scanf("%d%d",&k,&t[i]); e[k].push_back(i);
} dfs(n); printf("%d",mx[n]);
}
T7-电话连线
包含知识点-最小生成树
典型题目:
洛谷P3366:【模板】最小生成树
知识点讲解:
最小生成树是最小权重生成树的简称,简单来说,就是给你一个带权无向图,然后让你通过选取这个图的几条边,使这些点连通,并且权值之和最小,这样生成的树就是最小生成树。
这题是个模板,可以用Kruskal或者Prim算法进行计算,这两种算法将在后面进行讲解。这里的代码是Kruskal算法,如果是新手的话建议使用Kruskal算法,因为比较好理解,不过最终这两种都还是要学的~
代码实现:
#include<bits/stdc++.h>
using namespace std;
#define maxn 105
int fa[105];
struct edge
{
int from;
int to;
int c;
} e[100050];
int n,tot=0;
int cost,ans=0,m=0;
bool vis[maxn][maxn];
inline void init()
{
for(int i=1; i<=n; i++)
{
fa[i]=i;
}
}
int find(int x)
{
if(fa[x]==x)
{
return x;
}
else
{
return fa[x]=find(fa[x]);
}
}
void unite(int x,int y)
{
x=find(x);
y=find(y);
// printf("unite:%d %d\n",x,y);
if(x==y)return ;
fa[x]=y;
}
bool cmp(edge a,edge b)
{
return a.c<b.c;
}
bool same(int x,int y)
{
x=find(x);
y=find(y);
// printf("same:%d %d\n",x,y);
if(x==y)return true;
return false;
}
int main()
{
// freopen("out.txt","w",stdout);
memset(vis,true,sizeof vis);
scanf("%d",&n);
init();
for(int i=1; i<=n; i++)
{
for(int j=1; j<=n; j++)
{
scanf("%d",&cost);
if(vis[i][j]==true)
{
if(cost==0)
{
// printf("%d %d\n",i,j);
unite(i,j);
vis[i][j]=false;
vis[j][i]=false;
}
else
{
tot++;
e[tot].from=i;
e[tot].to=j;
e[tot].c=cost;
vis[i][j]=false;
vis[j][i]=false;
}
}
}
}
// for(int i=1;i<=n;i++)printf("%d->%d\n",i,fa[i]);
sort(e+1,e+1+tot,cmp);
for(int i=1; i<=tot; i++)
{
edge x=e[i];
// printf("%d %d %d\n",x.from,x.to,x.c);
// printf("%d %d\n",find(x.from),find(x.to));
if(same(x.from,x.to)==false)
{
unite(x.from,x.to);
m++;
ans+=x.c;
}
}
// for(int i=1;i<=n;i++)printf("%d->%d\n",i,fa[i]);
printf("%d\n%d",m,ans);
}