10.1
国庆节!祝祖国母亲生日快乐!
上午联考,昨晚玩得太晚,第二天没精神,打完第一题暴力后实在读不懂第二题题意。第三题概率更是直接放弃。最后越看越困……
最后,第一题暴力还打炸了……早知道我就去看阅兵式了!浪费这么多时间,还错过了阅兵式,唉。
听老师讲课,教室其他学校的同学里吵得让人发毛,啥也没听到,啥也没学着,就这么虚度了一下午。
换根dp考过好几次了,一次都不会,晚上看了看进阶指南。
10.9
补课期间鸽了一周……
接下来就随便刷题了,但不知道该往哪个方向……有点迷。
10.10
Acwing 的活动 AC Saber 还不错,整理了很多经典dp题。可在下周内完成(当然不一定能做完……)。
近年的noip原题要在考前过一遍,建议使用一本通oj。
10.11
做一做图论。Luogu 试炼场 里的题很不错
最小瓶颈生成树(瓶颈MST),即最长边最短的树。有结论:一棵最小生成树必定是一棵最小瓶颈生成树,但最小瓶颈生成树不一定是最小生成树。
10.13
Luogu P1268 树的重量
考思维的题目,我这种智力只能一边看题解一遍冥思苦想,花很久时间才勉强能理解[多多笑哭]。
两个点时:
树的重量当然就是二者距离 m[1][2] 。
出现第三个点时:
答案增加了红色部分,其计算方法为 (m[1][3] + m[2][3] - m[1][2] )/ 2 。
出现第四个点时,与三个点思考方式相同,也是找两个点与第四个点以上面的方式计算对答案的贡献(增加量),为方便我们将其中一个点定为1。
那么,另外一个点(设为 j,j<4)是否可以是任何点呢?
将1、4代入上文的式子得:m[1][4] + m[j][4] - m[1][j]。
j取不同值时,m[1][4]值相同,但m[j][4] - m[1][j] 的值不同。这时,我们就要取m[j][4] - m[1][j]的值最小的 j 。如图:
因为树的形状固定,我们不妨假设4号点应该处在1号点与3号点的路径上,即,答案真正增加的值为(m[1][4]+m[3][4]-m[1][3])/ 2。但如果我们将2号点代入计算,实际得到的值为(m[1][4]+m[2][4]-m[1][2])/ 2
棕色圆圈出的一段路径本不应加入答案(算第三个点时已经加入答案了)却被算了进去。即,所有不正确的 j 算出的m[1][4] + m[j][4] - m[1][j]都会比正确答案大,因为多加了边。
所以我们要找使 m[1][4] + m[j][4] - m[1][j] 最小的 j ,否则就会加入不应加入的边。
以后每加一个点就按照第四个点的做法加入答案。
10.14
这几天差不多是做一天dp,做一天图论,中间可能有变动,有时也要做一些搜索题。
一本通oj 1271:【例9.15】潜水员
二维dp题目,发现自己对背包问题还掌握的不牢固,以前虽专门学习过 dd大牛的背包九讲 ,今天做题时又忘了。
这题与一般的二维dp稍有不同:本题是在至少取m升氧、n升氮的情况下求重量的最低值,而其他题的限制条件一般是最多取某个值的情况。这也导致了我没看出来m,n应该为背包空间。不过这无关紧要。只要按照完全背包做即可。
#include<bits/stdc++.h>
using namespace std;
const int maxn=1005;
int n,x,y,ans,mb,mc;
int a[maxn],b[maxn],c[maxn],f[maxn][maxn];
int main(){
//freopen("input.txt","r",stdin);
scanf("%d%d",&x,&y);
scanf("%d",&n);
for(int i=1;i<=n;++i){
scanf("%d%d%d",&a[i],&b[i],&c[i]);
mb=max(mb,b[i]);mc=max(mc,c[i]);
}
memset(f,0x3f,sizeof(f));
f[0][0]=0;
ans=0x3f3f3f3f;
int z;
for(int i=1;i<=n;++i){
for(int j=x+min(mb,x);j>=0;--j){
for(int k=y+min(mc,y);k>=0;--k){
int nj=j-a[i]<0 ? 0:j-a[i],nk=k-b[i]<0 ? 0:k-b[i];
f[j][k]=min(f[j][k],f[nj][nk]+c[i]);
if(j>=x&&k>=y) ans=min(ans,f[j][k]);
}
}
}
printf("%d",ans);
return 0;
}
10.19
又鸽了很久……
今天考初赛……
我已退役,感觉良好
10.20
接下来要尽量把历年(至少五年内)noip原题做一遍。
做题记录另开一坑
10.24
1024 是我们(程序员)的生日!
今天其实也是我的生日(难道上天注定要我当个码农?)。
最近在看牛客网的学习视频。但因为视频有点多,且部分内容看上去比较基础,所以我主要选择看那些将dp的视频。
一道概率dp:
牛客网 [编程题]图上行走
概率期望dp,一般从结束位置开始推向初始位置(口胡的)。如本题,就是用 f[u] 表示从 u 点走到 n 点的长度期望,依次求出从 n 到 1 的 f 值。
一开始我设 f[u] 为从1走到 u 的期望长度,想用拓扑排序从 1 到 n 求出 f 值。out[u] 表示 u 的出度。
void bfs(){
queue<int>q;
for(int i=1;i<=n;++i) if(!in[i]) q.push(i);
//q.push(n);
while(!q.empty()){
int u=q.front();
q.pop();
for(int i=head[u];i;i=e[i].nxt){
int v=e[i].v;
double w=e[i].w;
f[v]+=(f[u]+w)/out[u];
--in[v];
if(!in[v]) q.push(v);
}
}
}
但实际上进入一个节点的期望需要乘上的概率还与它之前的节点有关,该思想是错误的。如下图:
a—>b—>c 这一段,算出来对 f[c] 的贡献是 1/2+2=5/2。而实际上是因为b—>c的路径长度要乘上的概率除了与由 b 点进入 c 点的概率有关外,还与 a 点进入 b 点的概率有关。实际上应为(1+2)*1/2=3/2。这样是错误的,而且若是强行维护也可能会很麻烦(反正我没搞出来)。
正解是用 f[u] 表示从 u 点走到 n 点的长度期望,初始设 f[n] 为0。则如下图:(E表期望,相当于我设的 f)
(图片来自上文所说的学习视频)
每个点的 f 只与它之后的点有关。我们可以用记忆化搜索来求每个点的 f 值:
double dfs(int u){
if(u==n) return 0;
if(f[u]>0) return f[u];
for(int i=head[u];i;i=e[i].nxt){
int v=e[i].v,w=e[i].w;
f[u]+=(dfs(v)+w)/out[u];
}
return f[u];
}
或者也可像我刚刚的错误思路一样使用拓扑排序,只不过要建反图。
#include<bits/stdc++.h>
using namespace std;
const int maxn=100005;
int n,m,cnt;
int head[maxn],out[maxn],in[maxn];
double f[maxn];
struct edge{
int v,nxt;
double w;
}e[maxn<<1];
void add(int u,int v,int w){
e[++cnt].nxt=head[u];
e[cnt].v=v;
e[cnt].w=w;
head[u]=cnt;
}
void bfs(){
queue<int>q;
for(int i=1;i<=n;++i) if(!in[i]) q.push(i);
//q.push(n);
while(!q.empty()){
int u=q.front();
q.pop();
for(int i=head[u];i;i=e[i].nxt){
int v=e[i].v;
double w=e[i].w;
f[v]+=(f[u]+w)/out[v];
--in[v];
if(!in[v]) q.push(v);
}
}
}
int main(){
//freopen("input.txt","r",stdin);
scanf("%d%d",&n,&m);
for(int x,y,z,i=1;i<=m;++i){
scanf("%d%d%d",&x,&y,&z);
add(y,x,z);++out[x],++in[x];
}
bfs();
printf("%.2f",f[1]);
return 0;
}
一个细节:题目上说“四舍五入保留两位小数”,所以我将答案加上0.005,但一直WA掉。去掉之后就通过了???
[编程题]排队
第一眼:woc感觉好难啊,怎么设计不了状态?怎么转移啊?还要求全排列?阶乘复杂度?
看了讲解视频:我是ZZ……
对于第 i 个人,我们单独考虑任意一个其他的人 j 对 i 做的贡献。已知 i,j 二者的先后顺序有很多种,但 i 在 j 前面与 j 在 i 前面的概率一定相同,都是1/2(这个应该不难理解吧)。
实在不懂的话随便打个全排列(1到3):
1 2 3;1 3 2;2 1 3;2 3 1;3 1 2;3 2 1。
令 i 为1,j 为2,i 在 j 前面的情况有1 2 3;1 3 2;3 1 2。j 在 i 前面的情况有2 1 3;2 3 1;3 2 1。在所有的情况中都各占1/2。
所以对于每个 j ,只有当它在 i 前面时才会对 i 的等待时间产生贡献,而这样的概率为1/2,期望即为 t[j] * 1/2。
所以每个 i 的期望等待时间即为其他所有人的看病时间除以2。
唉,思维还是太僵化了,一些看上去很难的题其实可以往简单的方面去想。做题时需要发散思维。
#include<bits/stdc++.h>
using namespace std;
const int maxn=1005;
int n,sum;
int t[maxn];
int main(){
scanf("%d",&n);
for(int i=1;i<=n;++i){
scanf("%d",&t[i]);
sum+=t[i];
}
for(int i=1;i<=n;++i){
printf("%.2f ",(double)(sum-t[i])/2);
}
return 0;
}