1 P1967 货车运输
一道十分综合的题目。存图,最小生成树,并查集,lca,权值更新于一体。
题面
题目描述
A国有(n)座城市,编号从1到(n),城市之间有(m)条双向道路。每一条道路对车辆都有重量限制,简称限重。现在有(q)辆货车在运输货物, 司机们想知道每辆车在不超过车辆限重的情况下,最多能运多重的货物。
输入格式
输入文件第一行有两个用一个空格隔开的整数(n,m),表示A国有(n)座城市和(m)条道路。接下来(m)行每行3个整数(x,y,z),每两个整数之间用一个空格隔开,表示从(x)号城市到(y)号城市有一条限重为(z)的道路。
注意:(x)不等于(y),两座城市之间可能有多条道路。
接下来一行有一个整数(q),表示有(q)辆货车需要运货。接下来(q)行,每行两个整数(x,y),之间用一个空格隔开,表示一辆货车需要从(x)城市运输货物到(y)城市,注意:(x)不等于(y)。
输出格式
输出共有(q) 行,每行一个整数,表示对于每一辆货车,它的最大载重是多少。如果货车不能到达目的地,输出-1。
输入样例
4 3
1 2 4
2 3 3
3 1 1
3
1 3
1 4
1 3
输出样例
3
-1
3
题解
分步解释
1 基本思路
首先题目给出的是一个图,这当中每两个点之间可能有多条路径,但是我们想要限重最大,所以说我们应该寻找任意两点之间限重之和最小的,换言之第一步我们要求出图的最小生成树。(此处用到了两个结构体)
使用kruskal求最小生成树(此处用到了并查集)之后,就可以用lca寻找任意两点的最近公共祖先,并更新最小权值。仅仅是在lca的代码中加入了边权的处理。
2 准备工作
int cnt,deep[500005],f[500005],fa[500005][21],w[500005][21];
bool vis[500005];
struct node{
int to,next,val;
}edge[500005<<1];
struct way{
int x,y,dis;
}tu[500005];
int head[500005];
int n,m,q;
int x,y,z;
其中
deep[]
记录深度,f[]
维护连通性(并查集),fa[]
表示点的祖先(lca),head[]
链前,w[]
表示最大载重
node
结构体edge[]
表示最小生成树的图,way
结构体的tu[]
数组表示原图
3 链前实现+并查集实现
这俩没啥好说的
void add(int x,int y,int val){
edge[++cnt].to=y;
edge[cnt].val=val;
edge[cnt].next=head[x];
head[x]=cnt;
}
int find(int x){
if(f[x]!=x) f[x]=find(f[x]);
return f[x];
}
//其中一部分
void update(){
int gg1=find(tu[i].x);int gg2=find(tu[i].y);
if(f[gg1]!=f[gg2]){
f[gg1]=gg2;
}
}
4 kruskal
bool cmp(way a,way b){
return a.dis>b.dis;
}
void krs(){
sort(tu+1,tu+m+1,cmp);//边权排序
for(int i=1;i<=n;i++) f[i]=i;
for(int i=1;i<=m;i++){
int gg1=find(tu[i].x);int gg2=find(tu[i].y);
if(f[gg1]!=f[gg2]){
f[gg1]=gg2;
add(tu[i].x,tu[i].y,tu[i].dis);
add(tu[i].y,tu[i].x,tu[i].dis);
}
}
return ;
}
5 lca
void dfs(int x){
vis[x]=true;
for(int i=head[x];i;i=edge[i].next){
int too=edge[i].to;
if(vis[too]) continue;
deep[too]=deep[x]+1;
fa[too][0]=x;
w[too][0]=edge[i].val;
dfs(too);
}
return ;
}
#define INF 999999999
int lca(int x,int y){
if(find(x)!=find(y)) return -1;
int ans=INF;
if(deep[x]>deep[y]) swap(x,y);
for(int i=20;i>=0;i--){
if(deep[fa[y][i]]>=deep[x]){
ans=min(ans,w[y][i]);
y=fa[y][i];
}
}
if(x==y) return ans;
for(int i=20;i>=0;i--){
if(fa[x][i]!=fa[y][i]){
ans=min(ans,min(w[x][i],w[y][i]));
x=fa[x][i];y=fa[y][i];
}
}
ans=min(ans,min(w[x][0],w[y][0]));
//fa[x][0],fa[y][0]即为公共祖先
return ans;
}
for(int i=1;i<=n;i++){
if(!vis[i]){
deep[i]=1;
dfs(i);
fa[i][0]=i;
w[i][0]=INF;
}
}
//LCA初始化
for(int i=1;i<=20;i++){
for(int j=1;j<=n;j++){
fa[j][i]=fa[fa[j][i-1]][i-1];
w[j][i]=min(w[j][i-1],w[fa[j][i-1]][i-1]);
}
}
lca这里有一个权值的更新,其他的都一样
完整代码
#include <cstdio>
#include <cmath>
#include <algorithm>
#include <cstring>
using namespace std;
int cnt,deep[500005],f[500005],fa[500005][21],w[500005][21];
bool vis[500005];
struct node{
int to,next,val;
}edge[500005<<1];
struct way{
int x,y,dis;
}tu[500005];
int head[500005];
void add(int x,int y,int val){
edge[++cnt].to=y;
edge[cnt].val=val;
edge[cnt].next=head[x];
head[x]=cnt;
}
bool cmp(way a,way b){
return a.dis>b.dis;
}
int find(int x){
if(f[x]!=x) f[x]=find(f[x]);
return f[x];
}
int n,m,q;
void krs(){
sort(tu+1,tu+m+1,cmp);//边权排序
for(int i=1;i<=n;i++) f[i]=i;
for(int i=1;i<=m;i++){
int gg1=find(tu[i].x);int gg2=find(tu[i].y);
if(f[gg1]!=f[gg2]){
f[gg1]=gg2;
add(tu[i].x,tu[i].y,tu[i].dis);
add(tu[i].y,tu[i].x,tu[i].dis);
}
}
return ;
}
void dfs(int x){
vis[x]=true;
for(int i=head[x];i;i=edge[i].next){
int too=edge[i].to;
if(vis[too]) continue;
deep[too]=deep[x]+1;
fa[too][0]=x;
w[too][0]=edge[i].val;
dfs(too);
}
return ;
}
#define INF 999999999
int lca(int x,int y){
if(find(x)!=find(y)) return -1;
int ans=INF;
if(deep[x]>deep[y]) swap(x,y);
for(int i=20;i>=0;i--){
if(deep[fa[y][i]]>=deep[x]){
ans=min(ans,w[y][i]);
y=fa[y][i];
}
}
if(x==y) return ans;
for(int i=20;i>=0;i--){
if(fa[x][i]!=fa[y][i]){
ans=min(ans,min(w[x][i],w[y][i]));
x=fa[x][i];y=fa[y][i];
}
}
ans=min(ans,min(w[x][0],w[y][0]));
//fa[x][0],fa[y][0]即为公共祖先
return ans;
}
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++){
int x,y,z;
scanf("%d%d%d",&x,&y,&z);
tu[i].x=x;
tu[i].y=y;
tu[i].dis=z;
}
krs();
for(int i=1;i<=n;i++){
if(!vis[i]){
deep[i]=1;
dfs(i);
fa[i][0]=i;
w[i][0]=INF;
}
}
//LCA初始化
for(int i=1;i<=20;i++){
for(int j=1;j<=n;j++){
fa[j][i]=fa[fa[j][i-1]][i-1];
w[j][i]=min(w[j][i-1],w[fa[j][i-1]][i-1]);
}
}
scanf("%d",&q);
for(int i=1;i<=q;i++){
int x,y;
scanf("%d%d",&x,&y);
printf("%d
",lca(x,y));
}
return 0;
}
2 1090 砝码称重
一道基础的dp题。主要是想状态转移。
#include <cstdio>
#include <cmath>
#include <algorithm>
#include <cstring>
using namespace std;
int sum;
int dp[10005];
int a[10];
int weight[10]={0,1,2,3,5,10,20};
int ans;
//dp[i]=0表示质量为i的情况目前没有称出 ,dp[i]=1表示质量为i的已经称出
//处理第j个砝码时质量为w_j,有dp[i-w_j]=1-->d[i]=1;(意思是当且仅当i-w_j称出,i可称出)
void input(){//输入每个砝码的数量,并求所有砝码的总质量sum
int i=1;
sum=0;
for(int i=1;i<=6;i++){
scanf("%d",&a[i]);
sum+=a[i]*weight[i];
}
}
void dxp(){
dp[0]=1;
for(int i=1;i<=6;i++){//砝码种类
for(int j=0;j<a[i];j++){//砝码个数
for(int k=sum;k>=weight[i];k--){//判断每种质量能否被称出
if(dp[k-weight[i]]==1) dp[k]=1;
}
}
}
}
void output(){
int time=0;
for(int i=1;i<=sum;i++){
if(dp[i]==1) time++;
}
printf("Total=%d",time);
}
int main(){
input();
dxp();
output();
return 0;
}
3 1088 轮船问题
仍然dp。本质是最长上升子序列,用(O(n^2))算法做的。下次用(O(nlog n))。
(O(n^2))的本质上就是如果(a[j]<a[i])且(j<i),那么(f[j]=max(f[i]+1,f[j])),(f[i])之前都是1
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
using namespace std;
struct way{
int nor,sou;
}p[5005];
bool cmp(way a,way b){
return a.nor<b.nor;
}
int n;
int x,y;
void init(){
scanf("%d%d",&x,&y);
scanf("%d",&n);
for(int i=1;i<=n;i++){
scanf("%d%d",&p[i].nor,&p[i].sou);
}
sort(p+1,p+n+1,cmp);
}
int f[5005];//以i结尾的最长上升子序列的长度
int ans;
void upline(){
for(int i=1;i<=n;i++){
f[i]=1;
}
for(int i=1;i<=n;i++){
for(int j=1;j<i;j++){
if(p[i].sou>p[j].sou) f[i]=max(f[j]+1,f[i]);
}
}
for(int i=1;i<=n;i++){
ans=max(ans,f[i]);
}
}
void prt(){
printf("%d",ans);
}
int main(){
init();
upline();
prt();
return 0;
}