《挑战程序设计竞赛》--初级篇习题POJ部分【2.4 - 2.6】
这次是延续上次的《挑战程序设计竞赛》初级篇,总结部分poj上的练习题,主要是2.4 ~ 2.6部分:
导航
- 2.4 加工并存储的数据结构
- 优先队列
- Sunscreen
- MooUniversity-FinancialAid
- 并查集
- Wireless-Network
- Find-them,Catch-them
- 2.5 它们其实都是‘图’
- 最短路
- Six-Degrees-of-Cowvin-Bacon
- Wormholes
- Silver-Cow-Party
- 最小生成树
- Agri-Net
- Bad-Cowtractors
- Out-of-Hay
- 2.6 数学问题的解题窍门
- 辗转相除法
- GCD&LCM-Inverse
- Dead-Fraction
- 质数
- Prime-Path
- X-factor-Chains
- Semi-prime-H-numbers
- 快速幂运算
- Pseudoprime-numbers
- Raising-Modulo-Numbers
2.4 加工并存储的数据结构
优先队列
简介: 优先队列实际上就是堆的一种实现,STL中在#include< queue >中提供了priority_queue 来操作优先队列,默认是大堆(大值优先出队);
如果想要实现小堆,priority_queue<Type, Contain, Functional > Functional 可以使用
#include< functional> 内置的greater< T > 来使用小堆(只对基本变量类型,pair…);如果优先队列中是 自定义结构,想要放入优先队列中,必须自定义比较函数:
- 可以在struct中重载 < 运算符(注意 重载 > 会出问题),自定义比较规则
- 或者声明一个结构体,内部是比较自定义结构的函数(实际上,就是自定义 Functional ),声明优先队列使用priority_queue<Type, Contain, Functional >形式
优先队列一般是使用在,容器不断变化(不断有新值进,旧值出),每次操作希望取出容器中的最值,而不希望每次需要遍历一遍容器的情况。
比如单源最短路的Dijkstra算法,每次找起点到未使用的其他点的最短距离,就可以用优先队列。习题:
Sunscreen
#include<cstdio> #include<algorithm> #include<queue> #define MAX_C 2505 #define MAX_L 2505 using namespace std; int C,L; typedef pair<int,int> Sunscreen; struct Cow{int minS,maxS; }; priority_queue<int> Q;//大堆bool cmp(const Cow& x,const Cow& y){ //牛按MAXspfif(x.maxS == y.maxS)return x.minS > y.minS;else return x.maxS > y.maxS; } bool cmp2(const Sunscreen& x,const Sunscreen& y){ //防晒霜按spf从大到小return x.first > y.first; } Sunscreen sunscreen[MAX_L]; Cow cow[MAX_C];void solve(){sort(cow,cow+C,cmp);sort(sunscreen,sunscreen+L,cmp2);int res = 0;for(int i = 0,j = 0;i < L;++i){ //对每种防晒霜 for(;j < C;++j){ //牛按MAXspf从大到小 算过的牛不要再算 所以j声明在外面 if(cow[j].maxS < sunscreen[i].first) break;Q.push(cow[j].minS); //把符合的牛的minSPF放入队列中 } while(!Q.empty()) {if(sunscreen[i].second == 0){ //这种防晒霜用完了就别取了 break;}int a = Q.top(); //取出当前符合的最大 minSPFQ.pop();if(sunscreen[i].first < a) //这种防晒霜这头牛 用不了continue;sunscreen[i].second--;res++;}}printf("%d\n",res); }int main(){scanf("%d%d",&C,&L);for(int i = 0;i < C;++i)scanf("%d%d",&cow[i].minS,&cow[i].maxS);for(int j = 0;j < L;++j){scanf("%d%d",&sunscreen[j].first,&sunscreen[j].second);}solve();return 0; }
MooUniversity-FinancialAid
输出
一个整数,是贝西能达到的最大中位数。如果没有足够的资金来接纳N犊牛,产出为-1。#include<cstdio> #include<algorithm> #include<queue> #include<functional> #define MAX_C 100005 #define MAX_N 20000 using namespace std; typedef pair<int,int> Cow; int N,C,F; Cow cow[MAX_C]; int front[MAX_C]; int behind[MAX_C]; priority_queue<int> Q; //大堆void solve() {//枚举中位数的位置,在其左右各选n/2个最小的数int ans = -1;for(int i = C - 1 - N/2; i >= N/2; i--){if(front[i]+ behind[i] + cow[i].second <= F){ans = cow[i].first; //逆序遍历拿符合的 最大中位数break;}}printf("%d\n",ans); }int main() {scanf("%d%d%d",&N,&C,&F);for(int i = 0; i < C; ++i){scanf("%d%d",&cow[i].first,&cow[i].second); // 分数, 需要资金}sort(cow,cow+C);if(N == 1){ //特判情况printf("%d\n",cow[C-1].first);return 0;}while(!Q.empty())Q.pop();int sum_F = 0;front[0] = 0;for(int i = 0; i < C; ++i) //计算front ,前i个数贪心最小解{if(i < (N - 1) / 2) //中位数的前i个数还不足{sum_F += cow[i].second;Q.push(cow[i].second);//队列存在前i个数的开销front[i+1] = sum_F;}else{if(Q.top() > cow[i].second) //队列中前i个数的开销最大值大于当前值,交换{sum_F -= Q.top() - cow[i].second;//减去减少的值Q.pop();Q.push(cow[i].second);}front[i+1] = sum_F;}}//对后面再来一次,类似sum_F = 0;while(!Q.empty())Q.pop();behind[C - 1] = 0;for(int i = C - 1; i > 0; i--){if(i >= C - (N - 1) / 2){sum_F += cow[i].second;Q.push(cow[i].second);behind[i-1] = sum_F;}else{if(cow[i].second < Q.top()){sum_F -= Q.top() - cow[i].second;Q.pop();Q.push(cow[i].second);}behind[i-1] = sum_F ;}}solve();return 0; }
并查集
简介:
并查集主要是管理元素分组情况的一种高效结构。其通常操作包括:- 查询a与b是否为同一组
- 合并a与b所在的组
那么怎么判断a和b是不是同一组的呢?对并查集的每个元素,我们都给它们分配一个父亲par,每一组看成一棵树,它们有共同的根节点。对a、b我们判断它们的根节点是否一致就知道是否为同一组了(通过父亲自下而上的访问)
为了更高效的使用并查集,我们通常会进行路径压缩 和 高度记录(避免树退化为链表):路径压缩 是指在查找一个节点的根节点时,将查询时向上经过的所有节点,全部直接与根节点相连。高度记录是指 合并操作时高度小的树连着高度大的树下,高度大的为父亲。
一般模板:
int par[MAX_N]; int rank[MAX_N]; void init(int n){for(int i = 0;i < n;++i){par[i] = i;//一开始每个节点都是自己的根,根节点的父亲为自己rank[i] = 0;} } int find(int n){ //查找根节点 + 路径压缩return par[n] == n ? n : par[n] = find(par[n]); } bool same(int x,int y){return find(x) == find(y); } void unite(int x,int y){int px = find(x),py = find(py);if(px == py) return;if(rank[px] < rank[py]){par[px] = py;}else {par[py] = px;if(rank[px] == rank[py]) rank[px]++;} }
习题:
Wireless-Network
输出
对于每个测试操作,如果两台计算机可以通信,则打印“yes”;如果不能通信,则打印“no”。思路
非常典型的并查集题目
每次维修新的电脑时,检测是否之前的电脑 有连接(距离是否小于等于d),如果有就合并,维修的时候检测是否连接即可#include<cstdio> #include<algorithm> #define MAX_N 1005 #define MAX_M 300000 using namespace std;int par[MAX_N]; int ran[MAX_N]; int N,d; typedef pair<int,int> CPT; CPT cpt[MAX_N]; int cins[MAX_M]; //输入int getD(const CPT& x,const CPT& y){return (x.first - y.first) * (x.first - y.first) + (x.second - y.second) * (x.second - y.second); // 10000 * 10000 * 2不会溢出int }void init(int n){for(int i = 0;i < n;++i){par[i] = i;ran[i] = 0;} }int find(int x){return par[x] == x ? x : par[x] = find(par[x]); }bool same(int x,int y){return find(x) == find(y); }void unite(int x,int y){int px = find(x),py = find(y);if(px == py) return;if(ran[px] < ran[py]){par[px] = py;}else{par[py] = px;if(ran[px] == ran[py]) ran[px]++;} }int main(){scanf("%d%d",&N,&d);for(int i = 1;i <= N;++i){scanf("%d%d",&cpt[i].first,&cpt[i].second);}//处理O or Schar c;int a,b;init(N+1);int repairNum = 0;while(scanf("%c",&c) != EOF){if(c == 'O'){scanf("%d",&a);getchar();//吃回车cins[repairNum++] = a;for(int i = 0;i < repairNum;++i){ //检测是否之前的电脑 有连接if(getD(cpt[a],cpt[cins[i]]) <= d * d){unite(a,cins[i]);}}}else if(c == 'S'){scanf("%d%d",&a,&b);getchar();//吃回车if(same(a,b)){printf("SUCCESS\n");}else printf("FAIL\n");}}return 0; }
Find-them,Catch-them
D [a] [b]
其中[a]和[b]是两名罪犯的人数,他们属于不同的帮派。A [A] [b]
其中[a]和[b]是两个罪犯的号码。这要求您判断 a和b 是否属于同一组。Sample Input1 //测试用例的数量5 5 //罪犯数 信息数A 1 2D 1 2A 1 2D 2 4A 1 4Sample OutputNot sure yet. In different gangs.In the same gang.
思路
并查集题目:
可以采用类似于 食物链poj-1182 的解法,并查集应该是保存所有的可能性
我们 让并查集为 2 * N,前N个表示为第一个帮派,后面N个表示在第二个帮派
那么 D x y 就等于两种情况,把所有可能性放入:unite(x,y+N) unite(x+N,y)
而 A 1 2 我就判断他们是不是同一个帮派,
判断两次 same(x,y),same(x+N,y+N) 相同为同一帮派
判断两次 same(x,y+N),same(x+N,y) 为不同帮派
剩余情况为不能确定代码
#include<cstdio> #define MAX_N 100005 int T,N,M; using namespace std; int par[MAX_N * 2]; int ran[MAX_N * 2];void init(int n){for(int i = 0;i < n;++i){par[i] = i;ran[i] = 0;} }int find(int x){return par[x] == x ? x : par[x] = find(par[x]); }bool same(int x,int y){return find(x) == find(y); }void unite(int x,int y){int px = find(x),py = find(y);if(px == py) return;if(ran[px] < ran[py]){par[px] = py;}else{par[py] = px;if(ran[px] == ran[py])ran[px]++;} }int main(){scanf("%d",&T);for(int i = 0;i < T;++i){scanf("%d%d",&N,&M);init(N * 2);for(int j = 0;j < M;++j){getchar();char c;int x,y;scanf("%c%d%d",&c,&x,&y);if(c == 'A'){// 有人说 2 1 A 1 2 应该返回In different gangs.····// 我这种思路没有确保每个帮派至少有一人,这里为了严谨性补一句// 实际我个人感觉每个帮派至少一个人,这个条件应该是由输入确保的,我们不检测输入数据带来的矛盾性···if(N == 2 && x * y == 2){printf("In different gangs.\n");}else if(same(x,y+N) || same(x+N,y)){printf("In different gangs.\n");}else if(same(x,y) || same(x+N,y+N)){printf("In the same gang.\n");}else printf("Not sure yet.\n");}else if(c == 'D'){unite(x,y+N);unite(x+N,y);}}}return 0; }
2.5 它们其实都是‘图’
最短路
简介:
最短路问题是图论问题里最常见的了,一般时求给定两个点,求从某个点到另一个点的路程开销最小的路径。
根据问题可以分为:1. 单源最短路2. 任意两点最短路
单源最短路有两个最著名的算法,一个是Bellman-Ford算法,支持带负边的图;
另一个是Dijstra算法,速度更快,但不支持带负边的图。简单介绍一下这两个算法:
1. Bellman-Ford算法:记起点S到点i的最短距离为d[i],有d[i] = min{ d[j] + e(j,i) } e(j,i) 为j到i的路权值初始时,d[s] = 0,d[i] = INF,不断使用上式来更新d数组,只要图中没有负圈,d的更新就是有限的。实际上d的更新循环应该最多是顶点数 - 1次,因为每次d的一整次更新,一定能确认某个点的最终d[i]利用这一点,可以判断图是否有负圈,如果有,在第V次循环d仍会更新。它的算法复杂度为O(V * E),其优化算法叫做SPFA```2. Dijstra算法:(没有负边) 先找出最短距离已经确定的点,从它出发更新相邻点的最短距离, 然后再找出更新后的最短距离里没用过的最小的(之前用过的不算) 重复第一步更新操作,直到所有最短距离都找到 如果每次找最短距离都是遍历查找的话,这个算法的复杂度最差有也有可能O(V * E) 但是我们可以用优先队列来优化查 ---> O(logV * E)
简单的代码实现:
1.Bellman-Fordint d[MAX_V]; struct Edge{int from,to,cost; } Edge edge[MAX_E]; int V,E; void Bellman(int start){for(int i = 0;i < V;++i) d[MAX_V] = INF;d[start] = 0;int up = 0;while(true){bool flag = false;for(int i = 0;i < E;++i){Edge e = edge[i];if(d[e.from] != INF && d[e.from] + e.cost < d[e.to]){d[e.to] = d[e.from] + e.cost;flag = true;}}if(!flag){break;}up++;if(up > n){printf("有负圈"\n);break;}} }
- Dijkstra算法
int d[MAX_V]; typedef pair<int,int> P; //cost 、to vector<P> G[MAX_V]; int V;void Dijstra(int start){for(int i = 0;i < V;++i) d[MAX_V] = INF;priority_queue<P,vector<P>, greater<P> > Q;//小堆d[start] = 0;Q.push(pair(0,start));while(!Q.empty()){P p = Q.top();Q.pop();int v = p.second,cost = p.first;if(d[v] < cost) continue;//更新邻近点for(int i = 0;i < G[v].size(); ++i){P e = G[v][i];if(d[e.second] > d[v] + e.first){d[e.second] = d[v] + e.first;Q.push(pair(d[e.second],e.second));}}} }
任意两点最短路:比较有名的是dp算法----Floyd-Warshall算法,这个算法另:
d[k+1][i][j] 只使用0-k的点,求得i->j的最短路径值 容易得到:(分经过或不经过k) d[k+1][i][j] = min{d[k][i][j],d[k][i][k] +d[k][k][j] } 使用二维数组节省空间 d[i][j] = min{[i][j],d[i][k] +d[k][j] } 注意循环的状态转移 初始化:dp[i][j]=cost[i][j] or INF实现: int d[MAX_V][MAX_V]; int N; for(int k = 1; k <= N; ++k) {for(int i = 1; i <= N; ++i)for(int j = 1; j <= N; ++j)if(d[i][j] > d[i][k] + d[k][j])d[i][j] = d[i][k] + d[k][j];//注意 i j k的顺序 }
习题:
Six-Degrees-of-Cowvin-Bacon
N (2 <= N <= 300)头奶牛对找出哪头奶牛与其他奶牛的平均距离最小感兴趣,当然不包括她自己。奶牛已经拍了M (1 <= M <= 10000)部电影,保证每一对奶牛之间都有一定的关系路径。
输入
*第1行:两个用空格分隔的整数:N和M
*行2 . .M+1:第一个整数是参与所描述电影的奶牛数量(如Mi);随后的Mi整数告诉我们哪些奶牛参演。输出
*第1行:单个整数,是任何奶牛的最小平均距离的100倍。Sample Input 4 2 3 1 2 3 2 3 4Sample Output 100
#include<cstdio> #define MAX_V 305 #define INF 1e9 using namespace std; int d[MAX_V][MAX_V]; //初始化为 i 到 j 的 权值,不存在时为INF i->i 为 0 int N,M; int cow[MAX_V];void init() {for(int i = 1; i <= N; ++i){for(int j = 1; j <= N; ++j){d[i][j] = ( i == j ? 0 : INF);}} }int main() {scanf("%d%d",&N,&M);init();for(int i = 0; i < M; ++i){int num;scanf("%d",&num);for(int j = 0; j < num; ++j){scanf("%d",&cow[j]);}for(int k = 0; k < num - 1; ++k) //画图连线{for(int m = k + 1; m < num; ++m){ d[cow[k]][cow[m]] = 1; //双向图d[cow[m]][cow[k]] = 1;}}}for(int k = 1; k <= N; ++k){for(int i = 1; i <= N; ++i)for(int j = 1; j <= N; ++j)if(d[i][j] > d[i][k] + d[k][j])d[i][j] = d[i][k] + d[k][j];//注意 i j k的顺序}int min_v = INF;for(int i = 1; i <= N; ++i){int this_v = 0;for(int j = 1; j <= N; ++j){this_v += d[i][j];}min_v = (this_v < min_v ? this_v : min_v);}printf("%d\n",min_v * 100 / (N-1) );return 0; }
Wormholes
理解题意:其实就是判断从起点走是否有可达负圈
道路是正权 双向边
虫洞是负权 单向边
找负圈····
使用SPFA(Bellman-Ford)算法,判断从s开始是否有达负圈#include<cstdio> #include<algorithm> #define MAX_N 505 #define MAX_M 2505 #define MAX_W 205 #define INF 100000000 using namespace std; struct Edge {int from,to,cost; }; Edge edges[MAX_M * 2 + MAX_W]; int d[MAX_N]; int F,N,M,W;void SPFA(){for(int i = 1;i <= N;++i){d[i] = INF;}d[1] = 0;int upNum = 0;while(true){bool update = false;for(int i = 0;i < 2 * M + W;++i){Edge e = edges[i];if(d[e.from] != INF && d[e.to] > d[e.from] + e.cost){d[e.to] = d[e.from] + e.cost;update = true;}}if(!update) break;else{upNum++;if(upNum == N ){ //若没有负圈,最短路不会经过同一个点2次 ==> 循环的更新次数最多为 N - 1次(因为每次循环,至少relax一边),多了说明有负圈printf("YES\n");return;}}}printf("NO\n"); }int main(){scanf("%d",&F);while(F--){scanf("%d%d%d",&N,&M,&W);for(int i = 0;i < 2 * M;i += 2){ //道路int f,t,c;scanf("%d%d%d",&f,&t,&c);edges[i].from = f;edges[i].to = t;edges[i].cost = c;edges[i+1].from = t;edges[i+1].to = f;edges[i+1].cost = c;}for(int j = 2 * M ;j < 2 * M + W;++j){ //虫洞int f,t,c;scanf("%d%d%d",&f,&t,&c);edges[j].from = f;edges[j].to = t;edges[j].cost = -c;}//printE();SPFA();}return 0; }
Silver-Cow-Party
思路
因为是有向图,我们可以先求目标点到所有其他点的最短路(从宴会回来),
但是由于来时的路和去时的可能不一样,从其他每个点到目标点都来一次最短路的话,可以用Floyd但是会超时O(10^9)可以换个思路,我们要的是:
目标点为起点,到其他所有点的最短路 + 其他所有点各为起点,到目标点终点de最短路#include<cstdio> #include<vector> #include<queue> #include<functional> #define MAX_N 1005 #define MAX_M 100005 #define MAX_T 105 #define INF 100000000 using namespace std; int N,M,X; //dijkstra 一般需要用到邻接表 struct Edge{int to,cost;bool operator< (const Edge &b) const //写在里面只用一个b,但是要用const和&修饰,并且外面还要const修饰;{return cost > b.cost; //最小堆} };//邻接表 vector<Edge> edge[MAX_N],rev_edge[MAX_N]; int d[MAX_N],rev_d[MAX_N]; priority_queue<Edge> Q,r_Q;void dijkstra(){fill(d+1,d+N+1,INF);fill(rev_d+1,rev_d+N+1,INF);d[X] = 0;rev_d[X] = 0;Edge e1;e1.to = X;e1.cost = 0;Q.push(e1);r_Q.push(e1);while(!Q.empty()){Edge min_n = Q.top(); Q.pop();if(d[min_n.to] < min_n.cost) continue;//遗留值不管,之前可能入队一些过往值,在之后的更新中,过往值已经不是真正的最小值了for(int i = 0;i < edge[min_n.to].size();++i){Edge e = edge[min_n.to][i];if(e.cost + d[min_n.to] < d[e.to]){d[e.to] = e.cost + d[min_n.to];Edge e1; e1.cost = d[e.to];e1.to = e.to;Q.push(e1);}}}while(!r_Q.empty()){Edge min_n = r_Q.top(); r_Q.pop();if(rev_d[min_n.to] < min_n.cost) continue;for(int i = 0;i < rev_edge[min_n.to].size();++i){Edge e = rev_edge[min_n.to][i];if(e.cost + rev_d[min_n.to] < rev_d[e.to]){rev_d[e.to] = e.cost + rev_d[min_n.to];Edge e1; e1.cost = rev_d[e.to];e1.to = e.to;r_Q.push(e1);}}}int max_z = 0;for(int i = 1;i <= N;++i){max_z = max_z > d[i] + rev_d[i] ? max_z : d[i] + rev_d[i];//最长的}printf("%d\n",max_z); }int main(){scanf("%d%d%d",&N,&M,&X);for(int i = 0;i < M;++i){int f,t,c;scanf("%d%d%d",&f,&t,&c);Edge e,r_e;e.to = t;e.cost = c;edge[f].push_back(e);//反向图r_e.to = f;r_e.cost = c;rev_edge[t].push_back(r_e);}dijkstra();return 0; }
最小生成树
简介:
MST 边上权值最小的生成树,一般用于修路问题的求解上。
算法主要有:(需要注意的是这些算法都只能获得从起点开始,能到达的连通分量的最小生成树,有的图不一定是全体点连通的)1. Prim从某个点作为初始树T出发,贪心地选取T与其他顶点间相连的最小权值边。将其点加入T中,再更新T到其他邻近点的权值,重复至所有点都在最小生成树中。其实现过程类似于Dijstra算法:
int mincost[MAX_N]; bool use[MAX_N]; typedef pair<int,int> P; //cost to priority_queue<P, vector<P>,greater<P> > Q; int cost[MAX_N][MAX_N]; #define INF 0x3f3f3f3fvoid Prim() {//初始化fill(mincost,mincost+MAX_N,INF);fill(use,use+MAX_N,false);mincost[0] = 0;//从0顶点开始int res = 0;Q.push(P(mincost[0],0));while(!Q.empty()){P p = Q.top(); Q.pop();if(use[p.second] == true) continue;use[p.second] = true;//加入该点res += p.first;for(int i = 0;i < N;++i){if(use[i] == false && mincost[i] > cost[p.second][i]){mincost[i] = cost[p.second][i];Q.push(P(mincost[i],i));}}}printf("%d\n",res); }
2. Kruskal按边的权值顺序从小到大排序,每次取最小的边,如果加入这条边,生成树中不会有圈就加入,至所有边访问完毕实现代码:(并查集代码见之前内容)
void Kruskal(){sort(edge,edge+Enum,cmp);//边从小到大init(MAX_N);//初始化并查集int res = 0;for(int i = 0; i < Enum; ++i){Edge e = edge[i];if(!same(e.from,e.to)) //这条线两点不相连{unite(e.from,e.to);res += e.cost;}}printf("%d\n",res); }
习题:
Agri-Net
农场铺网络,希望得到铺设网络路线最小的开销,使得所有农村都能连通
任何两个农场之间的距离都不会超过10万。输入
输入包括几种情况。
对于每种情况,第一行包含农场的数量N (3 <= N <= 100)。
下面的线条包含了N x N的邻接矩阵,其中每个元素表示农场到另一个农场的距离。输出
对于每种情况,输出单个整数长度,该长度是连接整个农场所需的最小光纤长度的总和。思路
就是一个典型的最小生成树问题
两种算法: Prim or Kruskal 算法#include <cstdio> #include <algorithm> #include <queue> #include <functional> #define MAX_N 105 using namespace std; int cost[MAX_N][MAX_N]; int N; struct Edge {int from,to,cost; }; bool cmp(const Edge& x,const Edge& y) {return x.cost < y.cost; } Edge edge[MAX_N * MAX_N];int par[MAX_N],ran[MAX_N]; void init(int n) {for(int i = 0; i < n; ++i){par[i] = i;ran[i] = 0;} } int find(int x) {return par[x] == x ? x : par[x] = find(par[x]); } bool same(int x,int y) {return find(x) == find(y); } void unite(int x,int y) {int px = find(x),py = find(y);if(px == py) return;if(ran[px] < ran[py]){par[px] = py;}else{par[py] = px;if(ran[px] == ran[py]) ran[px]++;} } void Kruskal() {//要获得农村的边,由题意知,所有农村都有边相连,由邻接表转化为边int Enum = 0;for(int i = 0; i < N; ++i){for(int j = 0; j < N; ++j){if(i == j) continue;edge[Enum].from = i;edge[Enum].to = j;edge[Enum++].cost = cost[i][j];}}sort(edge,edge+Enum,cmp);//边从小到大init(MAX_N);int res = 0;for(int i = 0; i < Enum; ++i){Edge e = edge[i];if(!same(e.from,e.to)) //这条线两点不相连{unite(e.from,e.to);res += e.cost;}}printf("%d\n",res); }int mincost[MAX_N]; bool use[MAX_N]; typedef pair<int,int> P; //cost to priority_queue<P, vector<P>,greater<P> > Q; #define INF 100000000 void Prim() {//初始化fill(mincost,mincost+MAX_N,INF);fill(use,use+MAX_N,false);mincost[0] = 0;//从0顶点开始int res = 0;Q.push(P(mincost[0],0));while(!Q.empty()){P p = Q.top(); Q.pop();if(use[p.second] == true) continue;use[p.second] = true;//加入该点res += p.first;for(int i = 0;i < N;++i){if(use[i] == false && mincost[i] > cost[p.second][i]){mincost[i] = cost[p.second][i];Q.push(P(mincost[i],i));}}}printf("%d\n",res); }int main() {while(scanf("%d",&N) != EOF){fill(cost[0],cost[0] + MAX_N * MAX_N,0);for(int i = 0; i < N; ++i){for(int j = 0; j < N; ++j){scanf("%d",&cost[i][j]);}}//Kruskal();Prim(); //两种都实现了,都可AC}return 0; }
Bad-Cowtractors
思路
思路有很多:
可以类比最小生成树的做法,修改为最大生成树
或者 每条边都取负,求最小生成树然后取绝对值···
注意最后判断一下是不是所有点都连通了#include<cstdio> #include<algorithm> #include<queue> #include<functional> #define MAX_N 1005 #define MAX_M 20005 #define MAX_C 100005 #define INF 100000000 using namespace std;int N,M; typedef pair<int,int> P; //cost to int mincost[MAX_N]; bool use[MAX_N]; vector<P> vetex[MAX_N]; void prim(){ //按最小生成数的思路,需要对边权值取负,最后取绝对值fill(use,use + N,false);fill(mincost,mincost+N,INF);mincost[1] = 0;int res = 0;priority_queue<P,vector<P>,greater<P> > Q;Q.push(P(0,1));while(!Q.empty()){P p = Q.top(); Q.pop();if(use[p.second] == true) continue;use[p.second] = true; //当前最小点res += p.first;for(int i = 0;i < vetex[p.second].size(); ++i){ //检测最小点的每条边P ne = vetex[p.second][i];if(use[ne.second] == false && mincost[ne.second] > ne.first){mincost[ne.second] = ne.first;Q.push(P(mincost[ne.second],ne.second) );}}}for(int i = 1;i <= N;++i){ //检测if(!use[i]){printf("-1\n");return;}}printf("%d\n",-res); }void prim2(){ //按最大生成数的思路fill(use,use + N,false);fill(mincost,mincost+N,0); //初始化为0,维护到非使用集合的最大距离mincost[1] = 0;int res = 0;priority_queue<P> Q;//da堆Q.push(P(0,1));while(!Q.empty()){P p = Q.top(); Q.pop();if(use[p.second] == true) continue;use[p.second] = true; //当前最大点res += p.first;for(int i = 0;i < vetex[p.second].size(); ++i){ //检测最大点的每条边P ne = vetex[p.second][i];if(use[ne.second] == false && mincost[ne.second] < ne.first){mincost[ne.second] = ne.first;Q.push(P(mincost[ne.second],ne.second) );}}}for(int i = 1;i <= N;++i){if(!use[i]){printf("-1\n");return;}}printf("%d\n",res); }struct Edge{int from,to,cost; }; Edge edge[MAX_M]; bool cmp(const Edge& x, const Edge& y){ //最大生成树从大到小排return x.cost > y.cost; } int par[MAX_N],ran[MAX_N]; void init(int n){for(int i = 1; i <= n;++i){par[i] = i;ran[i] = 0;} } int find(int x){return par[x] == x ? x : par[x] = find(par[x]); } bool same(int x,int y){return find(x) == find(y); } void unite(int x,int y){int px = find(x),py = find(y);if(px == py) return;if(ran[px] < ran[py])par[px] = py;else{par[py] = px;if(ran[px] == ran[py]) ran[px]++;} } void Kruskal(){ //对连通图才有效··sort(edge,edge+M,cmp);int res = 0;fill(use,use+N,false);init(N);for(int i = 0; i < M;++i){Edge e = edge[i];if(!same(e.from,e.to)){unite(e.from,e.to);res += e.cost;}}int num = 0; //若连通,只有一个根节点for(int i = 1;i <= N;++i){if(par[i] == i){if(num < 1) num++;else{printf("-1\n");return;}}}printf("%d\n",res); }int main(){scanf("%d%d",&N,&M);for(int i = 0;i < M;++i){int f,t,c;scanf("%d%d%d",&f,&t,&c);edge[i].from = f;edge[i].to = t;edge[i].cost = c;//vetex[f].push_back(P(-c,t)); //当前最小生成树需要负权//vetex[t].push_back(P(-c,f));vetex[f].push_back(P(c,t));vetex[t].push_back(P(c,f));}//prim();//prim2();Kruskal();return 0; }
Out-of-Hay
思路
没啥好说的,找最小生成树的同时,记录最大边,这里用Kruskal感觉更简单。#include<cstdio> #include<algorithm> using namespace std; #define MAX_N 2005 #define MAX_M 10005 #define INF 1200000000 int N,M; struct Edge{int from,to,cost; }; Edge edge[MAX_M]; bool cmp(const Edge& x,const Edge& y){return x.cost < y.cost; } //并查集 int par[MAX_N],ran[MAX_N]; void init(int n){for(int i = 1;i <= n;++i){par[i] = i;ran[i] = 0;} } int find(int x){return par[x] == x ? x : par[x] = find(par[x]); } bool same(int x,int y){return find(x) == find(y); } void unite(int x,int y){int px = find(x),py = find(y);if(px == py) return;if(ran[px] < ran[py]) par[px] = py;else{par[py] = px;if(ran[px] == ran[py]) ran[px]++;} } int main(){scanf("%d%d",&N,&M);for(int i = 0;i < M;++i){scanf("%d%d%d",&edge[i].from,&edge[i].to,&edge[i].cost); //双向边也只判断一次 }int res = 0;sort(edge,edge + M,cmp);init(N);for(int i = 0;i < M;++i){Edge e = edge[i];if(!same(e.from,e.to)){//printf("%d,%d,%d\n",e.from,e.to,e.cost);unite(e.from,e.to);res = res > e.cost ? res : e.cost;}}printf("%d\n",res);return 0; }
2.6 数学问题的解题窍门
辗转相除法
简介: 一般我们求取a和b的最大公约数gcd时采用的都是辗转相除法即欧几里得算法:
gcd(a,b) = gcd(b,a%b) 当b == 0时,a即是最大公约数另:扩展欧几里得算法AX1 + BY1 = gcd(A,B) 一定有整数特解其中 X1 = Y2Y1 = X2 - (A/B)*Y2 使用递归可求一组特解知道了gcd,那么求a,b的最小最小公倍数lcm,有lcm * gcd = a * b
习题:
GCD&LCM-Inverse
输出
对于每个测试用例,按升序输出a和b。如果有多个解,输出a + b最小的一对。思路
a * b = lcm * gcd;
a / gcd * b / gcd = lcm / gcd;
gcd (a/gcd , b/gcd) = 1 (a/gcd,b/gcd互质)就是把lcm / gcd分解成两个互质的因子。可以用Pollard rho分解子因子,然后再将相同的因子合并,再将因子分成两部分。
#include<iostream> #include<algorithm> #include<math.h> #include<stdio.h> #include<string.h> #include<time.h> #include<stdlib.h> typedef __int64 LL; LL a,b,sum; const int S=20;//随机算法判定次数,S越大,判错概率越小 //***************Miller_Rabin 算法进行素数测试*************** int cmp(void const *a,void const *b) {if(*(LL *)a > *(LL *)b)return 1;else return -1; } LL mult_mod(LL a,LL x,LL n)//返回(a*x) mod n,a,x,n<2^63 {a%=n;x%=n;LL ret=0;while(x){if(x&1){ret+=a;if(ret>=n)ret-=n;}a<<=1;if(a>=n)a-=n;x>>=1;}return ret; } LL pow_mod(LL a,LL x,LL n)//返回a^x mod n {if(x==1)return a%n;int bit[70],k=0;while(x){bit[k++]=(x&1?1:0);x>>=1;}LL ret=1;for(--k;k>=0;k--){ret=mult_mod(ret,ret,n);if(bit[k])ret=mult_mod(ret,a,n);}return ret; } bool judge(LL a,LL n,LL x,LL t)//以a为基,n-1=x*2^t,检验n是不是合数 费马定理 {LL ret=pow_mod(a,x,n),flag=ret;for(LL i=1;i<=t;i++){ret=mult_mod(ret,ret,n);if(ret==1&&flag!=1&&flag!=n-1)return true;flag=ret;}if(ret!=1)return true;return false; } bool Miller_Rabin(LL n) //判断是否是质数 {if(n==2||n==5||n==7||n==11)return true; //常见的直接判断if(n%2==0||n%5==0||n%7==0||n%11==0)return false;LL x=n-1,t=0;while((x&1)==0)x>>=1,t++;bool flag=true;if(t>=1&&(x&1)==1){for(int i=1;i<=S;i++){LL a=rand()%(n-1)+1;if(judge(a,n,x,t)){flag=true;break;}flag=false;}}if(flag)return false;else return true; }//*******pollard_rho(一种快速分解质因数的算法)进行质因数分解***************** LL factor[100];//质因子 int tot;//质因子个数 LL gcd(LL a,LL b) {if (a==0) return 1;if (a<0) return gcd(-a,b);while (b){LL t=a%b; a=b; b=t;}return a; } LL Pollard_rho(LL x,LL c) {LL i=1,x0=rand()%x,y=x0,k=2;while (1){i++;x0=(mult_mod(x0,x0,x)+c)%x;LL d=gcd(y-x0,x);if (d!=1 && d!=x)return d;if (y==x0) return x;if (i==k){y=x0;k+=k;}} } void find_factor(LL n) //递归进行质因数分解N {if(Miller_Rabin(n)){factor[tot++] = n;return;}LL p=n;while (p>=n) p=Pollard_rho(p,rand() % (n-1) +1);find_factor(p);find_factor(n/p); } void dfs(int k,LL ans,LL n) {if(ans>n)return ;//ans是较小的一个,控制它小于nif(k==tot){if(ans+sum/ans<a+b){a=ans,b=sum/ans;}return;}dfs(k+1,ans,n);dfs(k+1,ans*factor[k],n);return ; } int main() {LL lcm,g,temp,flag;int k,i;while(scanf("%I64d%I64d",&g,&lcm)!=-1){if(g==lcm){printf("%I64d %I64d\n",g,lcm);continue;}tot=0;sum=lcm/g;find_factor(sum);qsort(factor,tot,sizeof(factor[0]),cmp);k=0;a=1;b=sum;flag=temp=factor[0];for(i=1;i<tot;i++)//合并相同的质因子得到互质的一些因子{if(factor[i]==temp)flag*=temp;else{factor[k++]=flag;flag=temp=factor[i];}}factor[k++]=flag;tot=k;dfs(0,1,(LL)sqrt(1.0*sum));//将得到的互质因子分成两部分//if(a>b)std::swap(a,b);printf("%I64d %I64d\n",a*g,b*g);}return 0; }
Dead-Fraction
题目
复原分数,将省略小数的形式 还原为 精确的分数形式
(注意:循环的部分不一定是最后一位,有可能从小数点后面全是循环部分…)输入有几个测试用例。对于每个测试用例,都有一行形式为“0.dddd…”的输入,其中dddd是由1到9个数字组成的字符串,不全是0。最后一个大小写后面跟着一行0。输出对于每种情况,输出原始分数。Sample Input0.2...0.20...0.474612399...0Sample Output2/91/51186531/2500000
#include<iostream> #include<math.h> #include<string.h> using namespace std; int gcd(int a,int b) {if(!a)return b;return gcd(b%a,a); } int main() {char str[100]; int num,k,all,a,b,i,j,mina,minb,l;while(cin>>str&&strcmp(str,"0")){mina=minb=1000000000;for(i=2,all=0,l=0;str[i]!='.';i++) //0.123456 --> 123456{all=all*10+str[i]-'0';l++;}for(num=all,k=1,i=1;i<=l;i++) //枚举,从最后一位是一位循环节开始{num /= 10; //243333 取mk *= 10;a = all - num; //分子,除了循环节第一个部分,后面循环节都是0; 219000b = (int)pow(10.0,l-i)*(k-1); //分母,k为取9的个数,pow为取零的部分j = gcd(a,b); //化简if(b/j < minb) //取分母最小的{mina = a/j;minb = b/j;}}cout<<mina<<'/'<<minb<<endl;}return 0; }
质数
简介:
质数一般分为:质数测试、区间质数
对于n的质数检测,一般算法O(sqrt(n)),从2~sqrt(n) 进行逐个取%,有n%i == 0则不是质数区间质数对于从[1,n]判断区间内有多少个质数,更高效的做法是埃氏筛法:
思路上是:将2~n的数写下来,最小的质数从2开始,把所有在范围内的2的倍数划掉,取下一个最小的没被划掉的数是质数,是3,将3的倍数划掉,重复至所有数都进行完。对于[a,b]中有多少质数,如果a比较大,还采用埃氏筛法计算[2,b]可能不够高效:
这时候用类似的方法,用埃氏筛法把[2,sqrt(b)]的所有质数筛出来,将其倍数再[a,b]中划去,剩下的即是[a,b]中的质数个数。习题:
Prime-Path
#include<iostream> #include<cstring> #include<queue> using namespace std;const int maxn = 10000;bool isprime[maxn+1];//打表用辅助表 int prime[maxn+1]; //质数表 int dp[maxn+1];//开销表int getnext(int num,int t,int change) //改变change 获取下一个值 {if(t==0)return num/10*10+change; //个位else if(t==1)return num/100*100 + num%10 + change*10;//十位else if(t==2)return num/1000*1000 + num%100 + change*100; //百位else return num%1000 + change*1000; //千位 }int main() {//埃氏筛法打表int p = 0;for(int i = 0; i <= maxn; i++) //一开始全是质数isprime[i] = true;isprime[0] = isprime[1] = false;for(int i=2; i<=maxn; i++){if(isprime[i]){prime[p++] = i;for(int j = 2*i; j<= maxn; j+=i){isprime[j] = false;}}}int n;cin>>n;while(n--){int a,b;cin>>a>>b; //两个数memset(dp,0x3f,sizeof(dp));// 0x3f = 00111111 也就是十进制的63//0x3f3f3f3f的十进制是1061109567,是10^9级别的,而一般场合下的数据都是小于10^9的,所以它可以作为无穷大使用而不致出现数据大于无穷大的情形。dp[a] = 0;queue<int> q;q.push(a);while(!q.empty()){int cur = q.front();q.pop();for(int i=0; i<4; i++) //bfs{for(int j=0; j<10; j++){if(i==3 && j==0)continue;//千位变为0,前导零忽略int next = getnext(cur,i,j);if(isprime[next] == false || dp[next] <= dp[cur])continue;//下一个数不是质数or下一个数开销比之前好(不可能,发生这种情况只可能回溯了)dp[next] = dp[cur] + 1;q.push(next);}}}cout<<dp[b]<<endl;}return 0; }
X-factor-Chains
#include<cstdio> #include<algorithm> using namespace std; typedef long long LL; //阶乘可能溢出int int N;LL fact(int n){ //求n的阶乘return ((n == 0 || n == 1) ? 1 : n * fact(n - 1)); }void solve(int n){ int length = 0;LL b = 1;if(n == 1 || n == 2){printf("1 1\n");return;}for(int i = 2;i * i <= n;++i){ //分解质因数int num = 0;while(n % i == 0){n /= i;num++;}length += num;b *= fact(num);}if(n > 1){ //最后是一个单独的质因数length++;}printf("%d %lld\n",length,fact(length) / b); }int main(){while(scanf("%d",&N) != EOF){solve(N);}return 0; }
Semi-prime-H-numbers
An H-number(4 * k + 1,k >= 0) 分为三种: unit 1 H-primes = 只能由 1 * H-primes得到 H-composites:由两个H-number(1不算) 相乘得到 H-semi-primes: 由两个H-primes 相乘得到 输入 每一行输入包含一个≤1000,001的H-number。输入的最后一行包含0,这一行不应该被处理。输出 对于每个输入的h-number,打印一行h-number 以及[1,h-number]之间的H-semi-primes的个数,按照示例中所示格式用一个空格隔开。Sample Input 21 85 789 0Sample Output 21 0 85 5 789 62
思路
类似埃氏筛法···变化一下即可
i从5开始,每次+4
然后j从i起 有 i * j ; j+=4
若i,j都是H质数 i*j就是H-semi,不然就是H-composites;#include<cstdio> #include<algorithm> #define MAX_H 1000005 using namespace std; int H; int isHprime[MAX_H];void solve(){ //emmmm,对每个结果判断一次会超时···所以直接一次求wan得了fill(isHprime,isHprime + MAX_H,0);for(int i = 5;i <= MAX_H;i += 4){ //H-Numberfor(int j = i;j <= MAX_H;j += 4){if(i >= 1001) break;//肯定超了long long k = i * j;if(k <= MAX_H ){//printf("%lld\n",k);if(isHprime[i] == 0 && isHprime[j] == 0) //都是H-primesisHprime[k] = 1; //H-semi-primeselse isHprime[k] = -1; //H-composites}else break;}}int res = 0;for(int j = 5;j <= MAX_H;j += 4){if(isHprime[j] == 1){res++;}isHprime[j] = res;} }int main(){solve();//直接打完表while(scanf("%d",&H) != EOF){if(H == 0)break;printf("%d %d\n",H,isHprime[H]);}return 0; }
快速幂运算
简介:
一般我们求幂会使用STL中的pow(x,n)方法,但是这个方法实际上时间复杂度是o(n)
使用反复平方法可以快速得到幂:
例子:x^22 首先22 = 10110(二进制)
x^22 = x^16 + x^4 + x^2实际上我们只需要计算三次即可long long(long long x,long long n){long long res = 1;whiel(n > 0){if(n & 1) res = res * x;x = x*x;n >>= 1;}return res; }
习题:
Pseudoprime-numbers
题目
a的p次方模p为a && p为非素数为 Pseudoprime numbers输入
输入包含几个测试用例,后面是一行包含“0 0”的代码。每个测试用例由包含p和a的一行组成。
Given 2 < p ≤ 1000000000 and 1 < a < p输出
对于每个测试用例,如果p是一个Pseudoprime numbers,输出“yes”;否则输出“no”思路
先判断p是不是质数,然后在判断 a的p次方模p为a,需要使用到快速幂取模#include<cstdio> #include<algorithm> using namespace std; bool isPrime(long long N){for(int i = 2; i * i <= N; ++i){if(N % i == 0){return false;}}return N != 1; //除了1 } //快速幂 一般用LL 快速求出 a^b Mod c (注意:b是一个大数) /* 数论的一个定理: (a*b) mod c = [(a mod c)*(b mod c)] mod c 那么根据上面的定理可以推导出另一个定理: (a^b) mod c = (a mod c)^b mod c 参考博客:https://blog.csdn.net/qq_36760780/article/details/80092665 */ long long quickM(long long x,long long n,long long mod){long long res = 1;while(n > 0){if(n & 1 == 1){res = res * x % mod;//取模}x = x * x % mod; //将x平方n >>= 1;}return res; }//这题p 和 a用int会WA,我猜是测试数据里某个p超了int````` int main(){long long p,a;while(scanf("%lld%lld",&p,&a) != EOF){if(p == 0 && a == 0)break;if(isPrime(p)){printf("no\n");}else{if(quickM(a,p,p) == a)printf("yes\n");else printf("no\n");}}return 0; }
Raising-Modulo-Numbers
题目
给定一组数 Ai,Bi 和M
求 所有 Ai ^ Bi 后再相加,最后再取模M的结果#include<cstdio> using namespace std;long long quickM(long long x,long long n,long long M){long long res = 1;while(n > 0){if(n & 1) res = res * x % M;x = x * x % M;n >>= 1;}return res; }int main(){int T;scanf("%d",&T);while(T--){int M,num;scanf("%d%d",&M,&num);long long res = 0;for(int i = 0;i < num;++i){long long x,n;scanf("%lld%lld",&x,&n);res = (res + quickM(x,n,M)) % M;}printf("%lld\n",res);} }
以上就是我个人的一些总结,个人能力有限,有错误还请指出~
偷偷补一句,csdn写文章好像一篇文字太多了,会巨卡,这个总结写到后面真的挺卡的
《挑战程序设计竞赛》--初级篇习题POJ部分【2.4 - 2.6】相关推荐
- 《挑战程序设计竞赛》--初级篇习题POJ部分【动态规划】
关于基本的动态规划和经典的动态规划,在之前已经总结过了,可以温习一下: 传送门 这次是延续上次的<挑战程序设计竞赛>初级篇,总结部分poj上的练习题,主要是DP方面的练习题: 一.基础的动 ...
- 《挑战程序设计竞赛》--初级篇习题POJ部分【穷竭搜索+贪心】
最近看了<挑战程序设计竞赛>初级篇,这里总结一下部分poj上的练习题,主要涉及方面为: 穷竭搜索 and 贪心算法 具体题目: 简单导航 一.穷竭搜索 二.贪心算法 一.穷竭搜索 穷竭搜索 ...
- POJ 1150 The Last Non-zero Digit 《挑战程序设计竞赛》
为什么80%的码农都做不了架构师?>>> POJ 1150 The Last Non-zero Digit超大组合数:求超大组合数P(n, m)的最后一个非零位.4.1更加复杂 ...
- POJ 3735 Training little cats 题解 《挑战程序设计竞赛》
为什么80%的码农都做不了架构师?>>> POJ 3735 Training little cats调教猫咪:有n只饥渴的猫咪,现有一组羞耻Play,由k个操作组成,全部选自: ...
- POJ 3608 Bridge Across Islands 《挑战程序设计竞赛》
为什么80%的码农都做不了架构师?>>> POJ 3608 Bridge Across Islands跨岛大桥:在两个凸包小岛之间造桥,求最小距离?3.6与平面和空间打交道的计 ...
- POJ 3713 Transferring Sylla 题解 《挑战程序设计竞赛》
为什么80%的码农都做不了架构师?>>> POJ 3713 Transferring Sylla三连通图:判断一个无向图是否三连通?3.5借助水流解决问题的网络流最大流刷个题报 ...
- 挑战程序设计竞赛(第二章习题总结)
文章目录 搜索 Curling 2.0(POJ 3009) Meteor Shower(POJ 3669) Smallest Difference(POJ 2718) Hopscotch(POJ 30 ...
- POJ 1418 Viva Confetti 题解 《挑战程序设计竞赛》
为什么80%的码农都做不了架构师?>>> POJ 1418 Viva Confetti礼花:Confetti 是一些大小不一的彩色圆形纸片,人们在派对上.过节时便抛洒它们以示庆 ...
- 挑战程序设计竞赛(第2版)》
<挑战程序设计竞赛(第2版)> 基本信息 作者: (日)秋叶拓哉 岩田阳一 北川宜稔 译者: 巫泽俊 庄俊元 李津羽 丛书名: 图灵程序设计丛书 出版社:人民邮电出版社 ISBN:9787 ...
最新文章
- 对TD tree的使用体验及建议
- 如何解决Contacts中的多音字排序错误问题
- MySQL获取数据库每个表的行数
- oracle游标应用 sys_refcursor 和 cursor比较
- 什么是BNF EBNF 巴科斯范式及其扩展 BNF Augmented BNF
- java的迭代器类中有哪些类_java中的集合类 以及 迭代器
- 张一鸣这一条微博,阿里P8的我,竟然想了一夜
- iis php7页面空白,iis 无法显示htm页面问题解决
- 《构建之法》阅读笔记
- fatal error: absl/synchronization/mutex.h: No such file or directory
- 旧版sai笔刷_最详细的SAI笔刷设置教程,非常全面详细!
- 网络规划设计师水平考试备考资料(2.计算机网络原理)
- iOS宏定义的黑魔法 - 宏菜鸟起飞手册
- Rust学习:13.1_返回值和错误处理之panic 深入剖析
- 对抗样本之DeepFool原理coding
- java里创建一个长方形类_定义一个长方形类,定义 求周长和面积的方法实例
- python积木编程软件_积木编程软件手机版下载
- 大学生笔记本选Mac还是Windows?
- R语言实现关联规则与推荐算法(学习笔记)
- 防火墙——隧道技术类型