noip2016 假·解题报告
玩具谜题
题目描述
小南有一套可爱的玩具小人, 它们各有不同的职业。
有一天, 这些玩具小人把小南的眼镜藏了起来。 小南发现玩具小人们围成了一个圈,它们有的面朝圈内,有的面朝圈外。如下图:
这时singer告诉小南一个谜題: “眼镜藏在我左数第3个玩具小人的右数第1个玩具小人的左数第2个玩具小人那里。 ”
小南发现, 这个谜题中玩具小人的朝向非常关键, 因为朝内和朝外的玩具小人的左右方向是相反的: 面朝圈内的玩具小人, 它的左边是顺时针方向, 右边是逆时针方向; 而面向圈外的玩具小人, 它的左边是逆时针方向, 右边是顺时针方向。
小南一边艰难地辨认着玩具小人, 一边数着:
singer朝内, 左数第3个是archer。
archer朝外,右数第1个是thinker。
thinker朝外, 左数第2个是writer。
所以眼镜藏在writer这里!
虽然成功找回了眼镜, 但小南并没有放心。 如果下次有更多的玩具小人藏他的眼镜, 或是谜題的长度更长, 他可能就无法找到眼镜了 。 所以小南希望你写程序帮他解决类似的谜題。 这样的谜題具体可以描述为:
有 n个玩具小人围成一圈, 已知它们的职业和朝向。现在第1个玩具小人告诉小南一个包含 m条指令的谜題, 其中第 z条指令形如“左数/右数第 s,个玩具小人”。 你需要输出依次数完这些指令后,到达的玩具小人的职业。
输入输出格式
输入格式:
输入的第一行包含两个正整数 n,m, 表示玩具小人的个数和指令的条数。
接下来 n行, 每行包含一个整数和一个字符串, 以逆时针为顺序给出每个玩具小人的朝向和职业。其中0表示朝向圈内, 1表示朝向圈外。保证不会出现其他的数。字符串长度不超过10且仅由小写字母构成, 字符串不为空, 并且字符串两两不同。 整数和字符串之问用一个空格隔开。
接下来 m行,其中第 z行包含两个整数 a,,s,,表示第 z条指令。若 a,= 0,表示向左数 s,个人;若a,= 1 ,表示向右数 s,个人。保证a,不会出现其他的数, 1≤ s,<n 。
输出格式:
输出一个字符串, 表示从第一个读入的小人开始, 依次数完 m条指令后到达的小人的职业。
输入输出样例
7 3 0 singer 0 reader 0 mengbier 1 thinker 1 archer 0 writer 1 mogician 0 3 1 1 0 2
writer
10 10 1 C 0 r 0 P 1 d 1 e 1 m 1 t 1 y 1 u 0 V 1 7 1 1 1 4 0 5 0 3 0 1 1 6 1 2 0 8 0 4
y
说明
【样例1说明】
这组数据就是【题目描述】 中提到的例子。
【子任务】
子任务会给出部分测试数据的特点。 如果你在解决题目中遇到了困难, 可以尝试只解决一部分测试数据。
每个测试点的数据规模及特点如下表:
其中一些简写的列意义如下:
• 全朝内: 若为“√”, 表示该测试点保证所有的玩具小人都朝向圈内;
全左数:若为“√”,表示该测试点保证所有的指令都向左数,即对任意的
1≤z≤m, ai=0;
s,= 1:若为“√”,表示该测试点保证所有的指令都只数1个,即对任意的
1≤z≤m, si=1;
职业长度为1 :若为“√”,表示该测试点保证所有玩具小人的职业一定是一个
长度为1的字符串。
很简单的模拟。
有点小问题就是,存小人的时候要从0~n-1存,如果从1~n存就要在中间每一步都判断%n后是不是0,如果是就+1。如果在最后判断就会wa我也不知道为啥。。。
#include<cstdio> #include<cstring> #include<iostream> #include<algorithm> #include<cmath> #include<string> using namespace std; struct qwq{ int dir; char s[15]; }p[100005]; int n,m; int a,s; int now; int main() { scanf("%d%d",&n,&m); for(int i=0;i<n;i++){ scanf("%d%s",&p[i].dir,p[i].s); } now=0; for(int i=1;i<=m;i++){ scanf("%d%d",&a,&s); if(p[now].dir==0&&a==1||p[now].dir==1&&a==0){ now=(now+s)%n; } else{ now=(now+n-s)%n; } } printf("%s\n",p[now].s); return 0; }
蚯蚓
题目描述
本题中,我们将用符号⌊c⌋\lfloor c \rfloor⌊c⌋表示对c向下取整,例如:⌊3.0⌋=⌊3.1⌋=⌊3.9⌋=3\lfloor 3.0 \rfloor= \lfloor 3.1 \rfloor=\lfloor 3.9 \rfloor=3⌊3.0⌋=⌊3.1⌋=⌊3.9⌋=3。
蛐蛐国最近蚯蚓成灾了!隔壁跳蚤国的跳蚤也拿蚯蚓们没办法,蛐蛐国王只好去请神刀手来帮他们消灭蚯蚓。
蛐蛐国里现在共有n只蚯蚓(n为正整数)。每只蚯蚓拥有长度,我们设第i只蚯蚓的长度为ai(i=1,2,...,n)a_i(i=1,2,...,n)ai(i=1,2,...,n),并保证所有的长度都是非负整数(即:可能存在长度为0的蚯蚓)。
每一秒,神刀手会在所有的蚯蚓中,准确地找到最长的那一只(如有多个则任选一个)将其切成两半。神刀手切开蚯蚓的位置由常数p(是满足0<p<1的有理数)决定,设这只蚯蚓长度为x,神刀手会将其切成两只长度分别为⌊px⌋\lfloor px \rfloor⌊px⌋和x−⌊px⌋x-\lfloor px \rfloorx−⌊px⌋的蚯蚓。特殊地,如果这两个数的其中一个等于0,则这个长度为0的蚯蚓也会被保留。此外,除了刚刚产生的两只新蚯蚓,其余蚯蚓的长度都会增加q(是一个非负整常数)。
蛐蛐国王知道这样不是长久之计,因为蚯蚓不仅会越来越多,还会越来越长。蛐蛐国王决定求助于一位有着洪荒之力的神秘人物,但是救兵还需要m秒才能到来......
(m为非负整数)
蛐蛐国王希望知道这m秒内的战况。具体来说,他希望知道:
•m秒内,每一秒被切断的蚯蚓被切断前的长度(有m个数)
•m秒后,所有蚯蚓的长度(有n+m个数)。
蛐蛐国王当然知道怎么做啦!但是他想考考你......
输入输出格式
输入格式:
第一行包含六个整数n,m,q,u,v,t,其中:n,m,q的意义见【问题描述】;u,v,t均为正整数;你需要自己计算p=u/v(保证0<u<v)t是输出参数,其含义将会在【输出格式】中解释。
第二行包含n个非负整数,为ai,a2,...,ana_i,a_2,...,a_nai,a2,...,an,即初始时n只蚯蚓的长度。
同一行中相邻的两个数之间,恰好用一个空格隔开。
保证1≤n≤1051 \le n \le 10^51≤n≤105,0<m≤7∗1060<m \le 7*10^60<m≤7∗106,0≤u<v≤1090 \le u<v \le 10^90≤u<v≤109,0≤q≤2000 \le q \le 2000≤q≤200,1≤t≤711 \le t \le 711≤t≤71,0<ai≤1080<ai \le 10^80<ai≤108。
输出格式:
第一行输出⌊m/t⌋\lfloor m/t \rfloor⌊m/t⌋个整数,按时间顺序,依次输出第t秒,第2t秒,第3t秒……被切断蚯蚓(在被切断前)的长度。
第二行输出⌊(n+m)/t⌋\lfloor (n+m)/t \rfloor⌊(n+m)/t⌋个整数,输出m秒后蚯蚓的长度;需要按从大到小的顺序,依次输出排名第t,第2t,第3t……的长度。
同一行中相邻的两个数之间,恰好用一个空格隔开。即使某一行没有任何数需要 输出,你也应输出一个空行。
请阅读样例来更好地理解这个格式。
输入输出样例
3 7 1 1 3 1 3 3 2
3 4 4 4 5 5 6 6 6 6 5 5 4 4 3 2 2
3 7 1 1 3 2 3 3 2
4 4 5 6 5 4 3 2
3 7 1 1 3 9 3 3 2
//空行 2
说明
【样例解释1】
在神刀手到来前:3只蚯蚓的长度为3,3,2。
1秒后:一只长度为3的蚯蚓被切成了两只长度分别为1和2的蚯蚓,其余蚯蚓的长度增加了1。最终4只蚯蚓的长度分别为(1,2),4,3。括号表示这个位置刚刚有一只蚯蚓被切断
2秒后:一只长度为4的蚯蚓被切成了1和3。5只蚯蚓的长度分别为:2,3,(1,3),4。
3秒后:一只长度为4的蚯蚓被切断。6只蚯蚓的长度分别为:3,4,2,4,(1,3)。
4秒后:一只长度为4的蚯蚓被切断。7只蚯蚓的长度分别为:4,(1,3),3,5,2,4。
5秒后:一只长度为5的蚯蚓被切断。8只蚯蚓的长度分别为:5,2,4,4,(1,4),3,5。
6秒后:一只长度为5的蚯蚓被切断。9只蚯蚓的长度分别为:(1,4),3,5,5,2,5,4,6。
7秒后:一只长度为6的蚯蚓被切断。10只蚯蚓的长度分别为:2,5,4,6,6,3,6,5,(2,4)。所以,7秒内被切断的蚯蚓的长度依次为3,4,4,4,5,5,6。7秒后,所有蚯蚓长度从大到小排序为6,6,6,5,5,4,4,3,2,2
【样例解释2】
这个数据中只有t=2与上个数据不同。只需在每行都改为每两个数输出一个数即可。
虽然第一行最后有一个6没有被输出,但是第二行仍然要重新从第二个数再开始输出。
【样例解释3】
这个数据中只有t=9与上个数据不同。
注意第一行没有数要输出,但也要输出一个空行。
【数据范围】
如果每次都把每个蚯蚓拿出来+1,时间就会非常爆炸qwq...所以我们在最后进行+1的操作,用一个三队列维护当前最长的蚯蚓(把原来的队列排序,每次切完把左部分放在一个队列,右部分放在一个队列,就能保证三个队列都是单调递减的,每次只要比较队首元素就可以得到最长的蚯蚓)。每次切完之后-1再放入左/右队列中(因为最后要把所有都当作没有切过加上一段长度,多-的会被抵消).
#include<cstdio> #include<algorithm> #include<cstring> #include<iostream> #include<string> #include<cmath> #include<queue> using namespace std; int n,m,q,u,v,t; int a[100005]; int b[10000005]; int c[10000005]; int read(){ char c; while((c=getchar())<'0'||c>'9'); int sum=c-'0'; while((c=getchar())>='0'&&c<='9'){ sum=sum*10+c-'0'; } return sum; } int ha,hb,hc; int ta,tb,tc; double p; int ac,ans1,ans2; double qwq; int ans[10100005]; int cmp(int a,int b){ return a>b; } int mm=0; void gm(){ if(ha<ta&&a[ha+1]>=b[hb+1]&&a[ha+1]>=c[hc+1]){ ha++; ac=a[ha]+mm; a[ha]=0; } else { if(hb<tb&&b[hb+1]>=a[ha+1]&&b[hb+1]>=c[hc+1]){ hb++; ac=b[hb]+mm; b[hb]=0; } else { hc++; ac=c[hc]+mm; c[hc]=0; } } } int main() { n=read(); m=read(); q=read(); u=read(); v=read(); t=read(); ha=0; ta=n; hb=0; tb=0; hc=0; tc=0; for(int i=1;i<=n;i++){ a[i]=read(); } sort(a+1,a+1+n,cmp); a[n+1]=-999999999; for(int i=1;i<=m;i++){ if(i%t==0){ int maxx=a[ha+1]; maxx=max(maxx,b[hb+1]); maxx=max(maxx,c[hc+1]); printf("%d ",maxx+(i-1)*q); } gm(); ans1=(long long)ac*u/v; ans2=ac-ans1; ans1-=i*q; ans2-=i*q; b[++tb]=max(ans1,ans2); c[++tc]=min(ans1,ans2); mm+=q; } cout<<endl; for(int i=1;i<=n+m;i++){ gm(); ans[i]=ac; if(i%t==0) printf("%d ",ans[i]); } return 0; }
愤怒的小鸟
题目描述
Kiana最近沉迷于一款神奇的游戏无法自拔。
简单来说,这款游戏是在一个平面上进行的。
有一架弹弓位于(0,0)处,每次Kiana可以用它向第一象限发射一只红色的小鸟,小鸟们的飞行轨迹均为形如y=ax2+bxy=ax^2+bxy=ax2+bx的曲线,其中a,b是Kiana指定的参数,且必须满足a<0。
当小鸟落回地面(即x轴)时,它就会瞬间消失。
在游戏的某个关卡里,平面的第一象限中有n只绿色的小猪,其中第i只小猪所在的坐标为(xi,yi)。
如果某只小鸟的飞行轨迹经过了(xi,yi),那么第i只小猪就会被消灭掉,同时小鸟将会沿着原先的轨迹继续飞行;
如果一只小鸟的飞行轨迹没有经过(xi,yi),那么这只小鸟飞行的全过程就不会对第i只小猪产生任何影响。
例如,若两只小猪分别位于(1,3)和(3,3),Kiana可以选择发射一只飞行轨迹为y=−x2+4xy=-x^2+4xy=−x2+4x的小鸟,这样两只小猪就会被这只小鸟一起消灭。
而这个游戏的目的,就是通过发射小鸟消灭所有的小猪。
这款神奇游戏的每个关卡对Kiana来说都很难,所以Kiana还输入了一些神秘的指令,使得自己能更轻松地完成这个游戏。这些指令将在【输入格式】中详述。
假设这款游戏一共有T个关卡,现在Kiana想知道,对于每一个关卡,至少需要发射多少只小鸟才能消灭所有的小猪。由于她不会算,所以希望由你告诉她。
输入输出格式
输入格式:
第一行包含一个正整数T,表示游戏的关卡总数。
下面依次输入这T个关卡的信息。每个关卡第一行包含两个非负整数n,m,分别表示该关卡中的小猪数量和Kiana输入的神秘指令类型。接下来的n行中,第i行包含两个正实数(xi,yi),表示第i只小猪坐标为(xi,yi)。数据保证同一个关卡中不存在两只坐标完全相同的小猪。
如果m=0,表示Kiana输入了一个没有任何作用的指令。
如果m=1,则这个关卡将会满足:至多用⌈n3+1⌉\left \lceil \frac{n}{3} + 1 \right \rceil⌈3n+1⌉只小鸟即可消灭所有小猪。
如果m=2,则这个关卡将会满足:一定存在一种最优解,其中有一只小鸟消灭了至少⌊n3⌋\left \lfloor \frac{n}{3} \right \rfloor⌊3n⌋只小猪。
保证1<=n<=18,0<=m<=2,0<xi,yi<10,输入中的实数均保留到小数点后两位。
上文中,符号⌈x⌉\left \lceil x \right \rceil⌈x⌉和⌊x⌋\left \lfloor x \right \rfloor⌊x⌋分别表示对c向上取整和向下取整
输出格式:
对每个关卡依次输出一行答案。
输出的每一行包含一个正整数,表示相应的关卡中,消灭所有小猪最少需要的小鸟数量
输入输出样例
2 2 0 1.00 3.00 3.00 3.00 5 2 1.00 5.00 2.00 8.00 3.00 9.00 4.00 8.00 5.00 5.00
1 1
3 2 0 1.41 2.00 1.73 3.00 3 0 1.11 1.41 2.34 1.79 2.98 1.49 5 0 2.72 2.72 2.72 3.14 3.14 2.72 3.14 3.14 5.00 5.00
2 2 3
1 10 0 7.16 6.28 2.02 0.38 8.33 7.78 7.68 2.09 7.46 7.86 5.77 7.44 8.24 6.72 4.42 5.11 5.42 7.79 8.15 4.99
6
说明
【样例解释1】
这组数据中一共有两个关卡。
第一个关卡与【问题描述】中的情形相同,2只小猪分别位于(1.00,3.00)和 (3.00,3.00),只需发射一只飞行轨迹为y = -x^2 + 4x的小鸟即可消灭它们。
第二个关卡中有5只小猪,但经过观察我们可以发现它们的坐标都在抛物线 y = -x^2 + 6x上,故Kiana只需要发射一只小鸟即可消灭所有小猪。
【数据范围】
因为猪的数量少,所以我们可以用一个状态说明每个猪是死了还是活着(0代表活着,1代表死了),用g[i][j]数组表示经过i和j两只猪的抛物线能使所有的猪变成什么状态。
处理g数组的方法:枚举i和j两只猪,从n->1枚举第k只猪,枚举前g[i][j]<<=1 如果能被通过,就个g[i][j]|=1;
最后用f数组进行状压dp,枚举k为当前状态,先找到当前状态下第一个没有被杀死的猪记作i,再枚举g[i][j](j>=i)来试图杀死某些猪。最后直接输出所有猪都被杀死的状态的f就好啦~~
#include<cstdio> #include<algorithm> #include<cstring> #include<string> #include<cmath> #include<iostream> #define INF 0x3f3f3f3f #define MAXI (1 << 18) #define eps 0.0000001 using namespace std; int t,n,m; double x[50]; double y[50]; int g[20][20]; int f[MAXI+2]; bool ki(int i,int j,int k) { double a=(y[i]/x[i]-y[j]/x[j])/(x[i]-x[j]); if(a>=0) return 0; double b=(y[i]-a*x[i]*x[i])/x[i]; if(abs(a*x[k]*x[k]+b*x[k]-y[k])<eps) return 1; return 0; } int main() { scanf("%d",&t); while(t--){ scanf("%d%d",&n,&m); for(int i=1;i<=n;i++){ scanf("%lf%lf",&x[i],&y[i]); } for(int i=1;i<=n;i++){ for(int j=1;j<=n;j++){ g[i][j]=0; } } for(int k=1;k<(1 << n);k++){ f[k]=INF; } for(int i=1;i<=n;i++){ for(int j=1;j<=n;j++){ for(int k=n;k;k--){ g[i][j] <<= 1; if(ki(i,j,k)){ g[i][j] |= 1; } } if(i==j) g[i][j]=1 << (i-1); } } for(int k=0;k<(1 << n);k++){ int i; for(i=1;i<=n;i++) if(!(k & 1 << (i-1))) break; for(int j=i;i<=n;i++){ f[k | g[i][j]]=min(f[k|g[i][j]],f[k]+1); } } printf("%d\n",f[(1 << n)-1]); } return 0; }
组合数问题
题目描述
组合数CnmC_n^mCnm表示的是从n个物品中选出m个物品的方案数。举个例子,从(1,2,3) 三个物品中选择两个物品可以有(1,2),(1,3),(2,3)这三种选择方法。根据组合数的定 义,我们可以给出计算组合数的一般公式:
Cnm=n!m!(n−m)!C_n^m=\frac{n!}{m!(n - m)!}Cnm=m!(n−m)!n!
其中n! = 1 × 2 × · · · × n
小葱想知道如果给定n,m和k,对于所有的0 <= i <= n,0 <= j <= min(i,m)有多少对 (i,j)满足CijC_i^jCij是k的倍数。
输入输出格式
输入格式:
第一行有两个整数t,k,其中t代表该测试点总共有多少组测试数据,k的意义见 【问题描述】。
接下来t行每行两个整数n,m,其中n,m的意义见【问题描述】。
输出格式:
t行,每行一个整数代表答案。
输入输出样例
1 2 3 3
1
2 5 4 5 6 7
0 7
说明
【样例1说明】
在所有可能的情况中,只有C21=2C_2^1 = 2C21=2是2的倍数。
【子任务】
组合数的递推公式——c[i][j]=c[i-1][j]+c[i-1][j-1]l;
先用这个公式预处理c数组,再用s数组维护二维前缀和(0,0~2000,2000)
注意处理c数组的时候要把初始化成-1,然后递推的时候特判,不然%k之后合法的地方也是0,合法的数量就会出错。
#include<cstdio> #include<algorithm> #include<cmath> #include<cstring> #include<string> using namespace std; int t,k; int c[2025][2025]; int s[2025][2025]; void init() { memset(c,-1,sizeof(c)); c[0][0]=1; for(int i=1; i<=2000; i++) c[i][0]=1; for(int i=1; i<=2000; i++) for(int j=1; j<=2000 && j<=i; j++) c[i][j]=(c[i-1][j-1]+(c[i-1][j]==-1?0:c[i-1][j]))%k; for(int i=1; i<=2000; i++){ for(int j=1; j<=2000; j++){ s[i][j]=s[i-1][j]+s[i][j-1]-s[i-1][j-1]; if(!c[i][j]){ s[i][j]++; } } } } int main() { scanf("%d%d",&t,&k); init(); while(t--) { int n,m; scanf("%d%d",&n,&m); printf("%d\n",s[n][m]); } return 0; }
换教室
题目描述
对于刚上大学的牛牛来说,他面临的第一个问题是如何根据实际情况申请合适的课程。
在可以选择的课程中,有 2n2n2n 节课程安排在 nnn 个时间段上。在第 iii(1≤i≤n1 \leq i \leq n1≤i≤n)个时间段上,两节内容相同的课程同时在不同的地点进行,其中,牛牛预先被安排在教室 cic_ici 上课,而另一节课程在教室 did_idi 进行。
在不提交任何申请的情况下,学生们需要按时间段的顺序依次完成所有的 nnn 节安排好的课程。如果学生想更换第 iii 节课程的教室,则需要提出申请。若申请通过,学生就可以在第 iii 个时间段去教室 did_idi 上课,否则仍然在教室 cic_ici 上课。
由于更换教室的需求太多,申请不一定能获得通过。通过计算,牛牛发现申请更换第 iii 节课程的教室时,申请被通过的概率是一个已知的实数 kik_iki,并且对于不同课程的申请,被通过的概率是互相独立的。
学校规定,所有的申请只能在学期开始前一次性提交,并且每个人只能选择至多 mmm 节课程进行申请。这意味着牛牛必须一次性决定是否申请更换每节课的教室,而不能根据某些课程的申请结果来决定其他课程是否申请;牛牛可以申请自己最希望更换教室的 mmm 门课程,也可以不用完这 mmm 个申请的机会,甚至可以一门课程都不申请。
因为不同的课程可能会被安排在不同的教室进行,所以牛牛需要利用课间时间从一间教室赶到另一间教室。
牛牛所在的大学有 vvv 个教室,有 eee 条道路。每条道路连接两间教室,并且是可以双向通行的。由于道路的长度和拥堵程度不同,通过不同的道路耗费的体力可能会有所不同。 当第 iii(1≤i≤n−11 \leq i \leq n-11≤i≤n−1)节课结束后,牛牛就会从这节课的教室出发,选择一条耗费体力最少的路径前往下一节课的教室。
现在牛牛想知道,申请哪几门课程可以使他因在教室间移动耗费的体力值的总和的期望值最小,请你帮他求出这个最小值。
输入输出格式
输入格式:
第一行四个整数 n,m,v,en,m,v,en,m,v,e。nnn 表示这个学期内的时间段的数量;mmm 表示牛牛最多可以申请更换多少节课程的教室;vvv 表示牛牛学校里教室的数量;eee表示牛牛的学校里道路的数量。
第二行 nnn 个正整数,第 iii(1≤i≤n1 \leq i \leq n1≤i≤n)个正整数表示 cic_ici,即第 iii 个时间段牛牛被安排上课的教室;保证 1≤ci≤v1 \le c_i \le v1≤ci≤v。
第三行 nnn 个正整数,第 iii(1≤i≤n1 \leq i \leq n1≤i≤n)个正整数表示 did_idi,即第 iii 个时间段另一间上同样课程的教室;保证 1≤di≤v1 \le d_i \le v1≤di≤v。
第四行 nnn 个实数,第 iii(1≤i≤n1 \leq i \leq n1≤i≤n)个实数表示 kik_iki,即牛牛申请在第 iii 个时间段更换教室获得通过的概率。保证 0≤ki≤10 \le k_i \le 10≤ki≤1。
接下来 eee 行,每行三个正整数 aj,bj,wja_j, b_j, w_jaj,bj,wj,表示有一条双向道路连接教室 aj,bja_j, b_jaj,bj,通过这条道路需要耗费的体力值是 wjw_jwj;保证 1≤aj,bj≤v1 \le a_j, b_j \le v1≤aj,bj≤v, 1≤wj≤1001 \le w_j \le 1001≤wj≤100。
保证 1≤n≤20001 \leq n \leq 20001≤n≤2000,0≤m≤20000 \leq m \leq 20000≤m≤2000,1≤v≤3001 \leq v \leq 3001≤v≤300,0≤e≤900000 \leq e \leq 900000≤e≤90000。
保证通过学校里的道路,从任何一间教室出发,都能到达其他所有的教室。
保证输入的实数最多包含 333 位小数。
输出格式:
输出一行,包含一个实数,四舍五入精确到小数点后恰好222位,表示答案。你的输出必须和标准输出完全一样才算正确。
测试数据保证四舍五入后的答案和准确答案的差的绝对值不大于 4×10−34 \times 10^{-3}4×10−3。 (如果你不知道什么是浮点误差,这段话可以理解为:对于大多数的算法,你可以正常地使用浮点数类型而不用对它进行特殊的处理)
输入输出样例
3 2 3 3 2 1 2 1 2 1 0.8 0.2 0.5 1 2 5 1 3 3 2 3 1
2.80
说明
【样例1说明】
所有可行的申请方案和期望收益如下表:
【提示】
- 道路中可能会有多条双向道路连接相同的两间教室。 也有可能有道路两端连接
的是同一间教室。
2.请注意区分n,m,v,e的意义, n不是教室的数量, m不是道路的数量。
特殊性质1:图上任意两点 aia_iai, bib_ibi, aia_iai≠ bib_ibi间,存在一条耗费体力最少的路径只包含一条道路。
特殊性质2:对于所有的 $1≤ i≤ n$, ki=1k_i= 1ki=1 。
如果不会概率dp的话可以用floyd得到28分。然而感觉这个转移方程想出来了其实就。。。挺简单?
用f[i][j][0/1]表示已经过去了i的时间,j表示已经用过的换教室的次数,0/1表示第i个时间是否试图更换教室。每次可以用i-1的状态更新i的状态,对于这一次换了/没换分别算出前一次换了/没换的期望路程,分别用最小值更新当前状态。
因为最终的答案m可以用任意次,所以要遍历j求最小值。
#include<cstdio> #include<algorithm> #include<cstring> #include<iostream> #include<string> #include<cmath> using namespace std; int n,m,v,e; int c[2005]; int d[2005]; double k[2005]; int f[305][305]; void init() { for(int k=1;k<=v;k++){ for(int i=1;i<=v;i++){ for(int j=1;j<=v;j++){ if(f[i][k]+f[k][j]<f[i][j]) f[i][j]=f[i][k]+f[k][j]; } } } } double dp[2005][2005][2],ans=1e30; int main() { scanf("%d%d%d%d",&n,&m,&v,&e); for(int i=1;i<=n;i++) scanf("%d",&c[i]); for(int i=1;i<=n;i++) scanf("%d",&d[i]); for(int i=1;i<=n;i++) scanf("%lf",&k[i]); for(int i=1;i<=v;i++){ for(int j=1;j<=v;j++){ if(i==j) f[i][j]=0; else f[i][j]=999999999; } } for(int i=1;i<=e;i++){ int a,b,w; scanf("%d%d%d",&a,&b,&w); f[a][b]=min(f[a][b],w); f[b][a]=min(f[b][a],w); } init(); if(n==1){ printf("0.00\n"); return 0; } if(!m){ int ans=0; for(int i=1;i<n;i++){ ans+=f[c[i]][c[i+1]]; } printf("%.2lf",(double)ans); return 0; } for(int i=1;i<=n;i++){ for(int j=0;j<=m;j++){ dp[i][j][0]=dp[i][j][1]=1e30; } } dp[1][0][0]=dp[1][1][1]=0;//第一次换了或者没换,期望的路程都是0 for(int i=2;i<=n;i++){ for(int j=0;j<=m;j++){ double q1=dp[i-1][j][0]+f[c[i-1]][c[i]]; double q2=dp[i-1][j][1]+f[c[i-1]][c[i]]*(1-k[i-1])+f[d[i-1]][c[i]]*k[i-1]; dp[i][j][0]=min(q1,q2); if(j>=1){//如果这次换教室 q1=dp[i-1][j-1][0]+f[c[i-1]][c[i]]*(1-k[i])+f[c[i-1]][d[i]]*k[i]; q2=dp[i-1][j-1][1]; q2+=f[c[i-1]][c[i]]*(1-k[i-1])*(1-k[i]); q2+=f[c[i-1]][d[i]]*(1-k[i-1])*k[i]; q2+=f[d[i-1]][d[i]]*k[i-1]*k[i]; q2+=f[d[i-1]][c[i]]*k[i-1]*(1-k[i]); dp[i][j][1]=min(q1,q2); } } } for(int i=0;i<=m;i++){ ans=min(ans,dp[n][i][0]); ans=min(ans,dp[n][i][1]); } printf("%.2lf\n",ans); return 0; }