2017 清北济南考前刷题Day 7 afternoon
期望得分:100+100+30=230
实际得分:100+100+30=230
1. 三向城
题目描述
三向城是一个巨大的城市,之所以叫这个名字,是因为城市中遍布着数不尽的三岔路口。(来自取名力为0的出题人)
具体来说,城中有无穷多个路口,每个路口有唯一的一个正整数标号。除了1号路口外,每个路口都连出正好3条道路通向另外3个路口:编号为x(x>1)的路口连出3条道路通向编号为x*2,x*2+1和x/2(向下取整)的3个路口。1号路口只连出两条道路,分别连向2号和3号路口。
所有道路都是可以双向通行的,并且长度都为1。现在,有n个问题:从路口x到路口y的最短路长度是多少?
输入格式
第一行包含一个整数n,表示询问数量;
接下来n行,每行包含两个正整数x, y,表示询问从路口x到路口y的最短路长度。
输出格式
输出n行,每行包含一个整数,表示对每次询问的回答。如果对于某个询问不存在从x到y的路径,则输出-1。
样例输入
3
5 7
2 4
1 1
样例输出
0
样例解释
5号路口到7号路口的路径为:5->2->1->3->7,长度为4;
2号路口到4号路口的路径为:2->4,长度为1;
1号路口到本身的路径长度为0;
数据范围
对30%的数据,x,y≤20;
对60%的数据,x,y≤105,n≤10;
对100%的数据,x,y≤109,n≤104。
把大的除以2直到相等
#include<cstdio> #include<iostream> using namespace std; void read(int &x) { x=0; char c=getchar(); while(!isdigit(c)) c=getchar(); while(isdigit(c)) { x=x*10+c-'0'; c=getchar(); } } int main() { freopen("city.in","r",stdin); freopen("city.out","w",stdout); int n,x,y,ans; read(n); while(n--) { ans=0; read(x); read(y); while(x!=y) { if(x<y) swap(x,y); x>>=1; ans++; } cout<<ans<<'\n'; } }
2. 灵魂画师
题目描述
虽然不知道为什么,但是你一直想用一种神奇的方式完成一幅画作。
你把n张画纸铺成一排,并将它们从1到n编号。你一共有c种颜色可用,这些颜色可以用0到c-1来编号。初始时,所有画纸的颜色都为1。你一共想进行k次作画,第i次作画时,你会等概率随机地选闭区间[Li,Ri]内的画纸的一个子集(可以为空),再随机挑一种颜色bi,并把挑出来的画纸都涂上该颜色。原有颜色a的画纸在涂上颜色b后,颜色会变成(a*b) mod c,这是这个世界的规律。
因为你将颜色用数字来命名了,因此你可以求出在k次作画结束后,每张画纸上的颜色对应的数字相加之和的期望。现在请你编程求一下这个值。
以防万一你不知道什么是期望:如果一个量X,它有p1的概率值为v1,有p2的概率值为v2……pn的概率值为vn,则X的期望值等于。
输入格式
第一行包含3个正整数n, c, k,意义如题所述。
接下来k行,每行包含两个数Li, Ri,表示你每次操作会从哪个区间内随机地选画纸。
输出格式
一行,一个小数,表示答案,四舍五入精确到小数点后3位。
样例输入1
2 3 1
1 2点数、、
样例输出1
2.000
概率DP
dp[k][i][j] 表示操作了k次,第i张画纸改成颜色j的概率
不改转移到 dp[k+1][i][j]
改转移到 dp[k+1][i][j*h%c]
这是n^4
优化:
相同颜色的纸改的结果的概率相同,而且开始都是1
所以i那一维可以压去
即 dp[k][j] 表示一张纸操作了k次,变成颜色j的概率
转移同上
最后的答案=Σ dp[cnt[i]][j] cnt[i]表示i位置被操作了多少次
n^3代码
#include<cstdio> #include<iostream> using namespace std; double dp[101][101]; int cnt[101],col[101]; void read(int &x) { x=0; char c=getchar(); while(!isdigit(c)) c=getchar(); while(isdigit(c)) { x=x*10+c-'0'; c=getchar(); } } int main() { freopen("paint.in","r",stdin); freopen("paint.out","w",stdout); int n,c,k; read(n); read(c); read(k); int mx=0; int l,r; while(k--) { read(l); read(r); for(int i=l;i<=r;i++) cnt[i]++,mx=max(mx,cnt[i]); } dp[0][1]=1; for(int h=0;h<mx;h++) { for(int j=0;j<c;j++) { for(int g=0;g<c;g++) dp[h+1][j*g%c]+=dp[h][j]/(c*2.0); dp[h+1][j]+=dp[h][j]/2.0; } } double ans=0; for(int i=1;i<=n;i++) for(int j=0;j<c;j++) ans+=dp[cnt[i]][j]*j; printf("%.3lf",ans); }
n^4代码
#include<cstdio> #include<iostream> using namespace std; double dp[101][101][101]; int cnt[101]; void read(int &x) { x=0; char c=getchar(); while(!isdigit(c)) c=getchar(); while(isdigit(c)) { x=x*10+c-'0'; c=getchar(); } } int main() { int n,c,k; read(n); read(c); read(k); for(int i=1;i<=n;i++) dp[0][i][1]=1; cnt[1]=n; int l,r; for(int h=0;h<k;h++) { read(l); read(r); for(int i=1;i<l;i++) for(int j=0;j<c;j++) dp[h+1][i][j]=dp[h][i][j]; for(int i=l;i<=r;i++) for(int j=0;j<c;j++) { for(int g=0;g<c;g++) dp[h+1][i][j*g%c]+=dp[h][i][j]/(c*2.0); dp[h+1][i][j]+=dp[h][i][j]/2.0; } for(int i=r+1;i<=n;i++) for(int j=0;j<c;j++) dp[h+1][i][j]=dp[h][i][j]; } double ans=0; for(int i=1;i<=n;i++) for(int j=0;j<c;j++) ans+=j*dp[k][i][j]; printf("%.3lf",ans); }
3. 香子兰
题目描述
你承包了一片香子兰花田。现在到了收获的季节,你需要把种下的香子兰全部收获起来,到花店卖掉并取得新的种子,再向田里播种下一季的香子兰。
你的花田一共由n-2片花田组成,编号从1到n-2。算上你的家和花店,一共有n个地点,其中你的家编号为0,花店编号为n-1。即,家、花田、花店都属于地点,且它们都有一个唯一的0~n-1的编号。有m条双向道路连接这些地点。保证所有地点间都是直接或间接连通的。
你需要从家里出发,经过所有的花田进行收获,再到达花店,再从花店出发经过所有花田进行播种,最后重新回到家中。当你经过一片花田的时候,你可以选择收获、播种或者什么事都不做,也就是说你经过一片未收割的花田时可以不立即收割它,播种亦然。然而,播种必须发生在你完成了所有收获并到花店交货之后。在完成最后一个花田的收获后,你必须在到达花店后才能开始播种。也就是说,在你没有收获完所有花田并到花店交货前,即使你已经经过了花店,你也不能进行播种。(啰嗦了这么多但愿讲明白了)
然而还有一个问题。在收割完花朵后,花田会变得光秃秃的,此时土地里的水分会迅速蒸发。考虑到这个问题,更早被收割的花田也理应更早地被播种。具体来说,你必须保证前个被收割的花田也是前个被播种的,其中符号表示向下取整。你不需要保证这些花田收割和播种的顺序完全一致,而只需要保证前名的集合不变即可。
现在,你需要求出完成上述一系列动作走过的最短路程。
输入格式
第一行包含两个整数n, m,表示地点总数和道路条数;
接下来m行,每行包含3个整数xi, yi, zi,表示有一条连接编号为xi和yi 的地点,长为zi的道路。
输出格式
一行,包含一个整数,表示最短路程。
样例输入
4 4
0 1 1
1 2 2
2 3 1
1 3 1
样例输出
10
样例解释
从0出发,先走到1收获(距离1),再走到2收获(距离2),再走到3交货(距离1),再走到1播种(距离1),再走到2播种(距离2),再走到1什么都不干(距离2),再走到0(距离1)。总距离为10。前个被收获和播种的花田集合是{1号花田}。
数据范围
对于30%的数据,有n≤8;
另有10%的数据,有m=n-1,且对1≤i≤m,保证xi=i-1,yi=i;
另有20%的数据,有m=n-1,且每个花田至多只与另外两个花田有道路相连;
对于100%的数据,有n≤20,m≤400,0≤xi, yi≤n-1,xi≠yi,0≤zi≤10000。
状压DP
f[i][j] 表示从家到点i,经过了状态为j的点集
g[i][j] 表示从花店到点i,经过了状态为j的点集
j只包括n-2个花田
Floyd预处理任意两点间的最短距离dis
f[i][j|(1<<k-2)]=min(f[i][j]+dis[i][k])
k-2 是因为代码中家从1开始编号
枚举所有含(n-2)/2个花田的状态A,剩余的花田为B
如果是收获
枚举A中最后一个收获的花田j,枚举B中第一个收获的花田k
收获的最短路径= min(f[j][i]+g[k][S^i]+dis[j][k])
如果是播种
枚举A中最后一个播种的花田j,枚举B中第一个播种的花田k
播种的最短路径= min(g[j][i]+f[k][S^i]+dis[j][k])
#include<cstdio> #include<cstring> #include<iostream> #include<algorithm> using namespace std; #define N 21 #define M 401 int dis[N][N]; int f[N][1048580],g[N][1048580]; int bit[N],cnt[1048580]; void read(int &x) { x=0; char c=getchar(); while(!isdigit(c)) c=getchar(); while(isdigit(c)) { x=x*10+c-'0'; c=getchar(); } } int cal(int s) { int sum=0; while(s) sum+=(s&1),s>>=1; return sum; } int main() { freopen("vanilla.in","r",stdin); freopen("vanilla.out","w",stdout); int n,m; read(n); read(m); int u,v,w; memset(dis,63,sizeof(dis)); for(int i=1;i<=n;++i) dis[i][i]=0; while(m--) { read(u); read(v); read(w); ++u; ++v; dis[u][v]=min(dis[u][v],w); dis[v][u]=min(dis[v][u],w); } if(n==3) { printf("%d",(dis[1][2]+dis[2][3])*2); return 0; } for(int k=1;k<=n;++k) for(int i=1;i<=n;++i) for(int j=1;j<=n;++j) dis[i][j]=min(dis[i][j],dis[i][k]+dis[k][j]); bit[0]=1; for(int i=1;i<=n;++i) bit[i]=bit[i-1]<<1; int S=bit[n-2]-1; for(int i=1;i<=S;++i) cnt[i]=cal(i); int half=n-2>>1; memset(f,63,sizeof(f)); for(int i=2;i<n;++i) f[i][bit[i-2]]=dis[1][i]; for(int i=0;i<S;++i) if(cnt[i]<=half) { for(int j=2;j<n;++j) if(f[j][i]<1e9) for(int k=2;k<n;++k) f[k][i|bit[k-2]]=min(f[k][i|bit[k-2]],f[j][i]+dis[j][k]); } memset(g,63,sizeof(g)); for(int i=2;i<n;++i) g[i][bit[i-2]]=dis[n][i]; for(int i=0;i<S;++i) if(cnt[i]<=n-2-half) { for(int j=2;j<n;++j) if(g[j][i]<1e9) for(int k=2;k<n;++k) g[k][i|bit[k-2]]=min(g[k][i|bit[k-2]],g[j][i]+dis[j][k]); } int x,ans=2e9; for(int i=0;i<S-1;++i) if(cnt[i]==half) { x=2e9; for(int j=2;j<n;++j) if(i&bit[j-2]) for(int k=2;k<n;++k) if(!(i&bit[k-2])) x=min(x,f[j][i]+g[k][S^i]+dis[j][k]); for(int j=2;j<n;++j) if((i&bit[j-2])) for(int k=2;k<n;++k) if(!(i&bit[k-2])) ans=min(ans,x+g[j][i]+f[k][S^i]+dis[j][k]); } cout<<ans; }
中间30分暴力
讨论哪些边走4遍,哪些边走3遍
#include<cstdio> #include<iostream> using namespace std; int n,m; struct node { int u,v,w; }e[401]; int ru[21]; namespace solve1 { void work() { int ans=0; for(int i=2;i<m;i++) ans+=e[i].w*4; ans+=e[m].w*2; ans+=e[1].w*2; cout<<ans; } } namespace solve2 { int sum=0; bool f0=false,fn=false; void work() { if(ru[0]==1) f0=true; if(ru[n-1]==1) fn=true; for(int i=1;i<=m;i++) { if(!e[i].u || !e[i].v) { if(f0) sum+=e[i].w*2; else sum+=e[i].w*4; } else if(e[i].u==n-1 || e[i].v==n-1) { if(fn) { if(e[i].v && e[i].u) sum+=e[i].w*2; } else sum+=e[i].w*4; } else sum+=e[i].w*4; } cout<<sum; } } int main() { freopen("vanilla.in","r",stdin); freopen("vanilla.out","w",stdout); scanf("%d%d",&n,&m); bool flag1=true,flag2=true; for(int i=1;i<=m;i++) { scanf("%d%d%d",&e[i].u,&e[i].v,&e[i].w); if(e[i].u!=i-1 || e[i].v!=i) flag1=false; ru[e[i].u]++; ru[e[i].v]++; if(e[i].u>0 && e[i].u<n && ru[e[i].u]>2) flag2=false; if(e[i].v>0 && e[i].v<n && ru[e[i].v]>2) flag2=false; } if(flag1) { solve1::work(); return 0; } if(flag2) { solve2::work(); return 0; } else { int sum=0; for(int i=1;i<=m;i++) sum+=e[i].w; cout<<sum*2; } }