- [P2814 家谱](https://www.luogu.com.cn/problem/P2814)
题解:这题很容易让人想到利用并查集来做,所以我这里是将名字借助map转成了对应的序号,从而合并、查询
#include<bits/stdc++.h>
using namespace std;
map<string,int>mp;
int fa[50100],nowFa;
int findd(int x)
{
if(x==fa[x])return x;
else
{
fa[x]=findd(fa[x]);
return fa[x];
}
}
//合并顺序要注意,只能是儿子合并到父亲
void unionn(int x,int y)
{
//x是儿子,y是父亲
int dx=findd(x),dy=findd(y);
if(dx==dy)return;
else
{
fa[dx]=dy;
return;
}
}
string findFatherName(int x)
{
for(auto t:mp)
{
if(t.second==x)
{
return t.first;
}
}
return NULL;
}
int main()
{
char ch;
cin>>ch;
int index=1;
while(ch!='$')
{
string name;
cin>>name;
if(ch=='#')
{
if(mp.count(name))
{
nowFa=findd(mp[name]);
}
else
{
mp[name]=index;
fa[index]=index;
nowFa=index;
index++;
}
}
else if(ch=='+')
{
if(mp.count(name))
{
unionn(mp[name],nowFa);
}
else
{
mp[name]=index;
fa[index]=index;
unionn(index,nowFa);
index++;
}
}
else if(ch=='?')
{
int father=findd(mp[name]);
//cout<<"父亲序号:"<<father<<endl;
cout<<name<<" "<<findFatherName(father)<<endl;
}
cin>>ch;
}
return 0;
}
我后面看到一篇题解:用stl的map把他和父亲直接连起来,不要转成序号再做要简单很多:
#include<bits/stdc++.h>
using namespace std;
map<string,string>p;
string findd(string x)
{
if(x!=p[x])p[x]=findd(p[x]);
return p[x];
}
string s,nowFa;
int main()
{
char ch;
cin>>ch;
while(ch!='$')
{
cin>>s;
if(ch=='#')
{
nowFa=s;
if(p[s]=="") p[s]=s;
}
else if(ch=='+')p[s]=nowFa;
else cout<<s<<' '<<findd(s)<<endl;
cin>>ch;
}
return 0;
}
- [P3958 奶酪](https://www.luogu.com.cn/problem/P3958)
题解:
1.搜索算法—深度优先
其实深度优先应该是最快的。
首先,我们找出所有可以从下表面进入的球,然后深度优先搜一遍。一旦遇到一个点最高处高度z+r≥h,就表示可以到上表面,退出。因为每个洞最多访问一次(只需要求是否能到达上表面,而不是最短步数),然后决定下一步的时候还需要O(n)的时间。所以总复杂度时O(n^2)。
实际上,往往不需要访问所有的洞就可以判断“Yes”,大多数情况下只有“No”的情况要访问全部。因此很少达到O(n^2)的最高复杂度。
2.搜索算法—广度优先
同上思想,但是初始时不是对于每个点跑一遍,而是把所有与下表面接触的洞直接加入广搜队列,然后搜索。复杂度仍然是O(n^2)。细节同上,但是往往跑的量比DFS更大。
3.并查集算法
我们可以为每一个球都建立一个结点,然后用O(n^2)的时间完成并查集处理,最后查询 与下表面接触的球体中 是否有和 与上表面接触的球体 在同一个集合中 的球体(有些拗口,理解就好)。然后输出“Yes”或“No”。这个算法应该常数略大,因为总是要跑完所有n^2次连接。
dfs做法如下:
#include<bits/stdc++.h>
using namespace std;
int t,n,flag;
long long h,r;
int book[1001];
struct Node
{
double x,y,z;
} node[1001];
double getDistance(double x1,double y1,double z1,double x2,double y2,double z2)
{
double distance=sqrt((x2-x1)*(x2-x1)+(y2-y1)*(y2-y1)+(z2-z1)*(z2-z1));
return distance;
}
void dfs(int num)
{
if(node[num].z+r>=h)
{
flag=1;
return;
}
if(flag)return;
book[num]=1;
for(int i=1; i<=n; i++)
{
if(!book[i]&&getDistance(node[num].x,node[num].y,node[num].z,node[i].x,node[i].y,node[i].z)<=2*r)
{
dfs(i);
}
}
}
int main()
{
cin>>t;
while(t--)
{
cin>>n>>h>>r;
memset(book,0,sizeof(book));
for(int i=1; i<=n; i++)
{
cin>>node[i].x>>node[i].y>>node[i].z;
}
flag=0;
for(int i=1; i<=n; i++)
{
if(node[i].z<=r&&!flag)
{
dfs(i);
}
}
if(flag)
{
cout<<"Yes"<<endl;
}
else
{
cout<<"No"<<endl;
}
}
return 0;
}
- [P2330 繁忙的都市](https://www.luogu.com.cn/problem/P2330)
题解:这题就是kruskal算法的板子,不多说了
#include<bits/stdc++.h>
using namespace std;
int n,m,flag,maxx,num;
int fa[310];
struct Road
{
int u,v,c;
} road[100100];
bool compare(struct Road x,struct Road y)
{
return x.c<y.c;
}
int findd(int x)
{
if(x==fa[x])return x;
else
{
fa[x]=findd(fa[x]);
return fa[x];
}
}
void unionn(int x,int y)
{
int dx=findd(x),dy=findd(y);
if(dx==dy)return;
else
{
fa[dx]=dy;
return;
}
}
int main()
{
cin>>n>>m;
flag=n;
for(int i=1; i<=n; i++)
{
fa[i]=i;
}
for(int i=1; i<=m; i++)
{
cin>>road[i].u>>road[i].v>>road[i].c;
}
sort(road+1,road+m+1,compare);
for(int i=1; i<=m; i++)
{
if(flag!=1)
{
if(findd(road[i].u)!=findd(road[i].v))
{
unionn(road[i].u,road[i].v);
flag--;
num++;
maxx=max(maxx,road[i].c);
}
}
else break;
}
cout<<num<<" "<<maxx;
return 0;
}
- [P1340 兽径管理](https://www.luogu.com.cn/problem/P1340)
题目大意: 加m次边,每一次加边让你求最小生成树
如果每加一次边就跑一次 Kruskal ,时间复杂度会非常高,所以我们要考虑节约时间的方法。
显然从第一周跑到最后一周太耗时间,于是我们就会想到从最后一周开始,逆序跑kruskal
这样的好处在于,一旦发现某一周不能构成最小生成树,那么那周之前也不可能构成最小生成树,于是我们可以少跑很多次kruskal
因为只能用那一周及之前的兽径建树,而在快排的时候边会被打乱,所以我们在结构体存边时要加一个参数,记录该边是第几周的兽径
剩下的就是kruskal的基本操作了
#include<bits/stdc++.h>
using namespace std;
int n,w;
struct Edge
{
int start;
int endd;
int value;
int time;
} edge[6001];
int fa[301],ans[6001];
bool compare(struct Edge x,struct Edge y)
{
return x.value<y.value;
}
int findd(int x)
{
if(fa[x]==x)return x;
else
{
fa[x]=findd(fa[x]);
return fa[x];
}
}
int kruskal(int time)//每次kruskal时对于并查集用到的fa[]数组都要先初始化一次
{
int sum=0,num=0;
for(int i=1; i<=n; i++)fa[i]=i;
for(int i=1; i<=w; i++)
{
if(num==n-1)break;
if(edge[i].time<=time)
{
int x=findd(edge[i].start),y=findd(edge[i].endd);
if(x!=y)
{
fa[x]=y;
sum+=edge[i].value;
num++;
}
}
}
if(num==n-1)return sum;
else return -1;
}
int main()
{
cin>>n>>w;
for(int i=1; i<=w; i++)
{
cin>>edge[i].start>>edge[i].endd>>edge[i].value;
edge[i].time=i;
}
sort(edge+1,edge+1+w,compare);
//逆序克鲁斯卡尔
for(int i=w; i>=1; i--)
{
ans[i]=kruskal(i);
if(ans[i]==-1)
{
for(int j=1; j<i; j++)ans[j]=-1;
break;
}
}
for(int i=1; i<=w; i++)cout<<ans[i]<<endl;
return 0;
}
- [Mzc和体委的争夺战](https://www.luogu.com.cn/problem/P2299)
题解:堆优化的迪杰斯特拉算法的板子,需要注意的是因为是无向边,所以记录边的数组需要开成两倍
#include<bits/stdc++.h>
using namespace std;
#define inf 0x3f3f3f3f
int n,m;
struct Road
{
int to,value,next;
} road[410000];//双向边,数组应该开两倍
int book[2510],head[2510],cnt;
int dis[2510];
struct Node
{
int index;
int distance;
bool operator < (const Node &x)const
{
return distance>x.distance;
}
};
priority_queue<Node>q;
void add(int a,int b,int c)
{
cnt++;
road[cnt].to=b;
road[cnt].value=c;
road[cnt].next=head[a];
head[a]=cnt;
}
void dij()
{
q.push({1,0});
while(!q.empty())
{
Node t=q.top();
q.pop();
int index=t.index;
if(book[index])continue;
book[index]=1;
for(int i=head[index]; i!=-1; i=road[i].next)
{
int a=index,b=road[i].to,value=road[i].value;
if(dis[b]>dis[a]+value)
{
dis[b]=dis[a]+value;
q.push({b,dis[b]});
}
}
}
}
int main()
{
cin>>n>>m;
for(int i=1;i<=n;i++){
head[i]=-1;
dis[i]=inf;
}
dis[1]=0;
for(int i=1; i<=m; i++)
{
int a,b,c;
cin>>a>>b>>c;
add(a,b,c);
add(b,a,c);
}
dij();
cout<<dis[n];
return 0;
}
- [数列分段 Section II](https://www.luogu.com.cn/problem/P1182)
题解:二分答案的例题,关于二分答案不懂的可以看看 二分-答案(板子)_小白_学编程的博客
需要注意的是这里答案最小可能是数列中的最大值(即left),最大可能是所有数的和(即right)
#include<bits/stdc++.h>
using namespace std;
long long rightt=1e9+1,leftt;
long long vis[100010];
int n,m,ans;
bool checkMid(long long x){
long long sum=0;
int num=0;
for(int i=1;i<=n-1;i++){
sum+=vis[i];
if(sum+vis[i+1]>x){
num++;
sum=0;
}
}
if(num+1<=m)return true;//这里注意是num+1
else return false;
}
int main()
{
cin>>n>>m;
for(int i=1;i<=n;i++){
cin>>vis[i];
leftt=max(leftt,vis[i]);
}
while(leftt<=rightt){
long long mid=(leftt+rightt)/2;
if(checkMid(mid)){
ans=mid;
rightt=mid-1;
}else{
leftt=mid+1;
}
}
cout<<ans;
return 0;
}
- [P1873 EKO / 砍树](https://www.luogu.com.cn/problem/P1873)
题解:二分答案的例题
#include<bits/stdc++.h>
using namespace std;
vector<long long>vc;
long long l,r;
long long n,m,maxx,ans;
bool checkMid(long long x)
{
long long nums=0;
for(int i=0; i<n; i++)
{
if(vc[i]>x){
nums+=vc[i]-x;
}
}
if(nums>=m)return true;
else return false;
}
int main()
{
cin>>n>>m;
for(int i=0; i<n; i++)
{
long long number;
cin>>number;
vc.push_back(number);
maxx=max(maxx,number);
}
r=maxx;
while(l<=r)
{
long long mid=(r+l)/2;
if(checkMid(mid)){
ans=mid;
l=mid+1;
}
else r=mid-1;
}
cout<<ans;
return 0;
}
- [P1577 切绳子](https://www.luogu.com.cn/problem/P1577)
题解:二分答案的例题,这题可以先把小数乘以100化为整数,在整数域上二分,输出时再除以100即可。我这里是直接在实数域上二分,但是这题有点古怪的是如果只保留两位小数过不了...
#include<bits/stdc++.h>
using namespace std;
double vis[10010],l,r,mid;
int n,k;
bool checkMid(double x){
int num=0;
for(int i=1;i<=n;i++){
num+=vis[i]/x;
}
if(num>=k)return true;
else return false;
}
int main()
{
cin>>n>>k;
for(int i=1;i<=n;i++){
cin>>vis[i];
r+=vis[i];
}
while(r-l>1e-4){
mid=(r+l)/2;
if(checkMid(mid)){
l=mid;
}else{
r=mid;
}
}
printf("%.6lf",l);
return 0;
}
- [P1387 最大正方形](https://www.luogu.com.cn/problem/P1387)
题解:用动态规划解,用dp(i, j) 表示以 (i, j)为右下角, 且只包含 1 的正方形的边长最大值。
- 对于每个位置 (i,j)如果该位置的值是 0,则 dp(i,j)=0,因为当前位置不可能在由 1 组成的正方形中;
如果该位置的值是 1,则 dp(i,j) 的值由其上方、左方和左上方的三个相邻位置的 dp 值决定。具体而言,当前位置的元素值等于三个相邻位置的元素中的最小值加 1,状态转移方程如下:
dp(i,j)=min(dp(i−1,j),dp(i−1,j−1),dp(i,j−1))+1
#include<bits/stdc++.h>
using namespace std;
int n,m;
int vis[110][110];
int dp[110][110],ans;
int main()
{
cin>>n>>m;
for(int i=1; i<=n; i++)
{
for(int j=1; j<=m; j++)
{
cin>>vis[i][j];
}
}
for(int i=1; i<=n; i++)
{
for(int j=1; j<=m; j++)
{
if(vis[i][j]==0)dp[i][j]=0;//其实可以省略,因为默认是0
else
{
if(i==1||j==1)dp[i][j]=1;
else
{
dp[i][j]=min(min(dp[i-1][j],dp[i][j-1]),dp[i-1][j-1])+1;
}
}
ans=max(dp[i][j],ans);
}
}
cout<<ans;
return 0;
}