总目录详见https://blog.csdn.net/mrcrack/article/details/84471041
做题原则,找不到测评地址的题不做。2018-11-28
重走长征路---OI每周刷题记录---11月23日 2013
本周共计16题
测评地址:
dp
1.免费午餐 //在线测评地址http://www.rqnoj.cn/problem/167
2.吞噬游戏 //在线测评地址http://www.rqnoj.cn/problem/156
3.合并傻子
4.数字游戏
5.过河
拓扑排序
6.奖金 //在线测评地址http://codevs.cn/problem/6126/
7.家谱树 //在线测评地址http://ybt.ssoier.cn:8088/problem_show.php?pid=1351
并查集
8.约会计划 //在线测评地址http://codevs.cn/problem/2639/
9.mty的考验 //在线测评地址http://www.rqnoj.cn/problem/343
10.愚蠢的宠物 //在线测评地址http://codevs.cn/problem/1503/
17.【模板】最近公共祖先(LCA) 在线测评地址https://www.luogu.org/problemnew/show/P3379
11.亲戚 //在线测评地址http://codevs.cn/problem/1073/
高精度
12.麦森数 //在线测评地址https://www.luogu.org/problemnew/show/P1045
快速幂
13.转圈游戏 //在线测评地址https://www.luogu.org/problemnew/show/P1965
kruskal
14.最短网络 //在线测评地址http://codevs.cn/problem/1078/
15.最优布线问题 //在线测评地址http://codevs.cn/problem/1231/
单调栈
16.音乐会的等待 //在线测评地址https://www.luogu.org/problemnew/show/P1823
题解:
dp
1.免费午餐
//PID167 / 免费午餐
//在线测评地址http://www.rqnoj.cn/problem/167
//最长下降子序列
//因n=10^5,O(n^2)过不了,只能采用O(nlogn)的算法。
//即动归+二分。
//因数量最大100000,故f[0]=110000更大的值
//样例通过,提交WA80。2019-2-6 08:55
//重新读题,发现 数量 可以 等于0
//马上 加上 0判定,即 数量 为0时,该数据被忽略
//if(a==0)continue;//马上 加上 0判定,即 数量 为0时,该数据被忽略
//提交AC。2019-2-6 09:01 数据真没想到,还有0的情况,看来是故意的。
#include <stdio.h>
int f[100100],n,ans=0;
int main(){
int i,a,left,right,mid;
scanf("%d",&n);
f[0]=110000;
for(i=1;i<=n;i++){
scanf("%d",&a);
if(a==0)continue;//马上 加上 0判定,即 数量 为0时,该数据被忽略
if(f[ans]>a)f[++ans]=a;//a比数组中的任何数都小,加入数组
else{
left=0,right=ans;
while(left+1<right){
mid=(left+right)/2;
if(f[mid]<=a)right=mid;
else left=mid;
}
f[right]=a;//用a拔高相应位置的数。
}
}
printf("%d\n",ans);
return 0;
}
2.吞噬游戏
//PID156 / 吞噬比赛
//在线测评地址http://www.rqnoj.cn/problem/156
//因N=10000,采用O(nlogn)的算法
//动归+二分。
//最长上升子序列
//样例通过,提交AC。2019-2-6 17:00
#include <stdio.h>
int n,ans=0,f[10100];
int main(){
int i,a,left,right,mid;
scanf("%d",&n);
f[0]=0;
for(i=1;i<=n;i++){
scanf("%d",&a);
if(f[ans]<a)f[++ans]=a;
else{
left=0,right=ans;
while(left+1<right){
mid=(left+right)/2;
if(f[mid]>=a)right=mid;
else left=mid;
}
f[right]=a;
}
}
printf("%d\n",ans);
return 0;
}
3.合并傻子
4.数字游戏
5.过河
拓扑排序
6.奖金
//6126 奖金
//在线测评地址http://codevs.cn/problem/6126/
//有层次关系,拓扑排序
//钱拿最少的做root,这样容易计算增加的钱。
//构成图。并且可能图与图之间不连通。
//因n=10000,m=20000采用邻接表的方式存储数据
//有一种不合理的方案
//a b
//b a
//显然,上述例子,找不到入度为0的节点,所以是不合理方案。
//n=10000,是链式数据,100+101+102+...+(100+10000-1)=(100+100+10000-1)*10000/2=5.1*10^7 int不会溢出
//有向图,工资低 指向 工资高
//用队列来处理拓扑排序
//测试了数据
//4 3
//1 2
//2 3
//3 4
//406
//测试了数据
//4 0
//400
//测试了数据
//10 5
//1 2
//2 3
//3 4
//5 6
//6 7
//1009
//均正确
//算法的时间复杂度O(m)
//继续测试
//2 2
//1 2
//2 1
//Poor Xed
//提交90分,测试点1WA,数据如下
//6 5
//1 2
//2 3
//3 4
//4 5
//5 1
//Poor Xed
//仔细想想,应该是看拓扑排序后,是否还有些点未访问到,因图与图之间可能未联通。
//拓扑排序完成,应该所有点都访问过,若有未访问的点,那么,肯定就不能拓扑排序了。
//提交AC。2019-2-6 20:10
//想都想到了,就是有些点没串起来,归根结底,还是熟练度差了些。
//翻看了https://blog.csdn.net/mrcrack/article/details/78507306之前编写的代码 //1352 【例4-13】奖金 此次编写得棒多了。
#include <stdio.h>
#include <string.h>
#define maxn 10100
int n,m,rd[maxn],cnt=0,head[maxn],vis[maxn],ans=0;
struct node{
int to,next;
}e[20100];
struct node2{
int u;
int s;
}q[maxn];
void add_edge(int u,int v){
cnt++,e[cnt].to=v,e[cnt].next=head[u],head[u]=cnt;
}
void top_sort(){
int i,h,t,u,v,b;
memset(vis,0,sizeof(vis)),h=t=1;
for(i=1;i<=n;i++)//第1轮,将入度为0的点,加入队列
if(rd[i]==0&&vis[i]==0){
vis[i]=1;
q[t].u=i,q[t].s=100,t++;
}
while(h<t){
u=q[h].u;
ans+=q[h].s;
b=head[u];
while(b){
v=e[b].to;
if(vis[v]==0){
rd[v]--;
if(rd[v]==0){
vis[v]=1;
q[t].u=v;
q[t].s=q[h].s+1;
t++;
}
}
b=e[b].next;
}
h++;
}
for(i=1;i<=n;i++)
if(vis[i]==0){//有部分图,找不到入度为0的节点。不能拓扑排序,这样更合理
ans=-1;
break;
}
}
int main(){
int i,a,b;
memset(rd,0,sizeof(rd)),memset(head,0,sizeof(head));
scanf("%d%d",&n,&m);
for(i=1;i<=m;i++){
scanf("%d%d",&a,&b);
add_edge(b,a);
rd[a]++;
}
top_sort();
if(ans==-1)printf("Poor Xed\n");
else printf("%d\n",ans);
return 0;
}
7.家谱树
//1351:【例4-12】家谱树
//在线测评地址http://ybt.ssoier.cn:8088/problem_show.php?pid=1351
//有层次关系,很明显,拓扑排序
//有向图,父 指向 子
//一直样例未能通过,查了30分钟,发现
//while(scanf("%d",&j)&&j)add_edge(i,j),rd[j]++;//此处写成while(scanf("%d",&j)&j)add_edge(i,j),rd[j]++;
//修改,提交AC。2019-2-6 21:24
//翻看https://blog.csdn.net/mrcrack/article/details/78507306之前代码 //1351 【例4-12】家谱树
//明显现在代码写得好多了。
#include <stdio.h>
#include <string.h>
int n,cnt=0,head[110],vis[110],q[110],rd[110];
struct node{
int to,next;
}e[110*110];
void add_edge(int u,int v){
cnt++,e[cnt].to=v,e[cnt].next=head[u],head[u]=cnt;
}
void top_sort(){
int u,v,b,h,t;
memset(vis,0,sizeof(vis)),h=t=1;
for(u=1;u<=n;u++)
if(rd[u]==0)//找第1批入度为0的点
q[t]=u,vis[u]=1,t++;
while(h<t){
u=q[h],b=head[u],printf("%d ",u);
while(b){
v=e[b].to;
if(vis[v]==0){
rd[v]--;
if(rd[v]==0)
vis[v]=1,q[t]=v,t++;
}
b=e[b].next;
}
h++;
}
}
int main(){
int i,j;
memset(head,0,sizeof(head)),memset(rd,0,sizeof(rd));
scanf("%d",&n);
for(i=1;i<=n;i++)
while(scanf("%d",&j)&&j)add_edge(i,j),rd[j]++;//此处写成while(scanf("%d",&j)&j)add_edge(i,j),rd[j]++;
top_sort();
return 0;
}
并查集
8.约会计划
//2639 约会计划
//在线测评地址http://codevs.cn/problem/2639/
//该题的问题是,查找mm,确定位置的过程,比较花时间
//测算了一下,一对查找次数 2008*2
//最多2008次查找,总次数2008*2*2008
//并查集log2008,总次数2008*2*2008*log2008=1.6*10^8超时无疑
//若采用二分+并查集 一对查找次数2*log2008
//最多2008次查找,总次数2008*2*log2008
//并查集log2008,总次数2008*2*log2008*log2008=1.6*10^6稳过
//试试字符串的快排,测试成功
//样例通过,提交AC。甚是高兴,越来越顺手了,这里说的是二分。2019-2-4 16:09
#include <cstdio>
#include <cstring>
#include <algorithm>
int n,m,p,f[2020];
struct node{
char name[20];
}q[2020];
using namespace std;
int cmp(const struct node &a,const struct node &b){
return strcmp(a.name,b.name)<0;//自小到大排序
}
int locate(char *name){//定位mm在数组中的位置
int left=1,right=n+1,mid;//right=n+1的目的,让left能取到1...n
while(left+1<right){
mid=(left+right)/2;
if(strcmp(q[mid].name,name)>0)right=mid;
else left=mid;
}
return left;
}
int getf(int u){
if(f[u]==u)return u;
return f[u]=getf(f[u]);
}
void merge(int u,int v){
int f1=getf(u),f2=getf(v);
if(f1!=f2)f[f2]=f1;//左靠
}
int judge(int u,int v){
int f1=getf(u),f2=getf(v);
if(f1!=f2)return 0;//不同父亲
return 1;//同一父亲
}
int main(){
int i,j;
char a[20],b[20];
scanf("%d%d%d",&n,&m,&p);
for(i=1;i<=n;i++)scanf("%s",&q[i].name);
sort(q+1,q+1+n,cmp);//自小到大排序
for(i=1;i<=n;i++)f[i]=i;
while(m--){
scanf("%s%s",a,b);
i=locate(a),j=locate(b);
merge(i,j);
}
while(p--){
scanf("%s%s",a,b);
i=locate(a),j=locate(b);
if(judge(i,j))printf("safe\n");
else printf("cc cry\n");
}
return 0;
}
9.mty的考验
//PID343 / mty的考验
//在线测评地址http://www.rqnoj.cn/problem/343
//并查集log1000=10
//m=500,算法的时间复杂度500*10=5000
//提交20分,测试点2-5WA,不敢相信
//但仔细想想,在统计时,需再找一次父亲,因合并时,有些父亲还未更新
//for(i=1;i<=n;i++)cnt[getf(i)]++;//此处写成 cnt[f[i]]++;
//修改,提交AC。2019-2-4 16:35
#include <stdio.h>
#include <string.h>
int f[1010],n,m,cnt[1010];
int getf(int u){
if(f[u]==u)return u;
return f[u]=getf(f[u]);
}
void merge(int u,int v){
int f1=getf(u),f2=getf(v);
if(f1!=f2)f[f2]=f1;
}
int main(){
int i,u,v,ans=-1;
memset(cnt,0,sizeof(cnt));
scanf("%d%d",&n,&m);
for(i=1;i<=n;i++)f[i]=i;
while(m--){
scanf("%d%d",&u,&v);
merge(u,v);
}
for(i=1;i<=n;i++)cnt[getf(i)]++;//此处写成 cnt[f[i]]++;
for(i=1;i<=n;i++)
if(ans<cnt[i])
ans=cnt[i];
printf("%d\n",ans);
return 0;
}
10.愚蠢的宠物
//1503 愚蠢的宠物
//在线测评地址http://codevs.cn/problem/1503/
//并查集,编该题,感觉在走钢丝。
//仔细想了想,该题只是应用了并查集的概念
//两只宠物的速度应该一致
//碰到有三种情况,一是其中一只已找到最后的爸爸,二是两只相遇
//特判,有可能一开始就遇到
//该题用的是并查集的思想,一开始很可能会反应不过来
//但冷静能解决问题。
//提交90分,测试点1WA。2019-2-4 19:22
//不可能啊,什么情况没考虑
//仔细想了想,还是while里的顺序写得不对,一开始是这样写的
//while(u!=v){
// u=f[u],v=f[v];
// if(f[u]==u)v=u;
// else if(f[v]==v)u=v;
//}
//修改好顺序,提交0分,测试点1WA。怎么可能。2019-2-4 19:27
//以下为90分代码
//再读题,看看题解代码,该题与自己想的有出入,其实求的是最近公共祖先,如下
//6,8的最近公共祖先是3,
//该题应该将 最早什么时候会相遇(即步数最少) 改成 最近的相遇点(路径最近相交点)
//https://blog.csdn.net/lyx_2016/article/details/52851842?utm_source=blogxgwz4此文
//2、传统的求最近公共祖先的方法。求x和y到根节点的深度,先让深的跳到和浅的一层,然后他们一起向上跳,相遇的那个点就是答案
//易于理解
//1为根节点 此句很重要
//计算深度的记忆化搜索,写得手到擒来。
//样例通过,提交AC。2019-2-4 22:29
//以下为AC代码。
#include <stdio.h>
#include <string.h>
int n,f[1000100],h[1000100];
void dfs(int u){//计算深度
if(u==1){
h[u]=1;
return;
}
if(h[u])return;//采用记忆化搜索。
dfs(f[u]);
h[u]=h[f[u]]+1;
}
int main(){
int i,u,v;
memset(h,0,sizeof(h));
scanf("%d",&n);
for(i=1;i<n;i++){
scanf("%d%d",&u,&v);
f[v]=u;//此处无法算深度,必需将父子关系建立好后,才能计算深度
}
for(i=1;i<=n;i++)dfs(i);//漏了此行
scanf("%d%d",&u,&v);
while(h[u]>h[v])u=f[u];//深的跳到和浅的一层
while(h[v]>h[u])v=f[v];//深的跳到和浅的一层
while(u!=v)u=f[u],v=f[v];//此处写成 while(u!=v)u=h[u],v=h[v];
printf("%d\n",u);
return 0;
}
//1503 愚蠢的宠物
//在线测评地址http://codevs.cn/problem/1503/
//并查集,编该题,感觉在走钢丝。
//仔细想了想,该题只是应用了并查集的概念
//两只宠物的速度应该一致
//碰到有三种情况,一是其中一只已找到最后的爸爸,二是两只相遇
//特判,有可能一开始就遇到
//该题用的是并查集的思想,一开始很可能会反应不过来
//但冷静能解决问题。
//提交90分,测试点1WA。2019-2-4 19:22
//不可能啊,什么情况没考虑
//仔细想了想,还是while里的顺序写得不对,一开始是这样写的
//while(u!=v){
// u=f[u],v=f[v];
// if(f[u]==u)v=u;
// else if(f[v]==v)u=v;
//}
//修改好顺序,提交0分,测试点1WA。怎么可能。2019-2-4 19:27
//以下为90分代码
#include <stdio.h>
int n,f[1000100];
int main(){
int i,u,v;
scanf("%d",&n);
for(i=1;i<=n;i++)f[i]=i;
for(i=1;i<n;i++){
scanf("%d%d",&u,&v);
f[v]=u;
}
scanf("%d%d",&u,&v);
while(u!=v){
if(f[u]==u){v=u;break;}
else if(f[v]==v){u=v;break;}
u=f[u],v=f[v];
}
printf("%d\n",u);
return 0;
}
17.【模板】最近公共祖先(LCA)
//P3379 【模板】最近公共祖先(LCA)
//在线测评地址https://www.luogu.org/problemnew/show/P3379
//因 10.愚蠢的宠物 //在线测评地址http://codevs.cn/problem/1503/
//开始学习最近公共祖先(LCA),进步 需要处理 不舒服 的问题
//算法,将查询的2个节点,先处理成离根节点的深度一致
//计算深度时,采用记忆化搜索。
//数据的给出,没有明确的父子关系,还是通过深搜dfs,明确父子关系
//因N=500000,邻接矩阵已经爆内存了map[500100][500100]
//因是树,边数N-1条,考虑储存采用无向边,边数2(N-1),邻接表没有问题
//建立树的父子关系 编好后 马上测试 没有问题
//开始建立树的深度关系
//N=500000,建树500000-1,建立深度关系500000log500000=500000*20=10^7勉强不超时
//查询,M=500000,每次最多500000,总查询500000*500000=2.5*10^11超时无疑
//M=10000,总查询M*N=10000*10000=10^8,基本超时
//故,上述算法,稳拿30分,最终分数落在30-70之间,最低30分,最高70分
//建立树中节点,深度关系 测试,没有问题
//样例通过,提交80分,测试点2,9TLE。怎么回事,数据太水了
//以下为80分代码。2019-2-5 09:27 建树+确定深度关系+查询
//预示着要学新的方法,为啥,老方法没有办法完全解决新问题。
//突然想到,在建树的同时,能确定深度关系,马上修改代码。
//修改代码,样例通过,提交80分,测试点2,9TLE,以下为精简后的80分代码。2019-2-5 10:07
//https://baike.baidu.com/item/%E6%9C%80%E8%BF%91%E5%85%AC%E5%85%B1%E7%A5%96%E5%85%88/8918834?fr=aladdin此文说
//离线算法 Tarjan
//利用并查集优越的时空复杂度,我们可以实现LCA问题的O(n+Q)算法,这里Q表示询问的次数。
//O(n+Q)太牛了,那么就Tarjan算法
//https://www.cnblogs.com/JVxie/p/4854719.html此文排版不咋滴,但值得一读,该文要是配上代码,就理想了。
//https://blog.csdn.net/li1615882553/article/details/79762771此文,图文并茂,值得学习
//https://blog.csdn.net/ye_xingyu/article/details/79363407看了后,发现查询的数据也可用图处理
//样例通过,提交AC。2019-2-5 18:47
//以下为AC代码。先学会用,再问为什么。
#include <stdio.h>
#include <string.h>
#define maxn 500100
struct node{
int to;
int next;
}e[maxn*2],ask[maxn*2];//无向边
int N,M,S,head[maxn],cnt=0,f[maxn],vis[maxn],h_ask[maxn],cnt_ask=0,ans[maxn];
void add_edge(int u,int v){
cnt++,e[cnt].to=v,e[cnt].next=head[u],head[u]=cnt;
}
void add_ask(int u,int v){
cnt_ask++,ask[cnt_ask].to=v,ask[cnt_ask].next=h_ask[u],h_ask[u]=cnt_ask;
}
int getf(int u){
return f[u]==u?u:f[u]=getf(f[u]);
}
void Tarjan(int father){//树上,每个点只会被访问一次。
int u,v,b,f1,f2;
u=father,vis[u]=1;
b=head[u];
while(b){
v=e[b].to;
if(vis[v]==0){//需加此句
Tarjan(v);
f1=getf(u),f2=getf(v),f[f2]=f1;
}
b=e[b].next;
}
b=h_ask[u];
while(b){
v=ask[b].to;
if(vis[v])ans[(b+1)/2]=getf(v);//b=1 (1+1)/2=1;b=2 (2+1)/2=1;
b=ask[b].next;
}
}
int main(){
int i,u,v;
memset(head,0,sizeof(head)),memset(vis,0,sizeof(vis)),memset(h_ask,0,sizeof(h_ask));
scanf("%d%d%d",&N,&M,&S);
for(i=1;i<=N;i++)f[i]=i;
for(i=1;i<N;i++){
scanf("%d%d",&u,&v);
add_edge(u,v),add_edge(v,u);//无向图
}
for(i=1;i<=M;i++){//将询问加入无向图
scanf("%d%d",&u,&v);
add_ask(u,v),add_ask(v,u);
}
Tarjan(S);
for(i=1;i<=M;i++)printf("%d\n",ans[i]);
return 0;
}
//P3379 【模板】最近公共祖先(LCA)
//在线测评地址https://www.luogu.org/problemnew/show/P3379
//因 10.愚蠢的宠物 //在线测评地址http://codevs.cn/problem/1503/
//开始学习最近公共祖先(LCA),进步 需要处理 不舒服 的问题
//算法,将查询的2个节点,先处理成离根节点的深度一致
//计算深度时,采用记忆化搜索。
//数据的给出,没有明确的父子关系,还是通过深搜dfs,明确父子关系
//因N=500000,邻接矩阵已经爆内存了map[500100][500100]
//因是树,边数N-1条,考虑储存采用无向边,边数2(N-1),邻接表没有问题
//建立树的父子关系 编好后 马上测试 没有问题
//开始建立树的深度关系
//N=500000,建树500000-1,建立深度关系500000log500000=500000*20=10^7勉强不超时
//查询,M=500000,每次最多500000,总查询500000*500000=2.5*10^11超时无疑
//M=10000,总查询M*N=10000*10000=10^8,基本超时
//故,上述算法,稳拿30分,最终分数落在30-70之间,最低30分,最高70分
//建立树中节点,深度关系 测试,没有问题
//样例通过,提交80分,测试点2,9TLE。怎么回事,数据太水了
//以下为80分代码。2019-2-5 09:27 建树+确定深度关系+查询
//预示着要学新的方法,为啥,老方法没有办法完全解决新问题。
//突然想到,在建树的同时,能确定深度关系,马上修改代码。
//修改代码,样例通过,提交80分,测试点2,9TLE,以下为精简后的80分代码。2019-2-5 10:07
#include <stdio.h>
#include <string.h>
#define maxn 500100
struct node{
int to;
int next;
}e[maxn*2];//无向边
int N,M,S,head[maxn],cnt=0,f[maxn],vis[maxn],h[maxn];
void add_edge(int u,int v){
cnt++,e[cnt].to=v,e[cnt].next=head[u],head[u]=cnt;
}
void dfs_tree(int father){//建立树的父子关系
int u,v,b;
u=father,vis[u]=1;
b=head[u];
while(b){
v=e[b].to;
if(vis[v]==0)vis[v]=1,f[v]=u,h[v]=h[u]+1,dfs_tree(v);//添加此句h[v]=h[u]+1,确定深度关系
b=e[b].next;
}
}
int main(){
int i,u,v;
memset(head,0,sizeof(head)),memset(vis,0,sizeof(vis)),memset(h,0,sizeof(h));
scanf("%d%d%d",&N,&M,&S);
for(i=1;i<N;i++){
scanf("%d%d",&u,&v);
add_edge(u,v),add_edge(v,u);//无向图
}
f[S]=0,h[S]=1;
dfs_tree(S);
while(M--){//将2节点拉到同一深度,再开始向上找同一父节点
scanf("%d%d",&u,&v);
while(h[u]>h[v])u=f[u];
while(h[v]>h[u])v=f[v];
while(u!=v)u=f[u],v=f[v];
printf("%d\n",u);
}
return 0;
}
//P3379 【模板】最近公共祖先(LCA)
//在线测评地址https://www.luogu.org/problemnew/show/P3379
//因 10.愚蠢的宠物 //在线测评地址http://codevs.cn/problem/1503/
//开始学习最近公共祖先(LCA),进步 需要处理 不舒服 的问题
//算法,将查询的2个节点,先处理成离根节点的深度一致
//计算深度时,采用记忆化搜索。
//数据的给出,没有明确的父子关系,还是通过深搜dfs,明确父子关系
//因N=500000,邻接矩阵已经爆内存了map[500100][500100]
//因是树,边数N-1条,考虑储存采用无向边,边数2(N-1),邻接表没有问题
//建立树的父子关系 编好后 马上测试 没有问题
//开始建立树的深度关系
//N=500000,建树500000-1,建立深度关系500000log500000=500000*20=10^7勉强不超时
//查询,M=500000,每次最多500000,总查询500000*500000=2.5*10^11超时无疑
//M=10000,总查询M*N=10000*10000=10^8,基本超时
//故,上述算法,稳拿30分,最终分数落在30-70之间,最低30分,最高70分
//建立树中节点,深度关系 测试,没有问题
//样例通过,提交80分,测试点2,9TLE。怎么回事,数据太水了
//以下为80分代码。2019-2-5 09:27 建树+确定深度关系+查询
#include <stdio.h>
#include <string.h>
#define maxn 500100
struct node{
int to;
int next;
}e[maxn*2];//无向边
int N,M,S,head[maxn],cnt=0,f[maxn],vis[maxn],h[maxn];
void add_edge(int u,int v){
cnt++,e[cnt].to=v,e[cnt].next=head[u],head[u]=cnt;
}
void dfs_tree(int father){//建立树的父子关系
int u,v,b;
u=father,vis[u]=1;
b=head[u];
while(b){
v=e[b].to;
if(vis[v]==0)vis[v]=1,f[v]=u,dfs_tree(v);
b=e[b].next;
}
}
void dfs_depth(int u){//建立树中节点,深度关系
if(S==u){
h[u]=1;
return;
}
if(h[u])return;//记忆化搜索
dfs_depth(f[u]);
h[u]=h[f[u]]+1;
}
int main(){
int i,u,v;
memset(head,0,sizeof(head)),memset(vis,0,sizeof(vis)),memset(h,0,sizeof(h));
scanf("%d%d%d",&N,&M,&S);
for(i=1;i<N;i++){
scanf("%d%d",&u,&v);
add_edge(u,v),add_edge(v,u);//无向图
}
f[S]=0;
dfs_tree(S);
for(i=1;i<=N;i++)dfs_depth(i);
while(M--){//将2节点拉到同一深度,再开始向上找同一父节点
scanf("%d%d",&u,&v);
while(h[u]>h[v])u=f[u];
while(h[v]>h[u])v=f[v];
while(u!=v)u=f[u],v=f[v];
printf("%d\n",u);
}
return 0;
}
11.亲戚
//1073 家族
//在线测评地址http://codevs.cn/problem/1073/
//该题就是一个裸的并查集
//样例很快通过,提交AC. 2019-1-18
#include <stdio.h>
#define maxn 5100
int n,m,p,f[maxn];
int getf(int u){
if(f[u]==u)return u;
return f[u]=getf(f[u]);
}
void merge(int u,int v){
int f1=getf(u),f2=getf(v);
if(f1!=f2)//左靠
f[f2]=f1;
}
int main(){
int i,x,y,f1,f2;
scanf("%d%d%d",&n,&m,&p);
for(i=1;i<=n;i++)f[i]=i;
while(m--){
scanf("%d%d",&x,&y);
merge(x,y);
}
while(p--){
scanf("%d%d",&x,&y);
f1=getf(x),f2=getf(y);
if(f1==f2)printf("Yes\n");
else printf("No\n");
}
return 0;
}
高精度
12.麦森数
//P1045 麦森数
//NOIP 2003 普及组 第4题 共4题
//在线测评地址https://www.luogu.org/problemnew/show/P1045
//看完题目,发现,高精度算法是少不了了
//傻算,发现P最大3100000,结果有909526位,循环3100000*909526超时是必然的
//需加上 快速幂算法 909526*log3100000 可不超时
//同时,可用P=3021377,它有909526位,来检测程序,正确与否
//快速幂+高精度乘+高精度减
//测试了样例,有些卡顿,1s能险过,样例如下
//测试了P=3021377,1s肯定过不了。
//算了好久,1小时不到吧,答案对上了,却无法提交,因为肯定超时,P=3021377如下
//超时算法如下,2019-1-21
//提交,竟然测试点全MLE,#define maxn 20000000 计算 3*20000000*4/1024/1024=229M,确实MLE,一定要小心, NOIP此题要爆0了
//马上修改,#define maxn 10000000 3*10000000*4/1024/1024=115M
//提交50分,测试点1-3,9-10 TLE。
//以下为50分代码
//需改算法,计算最后500位
//先动手改进统计位数,2^P=10^x x=Plog2 位数=(int)x+1 此处思路,讨论出来的,搞竞赛,最好2个人
//因x[i]*2的个位数为2,4,6,8,故x[1]-1即可。此处思路,讨论出来的,搞竞赛,最好2个人
//因输出500位,故截取1000位进行计算。
//P=1279,P=3021377,1s 通过,提交AC。2019-1-21
//以下为AC代码。
#include <stdio.h>
#include <string.h>
#include <math.h>
#define maxn 510
int P,a[maxn*2],ans[maxn*2],z[maxn*2];//此处写成 a[maxn] 程序一直输出不正确
int min(int a,int b){
return a<b?a:b;
}
void mul(int *x,int *y){
int i,j;
memset(z,0,sizeof(z));
x[0]=min(x[0],500),y[0]=min(y[0],500);
z[0]=x[0]+y[0];
for(i=1;i<=x[0];i++)
for(j=1;j<=y[0];j++)
z[i+j-1]+=x[i]*y[j];
for(i=1;i<z[0];i++){//进位处理
z[i+1]+=z[i]/10;
z[i]%=10;
}
i=z[0];
while(z[i]==0)i--;//去除前导0
z[0]=i;
memcpy(y,z,sizeof(z));
}
void quick_power(int n){
while(n){
if(n%2==1)mul(a,ans);
mul(a,a);
n/=2;
}
}
int main(){
int i,cnt=0;
scanf("%d",&P);
memset(ans,0,sizeof(ans)),ans[0]=1,ans[1]=1;
memset(a,0,sizeof(a)),a[0]=1,a[1]=2;
quick_power(P);
ans[1]-=1;
printf("%d\n",(int)(P*log10(2))+1);//打印位数
ans[0]=min(ans[0],500);
for(i=500;i>ans[0];i--){
cnt++;
printf("0");
if(cnt%50==0)printf("\n");
}
for(i=ans[0];i>=1;i--){
cnt++;
printf("%d",ans[i]);
if(cnt%50==0)printf("\n");
}
return 0;
}
//P1045 麦森数
//在线测评地址https://www.luogu.org/problemnew/show/P1045
//NOIP 2003 普及组 第4题 共4题
//看完题目,发现,高精度算法是少不了了
//傻算,发现P最大3100000,结果有909526位,循环3100000*909526超时是必然的
//需加上 快速幂算法 909526*log3100000 可不超时
//同时,可用P=3021377,它有909526位,来检测程序,正确与否
//快速幂+高精度乘+高精度减
//测试了样例,有些卡顿,1s能险过,样例如下
//测试了P=3021377,1s肯定过不了。
//算了好久,1小时不到吧,答案对上了,却无法提交,因为肯定超时,P=3021377如下
//超时算法如下,2019-1-21
//提交,竟然测试点全MLE,#define maxn 20000000 计算 3*20000000*4/1024/1024=229M,确实MLE,一定要小心, NOIP此题要爆0了
//马上修改,#define maxn 10000000 3*10000000*4/1024/1024=115M
//提交50分,测试点1-3,9-10 TLE。
//以下为50分代码 2019-1-21
#include <stdio.h>
#include <string.h>
#define maxn 10000000
int P,a[maxn],ans[maxn],z[maxn];
void mul(int *x,int *y){
int i,j;
memset(z,0,sizeof(z));
z[0]=x[0]+y[0];
for(i=1;i<=x[0];i++)
for(j=1;j<=y[0];j++)
z[i+j-1]+=x[i]*y[j];
for(i=1;i<z[0];i++){//进位处理
z[i+1]+=z[i]/10;
z[i]%=10;
}
i=z[0];
while(z[i]==0)i--;//去除前导0
z[0]=i;
memcpy(y,z,sizeof(z));
}
void quick_power(int n){
while(n){
if(n%2==1)mul(a,ans);
mul(a,a);
n/=2;
}
}
void sub(int *x,int b){//高精度减
int i;
x[1]-=b;
for(i=1;i<x[0];i++)//退位处理
if(x[i]<0){
x[i+1]-=1;
x[i]+=10;
}
i=x[0];
while(x[i]==0)i--;
x[0]=i;
}
int main(){
int i,cnt=0;
scanf("%d",&P);
memset(ans,0,sizeof(ans)),ans[0]=1,ans[1]=1;
memset(a,0,sizeof(a)),a[0]=1,a[1]=2;
quick_power(P);
sub(ans,1);
printf("%d\n",ans[0]);
if(ans[0]<500){
for(i=500;i>ans[0];i--){
cnt++;
printf("0");
if(cnt%50==0)printf("\n");
}
for(i=ans[0];i>=1;i--){
cnt++;
printf("%d",ans[i]);
if(cnt%50==0)printf("\n");
}
}else{
for(i=500;i>=1;i--){
cnt++;
printf("%d",ans[i]);
if(cnt%50==0)printf("\n");
}
}
return 0;
}
//P1045 麦森数
//NOIP 2003 普及组 第4题 共4题
//在线测评地址https://www.luogu.org/problemnew/show/P1045
//看完题目,发现,高精度算法是少不了了
//傻算,发现P最大3100000,结果有909526位,循环3100000*909526超时是必然的
//需加上 快速幂算法 909526*log3100000 可不超时
//同时,可用P=3021377,它有909526位,来检测程序,正确与否
//快速幂+高精度乘+高精度减
//测试了样例,有些卡顿,1s能险过,样例如下
//测试了P=3021377,1s肯定过不了。
//算了好久,1小时不到吧,答案对上了,却无法提交,因为肯定超时,P=3021377如下
//超时算法如下,2019-1-21
//提交,竟然测试点全MLE,#define maxn 20000000 计算 3*20000000*4/1024/1024=229M,确实MLE,一定要小心, NOIP此题要爆0了
//以下为MLE算法
#include <stdio.h>
#include <string.h>
#define maxn 20000000
int P,a[maxn],ans[maxn],z[maxn];
void mul(int *x,int *y){
int i,j;
memset(z,0,sizeof(z));
z[0]=x[0]+y[0];
for(i=1;i<=x[0];i++)
for(j=1;j<=y[0];j++)
z[i+j-1]+=x[i]*y[j];
for(i=1;i<z[0];i++){//进位处理
z[i+1]+=z[i]/10;
z[i]%=10;
}
i=z[0];
while(z[i]==0)i--;//去除前导0
z[0]=i;
memcpy(y,z,sizeof(z));
}
void quick_power(int n){
while(n){
if(n%2==1)mul(a,ans);
mul(a,a);
n/=2;
}
}
void sub(int *x,int b){//高精度减
int i;
x[1]-=b;
for(i=1;i<x[0];i++)//退位处理
if(x[i]<0){
x[i+1]-=1;
x[i]+=10;
}
i=x[0];
while(x[i]==0)i--;
x[0]=i;
}
int main(){
int i,cnt=0;
scanf("%d",&P);
memset(ans,0,sizeof(ans)),ans[0]=1,ans[1]=1;
memset(a,0,sizeof(a)),a[0]=1,a[1]=2;
quick_power(P);
sub(ans,1);
printf("%d\n",ans[0]);
if(ans[0]<500){
for(i=500;i>ans[0];i--){
cnt++;
printf("0");
if(cnt%50==0)printf("\n");
}
for(i=ans[0];i>=1;i--){
cnt++;
printf("%d",ans[i]);
if(cnt%50==0)printf("\n");
}
}else{
for(i=500;i>=1;i--){
cnt++;
printf("%d",ans[i]);
if(cnt%50==0)printf("\n");
}
}
return 0;
}
快速幂
13.转圈游戏
//P1965 转圈游戏
//NOIP 2013 提高组 day1 第1题 day1 共3题
//在线测评地址https://www.luogu.org/problemnew/show/P1965
//快速幂
//int 容易溢出,不计算了,直接上long long
//(10^5)^2=10^10,需要long long
//样例通过,提交,有些紧张,还是要多测试几组数据
//提交AC。2019-2-4 22:59
#include <stdio.h>
#define LL long long
int n,m,k,x;
LL quick_pow(int x){
LL ans=1,a=10;
while(x){
if(x&1)ans=(ans*a)%n;
a=(a*a)%n;
x>>=1;
}
return ans;
}
int main(){
scanf("%d%d%d%d",&n,&m,&k,&x);
x=(x+(m*quick_pow(k))%n)%n;
printf("%d\n",x);
return 0;
}
kruskal
14.最短网络
//1078 最小生成树
//在线测评地址http://codevs.cn/problem/1078/
//采用Kruskal算法,理由,特别好写 快排+并查集
//总长度最多10^5*10^2=10^7 int不会溢出
//样例通过,提交AC。2019-2-6 16:00
#include <cstdio>
#include <algorithm>
using namespace std;
int n,map[110][110],k=0,f[110];
struct node{
int x,y,len;
}q[110*110/2];
int cmp(const struct node &a,const struct node &b){
return a.len<b.len;
}
int getf(int u){
return f[u]==u?u:f[u]=getf(f[u]);
}
int merge(int u,int v){
int f1=getf(u),f2=getf(v);
if(f1!=f2){//不同爸爸
f[f2]=f1;
return 1;
}
return 0;//同一个爸爸
}
int main(){
int i,j,ans=0,cnt=1;
scanf("%d",&n);
for(i=1;i<=n;i++)f[i]=i;
for(i=1;i<=n;i++)
for(j=1;j<=n;j++)
scanf("%d",&map[i][j]);
for(i=1;i<=n;i++)
for(j=i+1;j<=n;j++)
k++,q[k].x=i,q[k].y=j,q[k].len=map[i][j];
sort(q+1,q+1+k,cmp);
for(i=1;i<=k;i++){
if(merge(q[i].x,q[i].y))cnt++,ans+=q[i].len;
if(cnt==n)break;
}
printf("%d\n",ans);
return 0;
}
15.最优布线问题
//1231 最优布线问题
//在线测评地址http://codevs.cn/problem/1231/
//重边不要紧,一个快排之后,小的再前,大的在后
//别忘了,还有并查集,同样可以继续消除重边的影响。
//故,该题采用Kruskal算法,裸的。
//最后,还是n台的最小生成树问题。
//样例通过,提交AC。2019-2-6 16:23
#include <cstdio>
#include <algorithm>
#define LL long long
using namespace std;
int n,m,f[100100];
LL ans=0;
struct node{
int x,y,len;
}q[100100];
int cmp(const struct node &a,const struct node &b){
return a.len<b.len;
}
int getf(int u){
return f[u]==u?u:f[u]=getf(f[u]);
}
int merge(int u,int v){
int f1=getf(u),f2=getf(v);
if(f1!=f2){
f[f2]=f1;
return 1;
}
return 0;
}
int main(){
int i,x,y,len,cnt=1;
scanf("%d%d",&n,&m);
for(i=1;i<=n;i++)f[i]=i;
for(i=1;i<=m;i++){
scanf("%d%d%d",&x,&y,&len);
q[i].x=x,q[i].y=y,q[i].len=len;
}
sort(q+1,q+1+m,cmp);
for(i=1;i<=m;i++){
if(merge(q[i].x,q[i].y))ans+=q[i].len,cnt++;
if(cnt==n)break;
}
printf("%lld\n",ans);
return 0;
}
单调栈
16.音乐会的等待
//P1823 [COI2007] Patrik 音乐会的等待
//在线测评地址https://www.luogu.org/problemnew/show/P1823
//因N=500000,算法的时间复杂度要么O(n),要么O(nlogn)
//单调栈
//测试数据有点多,有23组,
//样例通过,提交0分,
//断定是int溢出
//极限情况,ans值为
//1+2+3+...+499999=(1+499999)*499999/2=1.25*10^11只能用long long
//修改,提交8分,测试点1-20,23WA.2019-2-3 22:31
//https://blog.csdn.net/qq_35786326/article/details/81734888此文代码写得不错
//之间没有人比A或B高 此句理解了好长时间 5 4 3 2 插入3,能被插入的3看见的是 2,3,4
//修改,提交80分,测试点21-23TLE。2019-2-3 23:07
//还算满意,以下为80分代码。
//参考了此文的写法http://hzwer.com/445.html
//考虑了特判,栈中元素全是a,样例通过,提交AC。2019-2-3 23:47
//二分,编写时,关键要清楚自己要的数据出现在left,还是right。注意特判情况。
#include <stdio.h>
#include <string.h>
#define LL long long
int n,stack[500100],top=0;//单调栈,自栈底到栈顶,逐渐减小
int main(){
int a,left,right,mid;
LL ans=0;
scanf("%d",&n);
scanf("%d",&a);
stack[++top]=a,n--;//第1个元素
while(n--){
scanf("%d",&a);//此过程中,ans增加的值,是能看见a向左能看见的人数
if(top>0&&stack[top]>a)stack[++top]=a,ans++;
else{
left=0,right=top;
while(left+1<right){
mid=(left+right)/2;
if(stack[mid]<=a)right=mid;
else left=mid;
}
if(left==0)ans+=top-left;//特判,栈中元素全是a//此处写成 ans+=top-right+1;
else ans+=top-left+1;
while(top>0&&stack[top]<a)top--;
stack[++top]=a;
}
}
printf("%lld\n",ans);
return 0;
}
//P1823 [COI2007] Patrik 音乐会的等待
//在线测评地址https://www.luogu.org/problemnew/show/P1823
//因N=500000,算法的时间复杂度要么O(n),要么O(nlogn)
//单调栈
//测试数据有点多,有23组,
//样例通过,提交0分,
//断定是int溢出
//极限情况,ans值为
//1+2+3+...+499999=(1+499999)*499999/2=1.25*10^11只能用long long
//修改,提交8分,测试点1-20,23WA.2019-2-3 22:31
//https://blog.csdn.net/qq_35786326/article/details/81734888此文代码写得不错
//之间没有人比A或B高 此句理解了好长时间 5 4 3 2 插入3,能被插入的3看见的是 2,3,4
//修改,提交80分,测试点21-23TLE。2019-2-3 23:07
//还算满意,以下为80分代码。
#include <stdio.h>
#include <string.h>
#define LL long long
int n,stack[500100],top=0;//单调栈,自栈底到栈顶,逐渐减小
int main(){
int i,a,t;
LL ans=0;
scanf("%d",&n);
while(n--){
t=1;
scanf("%d",&a);//此过程中,ans增加的值,是能看见a向左能看见的人数
while(top>0&&stack[top]<=a){
if(stack[top]==a)t++;
ans++;
top--;//被a挡住的弹出栈,或是与a相等的先弹出栈
}
if(top>0)ans++;//栈顶元素大于a
for(i=1;i<=t;i++)stack[++top]=a;//a必需放入栈
}
printf("%lld\n",ans);
return 0;
}