目录
T1 铺瓷砖 ヽ(✿゚▽゚)ノ
T2 小Y的问题 (๑•̀ㅂ•́)و✧
T3 水管工的难题 ╰(*°▽°*)╯
T1 铺瓷砖
【问题描述】
- 有一面很长很长的墙。你需要在这面墙上贴上两行瓷砖。
- 你的手头有两种不同尺寸的瓷砖, 你希望用这两种瓷砖各贴一行。
- 贴在第一行的每块瓷砖长度为A/B,贴在第二行的每块瓷砖长度为C/D。
- 如上图所示,两排瓷砖从同一起始位置开始向右排列,两排瓷砖的第一块的左端的缝隙
- 是对齐的。你想要知道, 最短铺多少距离后, 两排瓷砖的缝隙会再一次对齐。
【输入】
输入的第 1 行包含一个正整数 T,表示测试数据的组数。
接下来 T 行, 每行 4 个正整数,即两种瓷砖的长度分别为A/B,C/D。
【输出】
输出包含 T 行, 第 i 行包含一个分数或整数, 表示第 i 组数据的答案。
如果答案为分数,则以“X/Y”的格式输出,不含引号。
分数必须化简为最简形式。如果答案为整数,则输出整数 X。
【输入输出样例 1】
tile.in | tile.out |
2 1 2 1 3 1 2 5 6 |
1 5/2 |
【输入输出样例 1 说明】
对于第一组数据,第一行瓷砖贴 2 块,第二行贴 3 块,总长度都为 1,即在距离起始位
置长度为 1 的位置两行瓷砖的缝隙会再次对齐。
对于第二组数据, 第一行瓷砖贴 5 块,第二行贴 3 块,总长度都为5/2
【代码实现】
因为是用一种很神奇的方法写的所以也说不清楚...
模拟一下过程,找找规律,就可以先通分再找gcd。
利用 lcm*gcd=a*b,找出分数的 lcm,再同除约分。
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
#include<string>
#include<cmath>
using namespace std;
typedef long long ll;
/*【铺瓷砖】
有一面很长很长的墙。你需要在这面墙上贴上两行瓷砖。你的手头有两种不同尺寸的瓷砖,
你希望用这两种瓷砖各贴一行。贴在第一行的每块瓷砖长度为A/B,贴在第二行的每块瓷砖长度为C/D。
想要知道,最短铺多少距离后,两排瓷砖的缝隙会再一次对齐。*/
//...最小公倍数??还是分数的最小公倍数?
void reads(ll &x){ //读入优化(正负整数)
int fx=1;x=0;char s=getchar();
while(s<'0'||s>'9'){if(s=='-')fx=-1;s=getchar();}
while(s>='0'&&s<='9'){x=x*10+s-'0';s=getchar();}
x*=fx; //正负号
}
ll gcd(ll x,ll y){ //x>y
return (y==0)?x:gcd(y,x%y);
}
int main(){
freopen("tile.in","r",stdin);
freopen("tile.out","w",stdout);
ll T,a,b,c,d; reads(T);
while(T--){
reads(a),reads(b),reads(c),reads(d);
ll t1=a*d,t2=b*c; if(t1<t2) swap(t1,t2);
ll gcds=gcd(t1,t2),muls=t1*t2;
ll lcms=muls/gcds,yue=gcd(lcms,b*d);
if(yue==b*d) printf("%lld\n",lcms/(b*d));
else printf("%lld/%lld\n",lcms/yue,(b*d)/yue);
}
}
T2 小Y的问题
【问题描述】
- 有个孩子叫小 Y,一天,小 Y 拿到了一个包含 n 个点和 n-1 条边的无向连通图,
- 图中的点用 1~n 的整数编号。小 Y 突发奇想,想要数出图中有多少个“Y 字形”。
- 一个“Y 字形”由 5 个不同的顶点 A、 B、 C、 D、 E 以及它们之间的 4 条边组成,
- 其中 AB、BC、BD、DE 之间有边相连, 如下图所示。
- 同时,无向图中的每条边都是有一定长度的。
- 一个“Y 字形”的长度定义为构成它的四条边的长度和。
- 小 Y 也想知道,图中长度最大的“Y 字形”长度是多少。
【输入】
第一行包含一个整数 n,表示无向图的点数。
n 行,每行 3 个整数表示编号为 x 和 y 的点之间有一条长度为 z 的边。
【输出】
第 1 行包含一个整数,表示图中的“Y 字形”的数量。
第 2 行包含一个整数,表示图中长度最大的“Y 字形”的长度。
【输入输出样例 1】
question.in | question.out |
7 1 3 2 2 3 1 3 5 1 5 4 2 4 6 3 5 7 3 |
5 9 |
长度最大的“Y 字形”是编号 3、 5、 7、 4、 6 的顶点构成的,长度为 9。
【数据规模与约定】
对于 30%的数据, n≤10
对于 60%的数据, n≤2,000
对于 100%的数据, n≤200,000, 1≤x,y≤n, 1≤z≤10,000。
【分析】
这题确实是好题,看上去很复杂,实现还比较简单。
注意!这题直接记录连边就行,按照边来算!树形结构所以不会有环。
求Y字形个数可以用组合数的方法,对于关键点A(3连边),B(2连边),
我们枚举所有边,判断是否能满足每条边相连的两个点、分别的入度条件。
对于求最大长度,需要记录每个点的连边中前三大的。
因为关键点A除了AB边之外,还能选择两条边组成max值;
关键点B除了AB之外,还能选择一条边。这样就需要判断AB边是否冲突。
所以都可以线性处理完成辣。开心qwq。
【代码实现】
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
#include<string>
#include<cmath>
using namespace std;
typedef long long ll;
/*【小y的问题】
n个点和n-1条边的无向连通图,图中的点用1~n的整数编号。//树
一个“Y字形”由5个顶点以及它们之间的4条边组成,想要数出图中【有多少个】“Y字形”。
无向图中的每条边都是有一定长度的。“Y字形”的长度为构成它的四条边的长度和。
小Y也想知道,图中长度最大的“Y字形”【长度】是多少。 */
void reads(ll &x){ //读入优化(正负整数)
int fx=1;x=0;char s=getchar();
while(s<'0'||s>'9'){if(s=='-')fx=-1;s=getchar();}
while(s>='0'&&s<='9'){x=x*10+s-'0';s=getchar();}
x*=fx; //正负号
}
struct node{ ll u,v,w; }e[200019];
ll max1[200019],max2[200019],max3[200019],rd[200019];
int main(){
freopen("question.in","r",stdin);
freopen("question.out","w",stdout);
ll n,u,v,w; reads(n);
for(ll i=1;i<=n-1;i++){
reads(e[i].u),reads(e[i].v),reads(e[i].w);
rd[e[i].u]++; rd[e[i].v]++;
if(e[i].w>max1[e[i].u]) //比最大的还大
max3[e[i].u]=max2[e[i].u],max2[e[i].u]=max1[e[i].u],max1[e[i].u]=e[i].w;
else if(e[i].w>max2[e[i].u]) max3[e[i].u]=max2[e[i].u],max2[e[i].u]=e[i].w;
else if(e[i].w>max3[e[i].u]) max3[e[i].u]=e[i].w; //对每个点,预处理出前三大的边
if(e[i].w>max1[e[i].v]) //比最大的还大
max3[e[i].v]=max2[e[i].v],max2[e[i].v]=max1[e[i].v],max1[e[i].v]=e[i].w;
else if(e[i].w>max2[e[i].v]) max3[e[i].v]=max2[e[i].v],max2[e[i].v]=e[i].w;
else if(e[i].w>max3[e[i].v]) max3[e[i].v]=e[i].w; //对每个点,预处理出前三大的边
} ll ans1=0,ans2=0;
for(ll i=1;i<n;i++){ //枚举每条边
u=e[i].u,v=e[i].v,w=e[i].w;
if(!(max(rd[u],rd[v])>=3&&min(rd[u],rd[v])>=2)) continue;
if(rd[v]>=3&&rd[u]>=2){ //方案数是C(rd[v]-1,2)*(rd[u]-1)
ans1+=((rd[v]-1)*(rd[v]-2)/2)*(rd[u]-1);
if(e[i].w<max2[v]&&e[i].w<max1[u])
ans2=max(ans2,max1[u]+e[i].w+max1[v]+max2[v]);
else if(e[i].w<max2[v]&&e[i].w==max1[u])
ans2=max(ans2,max2[u]+e[i].w+max1[v]+max2[v]);
else if(e[i].w==max2[v]&&e[i].w<max1[u])
ans2=max(ans2,max1[u]+e[i].w+max1[v]+max3[v]);
else if(e[i].w==max1[v]&&e[i].w<max1[u])
ans2=max(ans2,max1[u]+e[i].w+max2[v]+max3[v]);
else if(e[i].w==max2[v]&&e[i].w==max1[u])
ans2=max(ans2,max2[u]+e[i].w+max1[v]+max3[v]);
else if(e[i].w==max1[v]&&e[i].w==max1[u])
ans2=max(ans2,max2[u]+e[i].w+max2[v]+max3[v]);
} if(rd[u]>=3&&rd[v]>=2){ //方案数是C(rd[u]-1,2)*(rd[v]-1)
ans1+=((rd[u]-1)*(rd[u]-2)/2)*(rd[v]-1);
if(e[i].w<max2[u]&&e[i].w<max1[v])
ans2=max(ans2,max1[v]+e[i].w+max1[u]+max2[u]);
else if(e[i].w<max2[u]&&e[i].w==max1[v])
ans2=max(ans2,max2[v]+e[i].w+max1[u]+max2[u]);
else if(e[i].w==max2[u]&&e[i].w<max1[v])
ans2=max(ans2,max1[v]+e[i].w+max1[u]+max3[u]);
else if(e[i].w==max1[u]&&e[i].w<max1[v])
ans2=max(ans2,max1[v]+e[i].w+max2[u]+max3[u]);
else if(e[i].w==max2[u]&&e[i].w==max1[v])
ans2=max(ans2,max2[v]+e[i].w+max1[u]+max3[u]);
else if(e[i].w==max1[u]&&e[i].w==max1[v])
ans2=max(ans2,max2[v]+e[i].w+max2[u]+max3[u]);
}
} cout<<ans1<<endl<<ans2<<endl;
}
T3 水管工的难题
【问题描述】
- 你是一名优秀的水管工。 一天你遇到了一个棘手的难题。
- 你需要在一个长方体状的房间内连接一条贯穿房间内部的水管。
- 房间的长为 X,宽为 Y,高为 Z, 可以看成是 X×Y×Z个小立方体组成的。
- 建立直角坐标系,则房间内每个小立方体空间都可以用一个三维坐标(x,y,z)表示,
- 房间一角的小立方体的坐标为(1,1,1), 它的对角的坐标为(X,Y,Z),
- 其余小立方体的坐标的三个维度都分别落在 1…X, 1…Y 和 1…Z 内。
- 水管的入口和出口已经开凿好了,它们位于长方体的表面。
- 下图展示了房间的立方体空间的划分方式和一种开凿水管出入口的方式。
- 只有一种水管部件, 呈 L 型,占据了 4 个小立方体空间,
- 如下图所示, 灰色部分是水管部件两端的开口位置。
- 你可以任意旋转,然后将它的开口接到入口、 出口或者其它水管部件的开口上。
- 水管的开口必须正好接在入口或出口上才算接上,伸出房间外一截是不行的。
- 下图展示了一种两个水管部件的连接方式。
- 在连接水管时, 水管部件间不能相交,也不能伸出房间之外。
- 房间内有一些小立方体空间已经被物品占据了,水管也不能经过那些格子。
- 另外,水管部件数量有限,你的手头只有12个这样的零件。
- 现在,给出房间的长、高、 宽, 以及入口、出口的位置和房间内物品的坐标,
- 请你计算至少需要多少个这样的水管部件才能完成任务,或者判断无法完成任务。
【输入】
输入的第 1 行包含 4 个正整数 X, Y, Z, m,
其中 X,Y,Z 表示房间的长、高、宽, m 表示房间中物品的数量。
输入的第 2 行包含 3 个正整数 x1, y1, z1, 和一个字符 ch。
其中(x1,y1,z1)是水管入口所在的小立方体的坐标。
ch 的值为'x'、 'y'或'z', 用于指示入口所在的面。
当 ch 为'x'时,表示入口所在的面与 YZ 坐标平面平行,
当 ch 为'y'时,表示入口所在的面与 XZ 坐标平面平行,
当 ch 为'z'时,表示入口所在的面与 XY 坐标平面平行。
输入保证入口所在的面在长方体表面上。
输入的第 3 行包含 3 个正整数 x2, y2, z2,和一个字符 ch,
表示水管出口的位置, 意义与格式同上。
接下来 m 行,每行包含三个正整数 xi, yi, zi,
表示在坐标(xi,yi,zi)的小立方体空间内有物品。
【输出】
输出包含 1 个整数,表示最少使用的水管部件的数量。
如果不能完成接水管的任务,输出“impossible”, 不含引号。
【输入输出样例 1】
plumber.in | plumber.out |
5 4 3 1 3 1 1 z 1 4 3 x 2 3 3 |
2 |
【输入输出样例 1 说明】
入口和出口的位置如图所示:
【分析】
dfs,记录放的水管的最后一个位置和方向,具体实现看代码。
需要最优性剪枝,记录三维空间的曼哈顿距离,表示出这个点到达终点的最小步数。
(当然,本蒟蒻太菜了这代码T了3个点,WA了一个点qaq)可能是评测机的问题
这题最难的地方是表示状态,和把L形转化成(某个方向走两步+另一个方向走两步);
或者(某个方向走三步+另一个方向走一步),注意起点,终点朝向的处理方式不同。
【代码实现】
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
#include<string>
#include<cmath>
using namespace std;
typedef long long ll;
//看到这题的时候我真想说我立体几何学的极差......
//想了几分钟我真想说我深搜学的极差......
//深搜+剪枝。对于每个块,可以看成{向一个方向放两个,另一个方向放两个}。
//或者{向一个方向放三个,另一个方向放一个},两种情况分别dfs。
void reads(int &x){ //读入优化(正负整数)
int fx=1;x=0;char s=getchar();
while(s<'0'||s>'9'){if(s=='-')fx=-1;s=getchar();}
while(s>='0'&&s<='9'){x=x*10+s-'0';s=getchar();}
x*=fx; //正负号
}
const int dx[6]={1,-1,0,0,0,0};
const int dy[6]={0,0,1,-1,0,0};
const int dz[6]={0,0,0,0,1,-1}; //正方体中的六个朝向
int X,Y,Z,m,xi,yi,zi,x2,y2,z2; //大小,起点,终点
int fs_faced,ed_faced,ans=13;
bool no[30][30][30],vis[30][30][30];
bool checks(int x,int y,int z){
if(x>=1&&x<=X&&y>=1&&y<=Y&&z>=1&&z<=Z
&&!vis[x][y][z]&&!no[x][y][z]) return true;
return false; //在范围内,没有搜索过,且不是障碍
}
void dfs(int x,int y,int z,int faced,int cnt){
int dist=(int)(abs(X-x)+abs(Y-y)+abs(Z-z))/4;
if(cnt+dist>=ans) return; //【剪枝】
//加上到达终点的最小步数,还是没有ans优,直接剪枝
if(x==x2&&y==y2&&z==z2&&faced==ed_faced)
{ ans=min(ans,cnt); return; } //到达终点,更新答案
if(checks(x+dx[faced],y+dy[faced],z+dz[faced])
&&checks(x+2*dx[faced],y+2*dy[faced],z+2*dz[faced])){
//↑↑首先,必须能向某个方向走2步(2+2或者3+1)
vis[x+dx[faced]][y+dy[faced]][z+dz[faced]]=true;
vis[x+2*dx[faced]][y+2*dy[faced]][z+2*dz[faced]]=true;
int xx=x+2*dx[faced],yy=y+2*dy[faced],zz=z+2*dz[faced]; //先走两步
for(int k=0;k<6;k++) //6个方向【走下一步(上一步短,下一步长)】
if((faced/2)!=(k/2)) //不是走向faced或faced的反向
if(checks(xx+dx[k],yy+dy[k],zz+dz[k]) //向第二个方向走两步
&&checks(xx+2*dx[k],yy+2*dy[k],zz+2*dz[k])){
vis[xx+dx[k]][yy+dy[k]][zz+dz[k]]=true;
vis[xx+2*dx[k]][yy+2*dy[k]][zz+2*dz[k]]=true;
dfs(xx+2*dx[k],yy+2*dy[k],zz+2*dz[k],k,cnt+1);
vis[xx+dx[k]][yy+dy[k]][zz+dz[k]]=false;
vis[xx+2*dx[k]][yy+2*dy[k]][zz+2*dz[k]]=false;
}
if(checks(x+3*dx[faced],y+3*dy[faced],z+3*dz[faced])){ //前一步就走三步
vis[x+3*dx[faced]][y+3*dy[faced]][z+3*dz[faced]]=true;
int xx=x+3*dx[faced],yy=y+3*dy[faced],zz=z+3*dz[faced]; //走三步
for(int k=0;k<6;k++) //6个方向【走下一步(上一步长,下一步短)】
if((faced/2)!=(k/2)) //不是走向faced或faced的反向
if(checks(xx+dx[k],yy+dy[k],zz+dz[k])){ //向第二个方向走一步
vis[xx+dx[k]][yy+dy[k]][zz+dz[k]]=true;
dfs(xx+dx[k],yy+dy[k],zz+dz[k],k,cnt+1);
vis[xx+dx[k]][yy+dy[k]][zz+dz[k]]=false;
}
vis[x+3*dx[faced]][y+3*dy[faced]][z+3*dz[faced]]=false;
}
vis[x+dx[faced]][y+dy[faced]][z+dz[faced]]=false;
vis[x+2*dx[faced]][y+2*dy[faced]][z+2*dz[faced]]=false;
}
}
int main(){
freopen("plumber.in","r",stdin);
freopen("plumber.out","w",stdout);
reads(X),reads(Y),reads(Z),reads(m);
reads(xi),reads(yi),reads(zi);
char ss[2]; cin>>ss; //作为字符串输入,可以忽略空格的影响
if(ss[0]=='x'&&xi==1) fs_faced=0;
if(ss[0]=='x'&&xi==X) fs_faced=1;
if(ss[0]=='y'&&yi==1) fs_faced=2;
if(ss[0]=='y'&&yi==Y) fs_faced=3;
if(ss[0]=='z'&&zi==1) fs_faced=4;
if(ss[0]=='z'&&zi==Z) fs_faced=5;
reads(x2),reads(y2),reads(z2); cin>>ss;
if(ss[0]=='x'&&x2==1) ed_faced=1;
if(ss[0]=='x'&&x2==X) ed_faced=0;
if(ss[0]=='y'&&y2==1) ed_faced=3;
if(ss[0]=='y'&&y2==Y) ed_faced=2;
if(ss[0]=='z'&&z2==1) ed_faced=5;
if(ss[0]=='z'&&z2==Z) ed_faced=4; //←大型翻车现场...
int xx,yy,zz;
for(int i=1;i<=m;i++) //障碍物
reads(xx),reads(yy),reads(zz),no[xx][yy][zz]=true;
xi-=dx[fs_faced],yi-=dy[fs_faced],zi-=dz[fs_faced];
dfs(xi,yi,zi,fs_faced,0);
if(ans==13) cout<<"impossible"<<endl;
else cout<<ans<<endl;
}
p.s.hss-2018noip-rp++ qwq~~~
——时间划过风的轨迹,那个少年,还在等你。