梦开始的地方(Noip模拟3) 2021.5.24
作者:互联网
T1 景区路线规划(期望dp/记忆化搜索)
游乐园被描述成一张n个点,m条边的无向图(无重边,无自环)。每个点代表一个娱乐项目,第i个娱乐项目需要耗费c[i]分钟的时间,会让小ma和妹子的开心度分别增加h1[i],h2[i],他们俩初始的开心度都是0。每条边代表一条路,第i条边连接编号为x[i],y[i]的两个娱乐项目,从x[i]走到y[i]或者从y[i]走到x[i]耗费的时间都是t[i]分钟。小ma和妹子预计在游乐园里玩k分钟。最开始的时候,小ma和妹子会等概率的随机选择一个娱乐项目开始玩,每玩完一个项目后,小ma和妹子会等概率的随机选择一个可以从当前项目直达的且来得及玩的项目作为下一个项目。如果玩完一个项目后周围没有可以直达的且来得及玩的项目,小ma和妹子就会提前结束游玩。 请你分别计算小ma和妹子在游玩结束后开心度的期望
输入第一行给出三个空格隔开的整数,分别表示n,m,k;
接下来的n行,每行三个空格隔开的整数,分别表示c[i],h1[i],h2[i];
接下来的m行,每行三个空格隔开的整数,分别表示x[i],y[i],t[i];
输出两个用空格隔开的实数,分表表示小ma和妹子开心度的期望,精确到小数点后 5 位。
a.in:
5 4 60
25 12 83
30 38 90
16 13 70
22 15 63
50 72 18
2 1 7
3 1 7
4 3 1
5 3 10
a.out
39.20000 114.40000
一看题目发现肯定是概率期望题,再仔细想想这三天做的题,就知道是个期望dp。
考试思路(错):
因为聪聪与可可的10分打法根深蒂固,导致在考试时想到了用深搜(就不知道当时为什么没想到用那个题的正解思路.....)
深搜其实打的也挺对的,应该是能拿到55分(因为没用记忆化和数组记录更新状态),可是还犯了一个致命错误: 出度没有找对!!
看题:每玩完一个项目后,小y和妹子会等概率的随机选择一个可以从当前项目直达的且来得及玩的项目作为下一个项目。
意思就是每次的出度会可能跟刚开始初始化记录的出度不统一,于是应每次都找出度:
for(int i=r[st];i;i=e[i].next){
int v=e[i].to;
if(time+e[i].value+s[v].c>k) continue;
out++;
}
每次的概率都是之前的概率连乘出来的。
这样就可以写出来了:
1 #include<bits/stdc++.h> 2 using namespace std; 3 const int NN=10005; 4 int n,m,k,tmp; 5 double hh1,hh2,H1[105][485],H2[105][485]; 6 struct node{ 7 int c,h1,h2; 8 };node s[105]; 9 struct SNOW{ 10 int to,next,value; 11 };SNOW e[NN]; int tot,r[NN]; 12 inline void add(int x,int y,int z){ 13 e[++tot]=(SNOW){y,r[x],z}; 14 r[x]=tot; 15 } 16 inline double dfs1(int st,int time){ 17 int out=0; 18 if(time>k) return 0.0; 19 if(H1[st][time]) return H1[st][time]; 20 for(int i=r[st];i;i=e[i].next){ 21 int v=e[i].to; 22 if(time+e[i].value+s[v].c>k) continue; 23 out++; 24 } 25 H1[st][time]=s[st].h1; 26 if(!out) return H1[st][time]; 27 for(int i=r[st];i;i=e[i].next){ 28 int v=e[i].to; 29 H1[st][time]+=dfs1(v,time+e[i].value+s[v].c)/out; 30 } 31 return H1[st][time]; 32 } 33 inline double dfs2(int st,int time){ 34 int out=0; 35 if(time>k) return 0.0; 36 if(H2[st][time]) return H2[st][time]; 37 for(int i=r[st];i;i=e[i].next){ 38 int v=e[i].to; 39 if(time+e[i].value+s[v].c>k) continue; 40 out++; 41 } 42 H2[st][time]=s[st].h2; 43 if(!out) return H2[st][time]; 44 for(int i=r[st];i;i=e[i].next){ 45 int v=e[i].to; 46 H2[st][time]+=dfs2(v,time+e[i].value+s[v].c)/out; 47 } 48 return H2[st][time]; 49 } 50 namespace WSN{ 51 inline int main(){ 52 scanf("%d%d%d",&n,&m,&k); 53 for(int i=1;i<=n;i++) 54 scanf("%d%d%d",&s[i].c,&s[i].h1,&s[i].h2); 55 for(int i=1,x,y,z;i<=m;i++){ 56 scanf("%d%d%d",&x,&y,&z); 57 add(x,y,z); add(y,x,z); 58 } 59 for(int i=1;i<=n;i++){ 60 hh1+=dfs1(i,s[i].c)/n; 61 hh2+=dfs2(i,s[i].c)/n; 62 } 63 printf("%.5lf %.5lf",hh1,hh2); 64 return 0; 65 } 66 } 67 signed main(){return WSN::main();}View Code
小结:
概率期望专题里这种题型不少见,感觉比聪聪与可可简单一些,还是要记住正解代码呀!!!
T2 [中山市选2009] 树(树规/高斯消元)
图论中的树为一个无环的无向图。给定一棵树,每个节点有一盏指示灯和一个按钮。如果节点的按扭被按了,那么该节点的灯会从熄灭变为点亮(当按之前是熄灭的),或者从点亮到熄灭(当按之前是点亮的)。并且该节点的直接邻居也发生同样的变化。 开始的时候,所有的指示灯都是熄灭的。请编程计算最少要按多少次按钮,才能让所有节点的指示灯变为点亮状态。
输入文件有多组数据。
输入第一行包含一个整数n,表示树的节点数目。每个节点的编号从1到n。
输入接下来的n – 1行,每一行包含两个整数x,y,表示节点x和y之间有一条无向边。
当输入n为0时,表示输入结束。
对于每组数据,输出最少要按多少次按钮,才能让所有节点的指示灯变为点亮状态。每一组数据独占一行。
a.in:
3
1 2
1 3
0
a.out:
1
看到题目第一个肯定想到高斯消元,数据也比较合适,刚好也特别像专题里的开关问题,于是。。。我们讲讲正解的树规(俏展了)
状态数组:dp[n][2][2]表示第n个点操作(摁或不摁)后的状态(开或关);
状态转移也分成四种情况来搞,就差不多了。
最终状态:min(dp[1][1][1],dp[1][0][1]);
其他の注意 :
1.建边的数组开二倍(要不然MLE40);
2.要提前将dfs的点的状态值保存,要不然以后转移状态会串数。。。
3.不能将两种暂时不合法状态赋太大的值,否则转移会爆long long
1 #include<bits/stdc++.h> 2 using namespace std; 3 const int NN=105; 4 int n,g[105][2][2];//i->节点x 0/1->不按/按x 0/1->不开/开x 5 struct SNOW{ 6 int to,next; 7 };SNOW e[NN<<1]; int tot,r[NN<<1]; 8 inline void add(int x,int y){ 9 e[++tot]=(SNOW){y,r[x]}; 10 r[x]=tot; 11 } 12 inline void snow(int x,int fa){ 13 g[x][0][0]=0; g[x][1][1]=1; g[x][1][0]=n+1; g[x][0][1]=n+1; 14 for(int i=r[x];i;i=e[i].next){ 15 int v=e[i].to; 16 if(v==fa) continue; 17 snow(v,x); 18 int g00=g[x][0][0],g01=g[x][0][1],g10=g[x][1][0],g11=g[x][1][1];//提前记录,否则状态转移时值会改变 19 g[x][0][0]=min(g01+g[v][1][1],g00+g[v][0][1]); 20 g[x][0][1]=min(g00+g[v][1][1],g01+g[v][0][1]); 21 g[x][1][0]=min(g11+g[v][1][0],g10+g[v][0][0]); 22 g[x][1][1]=min(g10+g[v][1][0],g11+g[v][0][0]); 23 } 24 } 25 namespace WSN{ 26 inline int main(){ 27 while(1){ 28 memset(g,0,sizeof(g)); 29 memset(r,0,sizeof(r)); 30 tot=0; 31 scanf("%d",&n); 32 if(n==0) break; 33 for(int i=1,x,y;i<n;i++){ 34 scanf("%d%d",&x,&y); 35 add(x,y); add(y,x); 36 } 37 snow(1,0); 38 printf("%d\n",min(g[1][1][1],g[1][0][1])); 39 } 40 return 0; 41 } 42 } 43 signed main(){return WSN::main();}View Code
附上特殊的高斯消元打法
1 #include<bits/stdc++.h> 2 using namespace std; 3 const int NN=105; 4 int n,g[NN][NN],wsn; 5 inline void Guass(){ 6 int cnt=1; 7 for(int i=1;i<=n;i++){ 8 int r=cnt; 9 for(int j=cnt+1;j<=n;j++) if(g[j][i]>g[r][i]) r=j; 10 if(!g[r][i]) continue; 11 if(r!=cnt) for(int j=i;j<=n+1;j++) swap(g[r][j],g[cnt][j]); 12 for(int j=1;j<=n;j++) 13 if(j!=cnt&&g[j][i])//将所有的系数都变历一遍,保证只剩下自由元 14 for(int k=i;k<=n+1;k++) g[j][k]^=g[cnt][k]; 15 cnt++; 16 } 17 } 18 inline void dfs(int x,int line){//暴力搜索自由元 19 if(line>=wsn) return; 20 if(!x){ wsn=line; return;} 21 if(g[x][x]) dfs(x-1,line+g[x][n+1]); 22 else{ 23 if(g[x][n+1]) return; 24 dfs(x-1,line); 25 for(int i=x;i>=1;i--) g[i][n+1]^=g[i][x]; 26 dfs(x-1,line+1); 27 for(int i=x;i>=1;i--) g[i][n+1]^=g[i][x]; 28 } 29 } 30 namespace WSN{ 31 inline int main(){ 32 while(1){ 33 wsn=0x7fffffff; 34 memset(g,0,sizeof(g)); 35 scanf("%d",&n); 36 if(n==0) break; 37 for(int i=1,x,y;i<n;i++){ 38 scanf("%d%d",&x,&y); 39 g[x][y]=g[y][x]=1; 40 } 41 for(int i=1;i<=n;i++) g[i][i]=g[i][n+1]=1; 42 Guass(); 43 dfs(n,0); 44 printf("%d\n",wsn); 45 } 46 return 0; 47 } 48 } 49 signed main(){return WSN::main();}View Code
T3 奇怪的道路(状压dp)
小宇从历史书上了解到一个古老的文明。这个文明在各个方面高度发达,交通方面也不例外。考古学家已经知道,这个文明在全盛时期有n座城市,编号为1..n。m条道路连接在这些城市之间,每条道路将两个城市连接起来,使得两地的居民可以方便地来往。一对城市之间可能存在多条道路。 据史料记载,这个文明的交通网络满足两个奇怪的特征。首先,这个文明崇拜数字K,所以对于任何一条道路,设它连接的两个城市分别为u和v,则必定满足1 <=|u - v| <= K。此外,任何一个城市都与恰好偶数条道路相连(0也被认为是偶数)。不过,由于时间过于久远,具体的交通网络我们已经无法得知了。小宇很好奇这n个城市之间究竟有多少种可能的连接方法,于是她向你求助。 方法数可能很大,你只需要输出方法数模1000000007后的结果。
输入共一行,为3个整数n,m,K。
输出1个整数,表示方案数模1000000007后的结果。
【输入样例1】
3 4 1
【输入样例2】
4 3 3
【输出样例1】
3
【输出样例2】
4
考试时候真的没看出来是状压,那个1000000007看着太像排列组合里的东西了(还是太弱。。。)
状压思路有点像动物园;
得出状态转移数组: dp[i][j][u][t]表示第i个结点用了j条边情况下的前u位连边数状态(奇数0,偶数1),i的前t个点是第四维开的意义:
思路:
1.循环的时候避免开头几位找的数大,用一个min(i,k)来卡住范围;
2.如果不连: dp[i][j][u][t-1]+=dp[i][j][u][t];
3.如果连: dp[i][j+1][u^1^(1<<t)][t]+=dp[i][j][u][t];
4.2,3步将会把所有状态转移至第四维是0上;
5.如果开头是偶数,可以进行下一个节点的转移:dp[i+1][j][u<<1][min(i,k)]+=dp[i][j][u][0];
6.最终状态: dp[n][m][0][0];
7.初始状态: dp[1][0][0][0]=1;
1 #include<bits/stdc++.h> 2 #define int long long 3 using namespace std; 4 const int momomo=1000000007; 5 int n,m,k,dp[35][35][1<<10][10]; 6 namespace WSN{ 7 inline int main(){ 8 scanf("%lld%lld%lld",&n,&m,&k); 9 dp[1][0][0][0]=1; 10 for(int i=1;i<=n;i++) 11 for(int j=0;j<=m;j++) 12 for(int u=0;u<(1<<k+1);u++){ 13 for(int t=min(i,k);t>=1;t--){ 14 dp[i][j][u][t-1]=(dp[i][j][u][t-1]+dp[i][j][u][t])%momomo; 15 dp[i][j+1][u^1^(1<<t)][t]=(dp[i][j+1][u^1^(1<<t)][t]+dp[i][j][u][t])%momomo; 16 } 17 if(!((u>>k)&1)) dp[i+1][j][u<<1][min(i,k)]=(dp[i+1][j][u<<1][min(i,k)]+dp[i][j][u][0])%momomo; 18 } 19 printf("%lld\n",dp[n][m][0][0]); 20 return 0; 21 } 22 } 23 signed main(){return WSN::main();}View Code
马の思
由于种种原因吧,这次考的比较崩,这三道题改下来感觉自己拿100+还是可行的,毕竟第一题确实比较水。
搭嘎(但是),毕竟自己只是拿了9分,就没什么可吹嘘的理由了,只能是暂且埋头苦干吧,
5.24已是历史,抬头往前看才有阳光。。。。暂且鼓励自己一波
马の想
SNack:
你们快集训了吧,暑假要加油鸭!
小小马:
肯定会的,并且一直想你。。
标签:24,2021.5,Noip,int,st,time,return,dp,out 来源: https://www.cnblogs.com/hzoi-wsn/p/14890641.html