(1)第一题
财富(treasure)
Time Limit:1000ms Memory Limit:128MB
题目描述
LYK有n个小伙伴。每个小伙伴有一个身高hi。
这个游戏是这样的,LYK生活的环境是以身高为美的环境,因此在这里的每个人都羡慕比自己身高高的人,而每个人都有一个属性ai表示它对身高的羡慕值。
这n个小伙伴站成一列,我们用hi来表示它的身高,用ai来表示它的财富。
每个人向它的两边望去,在左边找到一个最近的比自己高的人,然后将ai朵玫瑰给那个人,在右边也找到一个最近的比自己高的人,再将ai朵玫瑰给那个人。当然如果没有比自己身高高的人就不需要赠送别人玫瑰了。也就是说一个人会给0,1,2个人玫瑰(这取决于两边是否有比自己高的人)。
每个人都会得到若干朵玫瑰(可能是0朵),LYK想知道得了最多的玫瑰的那个人得了多少玫瑰。(然后嫁给他>3<)
输入格式(treasure.in)
第一行一个数n表示有n个人。
接下来n行,每行两个数hi,ai。
输出格式(treasure.out)
一个数表示答案。
输入样例
3
4 7
3 5
6 10
输出样例
12
样例解释
第一个人会收到5朵玫瑰,第二个没人送他玫瑰,第三个人会收到12朵玫瑰。
数据范围
对于50%的数据n<=1000,hi<=1000000000。
对于另外20%的数据n<=50000,hi<=10。
对于100%的数据1<=n<=50000,1<=hi<=1000000000。1<=ai<=10000。
这道题从某种程度上来说是道一眼题。因为一眼就能看出O(n^2)的做法。但作为一个有追求的人,我还是认真的思考了一下有没有O(n)的办法,毕竟O(n^2)估计不能A。但后来也没想出来,所以就……嗯。
我的代码(没想到竟然拿满了,大概是数据有点弱):
1 #include <iostream> 2 #include <cmath> 3 #include <cstring> 4 #include <cstdio> 5 #include <cstdlib> 6 #include <algorithm> 7 using namespace std; 8 long long a[50101],h[50101]; 9 long long sum[50101]; 10 int main() 11 { 12 13 freopen("treasure.in","r",stdin); 14 freopen("treasure.out","w",stdout); 15 16 int n; 17 scanf("%d",&n); 18 long long minn=1000000005,maxn=0; 19 for(int i=1;i<=n;i++) 20 { 21 scanf("%lld%lld",&h[i],&a[i]); 22 minn=min(minn,h[i]); 23 maxn=max(maxn,h[i]); 24 } 25 for(int i=1;i<=n;i++) 26 { 27 if(a[i]==maxn) continue; 28 if(a[i]==minn) {sum[i-1]+=a[i];sum[i+1]+=a[i];continue;} 29 for(int j=i-1;j>0;j--) 30 { 31 if(h[j]>h[i]) {sum[j]+=a[i];break;} 32 } 33 for(int j=i+1;j<=n;j++) 34 { 35 if(h[j]>h[i]) {sum[j]+=a[i];break;} 36 } 37 } 38 long long ans=0; 39 for(int i=1;i<=n;i++) ans=max(ans,sum[i]); 40 printf("%lld",ans); 41 //system("pause"); 42 return 0; 43 }
后来老师讲了O(n)的做法。然而好像有点没听懂……代码如下 : @周老师
1 #include <cmath> 2 #include <cstdio> 3 #include <cstdlib> 4 #include <iostream> 5 #include <algorithm> 6 using namespace std; 7 int n,s[50002],d[50002],ans[50002],ANS,a[50002],b[50002],r,i; 8 //b[i]表示第i个人收到的玫瑰数 9 //s[]单调队列 10 //d[i]在单调的数列中第i个位置是n个人中的哪个人 11 int main() 12 { 13 freopen("treasure.in","r",stdin); 14 freopen("treasure.out","w",stdout); 15 cin>>n; 16 for (i=1; i<=n; i++) scanf("%d%d",&a[i],&b[i]); 17 s[1]=a[1]; d[1]=1; r=1; 18 for (i=2; i<=n; i++) 19 { 20 while (r!=0 && a[i]>s[r]) {ans[i]+=b[d[r]]; r--; } 21 r++; //队列中元素+1 22 s[r]=a[i];//推入单调队列 23 d[r]=i; 24 } 25 s[1]=a[n]; d[1]=n; r=1; 26 for (i=n-1; i>=1; i--) 27 { 28 while (r!=0 && a[i]>s[r]) { ans[i]+=b[d[r]]; r--; } 29 r++; 30 s[r]=a[i]; 31 d[r]=i; 32 } 33 for (i=1; i<=n; i++) ANS=max(ANS,ans[i]); 34 cout<<ANS; 35 return 0; 36 }
(2)第二题
正方形(square)
Time Limit:1000ms Memory Limit:128MB
题目描述
在一个10000*10000的二维平面上,有n颗糖果。
LYK喜欢吃糖果!并且它给自己立了规定,一定要吃其中的至少C颗糖果!
事与愿违,LYK只被允许圈出一个正方形,它只能吃在正方形里面的糖果。并且它需要支付正方形边长的价钱。
LYK为了满足自己的求食欲,它不得不花钱来圈一个正方形,但它想花的钱尽可能少,你能帮帮它吗?
输入格式(square.in)
第一行两个数C和n。
接下来n行,每行两个数xi,yi表示糖果的坐标。
输出格式(square.out)
一个数表示答案。
输入样例
3 4
1 2
2 1
4 1
5 2
输出样例
4
样例解释
选择左上角在(1,1),右下角在(4,4)的正方形,边长为4。
数据范围
对于30%的数据n<=10。
对于50%的数据n<=50。
对于80%的数据n<=300。
对于100%的数据n<=1000。1<=xi,yi<=10000。
我就是两两的枚举端点,然后用这两个端点扩出一个最小的正方形,之后搜一遍里面有多少点。如果点数够了,就与当前的ans比较,保留小的。但是只过了两个点,其他的都结果错误。可能是因为我默认正方形的左上角(或右上角)有一颗糖框住,但其实不一定。
我的代码:
1 #include <iostream> 2 #include <cmath> 3 #include <cstring> 4 #include <cstdio> 5 #include <cstdlib> 6 #include <algorithm> 7 using namespace std; 8 int n,C; 9 int xz,yz,xy,yy; 10 int maxn; 11 struct point 12 { 13 int x,y; 14 bool operator <(const point&o)const 15 { 16 if(x<o.x) return true; 17 else if(x==o.x && y<o.y) return true; 18 else return false; 19 } 20 }a[1010]; 21 void make_square(int i,int j) 22 { 23 maxn=0; 24 maxn=max(abs(a[j].y-a[i].y)+1,abs(a[j].x-a[i].x)+1); 25 //cout<<maxn<<endl; 26 if(a[i].y<=a[j].y) {xz=a[i].x;yz=a[i].y;xy=xz+maxn-1;yy=yz+maxn-1;} 27 else 28 { 29 xz=a[i].x;yz=a[i].y-maxn+1; 30 xy=a[i].x+maxn-1;yy=a[i].y; 31 } 32 } 33 bool check() 34 { 35 int cnt=0; 36 for(int i=1;i<=n;i++) 37 { 38 if(a[i].x>=xz && a[i].x<=xy && a[i].y>=yz && a[i].y<=yy) cnt++; 39 if(cnt>=C) break; 40 } 41 if(cnt>=C) return true; 42 else return false; 43 } 44 45 int main() 46 { 47 freopen("square.in","r",stdin); 48 freopen("square.out","w",stdout); 49 scanf("%d%d",&C,&n); 50 for(int i=1;i<=n;i++) scanf("%d%d",&a[i].x,&a[i].y); 51 sort(a+1,a+n+1); 52 //for(int i=1;i<=n;i++) cout<<a[i].x<<" "<<a[i].y<<endl; 53 int ans=10005; 54 for(int i=1;i<n;i++) 55 { 56 for(int j=i+1;j<=n;j++) 57 { 58 make_square(i,j); 59 if(check()) ans=min(ans,maxn); 60 } 61 } 62 printf("%d",ans); 63 //system("pause"); 64 return 0; 65 }
正解思路:
1.正方形的上、下、左、右一定有和糖果贴在一起的。不然会浪费。
因此我们可以枚举上下左,之后找一个合适的右边。最后再把这个长方形扩成正方形。
------------------------------插播:离散化---------------------------
1 Struct node{int x,y;}t[10005]; 2 In cmp(node I,node j) {i.y<j.y;} 3 4 For(int i=1;i<=n;i++) cin>>a[i]; 5 For(int i=1;i<=n;i++) t[i].x=I,t[i].y=a[i]; 6 Sort(t+1,t+n+1,cmp); 7 For(int i=1;i<=n;i++) 8 { 9 If(t[i].y!=t[i-1].y) now++; 10 P[now]=a[t[i],x]; 11 A[t[i].x]=now; 12 }
【例子】:
离散前:3 3 1 5
离散后:2 2 1 3
由此可见,离散化的作用是缩小空间。正好适用于这道题(由原来的10000*10000变为n*n的大小,二维空间大大缩小)
--------------------------------插播结束--------------------------------
算法改进:枚举上边和下边。左边为1的情况下,右边是什么随着左边向右移动,右边也一定向右移动。
左边至多移动n次,右边也至多移动n次,总共2n次
O(n^3)
继续改进:
我们发现,本题答案是连续的。即,如果边长为x可行,边长为x+1也可行(至少覆盖C个糖果);假如x不可行,x-1也不可行。
由此可知,我们可以二分答案。
L,r判断mid是否可行 --->正常的二分
之后,
枚举上边在哪里,下边的位置是固定的。哪些糖果被夹在这段区间中 O(n)
再之后
左边为1的情况下,右边是什么
随着左边向右移动,右边也一定向右移动。
左边至多移动n次,右边也至多移动n次,总共2n次 O(n)
P.s:也可以用前缀和来做
标程:
1 #include <cmath> 2 #include <cstdio> 3 #include <cstdlib> 4 #include <iostream> 5 #include <algorithm> 6 using namespace std; 7 struct node {int x,y;} a[1005]; 8 int C,n,L,R,mid,b[1005],o,i; 9 int cmp(node i,node j) {return i.x<j.x;} 10 int CMP(int i,int j) {return i<j;} 11 bool WORK(int l,int r) 12 { 13 if (r-l+1<C) return false; o=0; 14 for (int i=l; i<=r; i++) b[++o]=a[i].y; 15 sort(b+1,b+o+1,CMP); 16 for (int i=C; i<=o; i++) 17 if (b[i]-b[i-C+1]<=mid) return true; 18 return false; 19 } 20 bool OK(int x) 21 { 22 int l=1; 23 for (int i=1; i<=n; i++) //枚举上边 24 { 25 if (a[i].x-a[l].x>x) 26 { 27 if (WORK(l,i-1)) return true; 28 while (a[i].x-a[l].x>x) l++; 29 } 30 } 31 if (WORK(l,n)) return true; 32 return false; 33 } 34 int main() 35 { 36 freopen("square.in","r",stdin); 37 freopen("square.out","w",stdout); 38 scanf("%d%d",&C,&n); 39 for (i=1; i<=n; i++) 40 scanf("%d%d",&a[i].x,&a[i].y); 41 sort(a+1,a+n+1,cmp); 42 L=0; R=10000; mid=(L+R)/2; 43 while (L<=R) //二分答案 44 { 45 if (OK(mid)) {R=mid-1; mid=(L+R)/2;} else 46 { 47 L=mid+1; 48 mid=(L+R)/2; 49 } 50 } 51 cout<<L+1; 52 return 0; 53 }
(3)第三题这是不是我第一次决定把第三题也写下来...
追逐(chase)
Time Limit:1000ms Memory Limit:128MB
题目描述
这次,LYK以一个上帝视角在看豹子赛跑。
在一条无线长的跑道上,有n只豹子站在原点。第i只豹子将在第ti个时刻开始奔跑,它的速度是vi/时刻。
因此在不同的时刻,这n只豹子可能在不同的位置,并且它们两两之间的距离也将发生变化。
LYK觉得眼光八方太累了,因此它想找这么一个时刻,使得最远的两只豹子的距离尽可能近,当然这不能是第0时刻或者第0.01时刻。它想知道的是最迟出发的豹子出发的那一刻开始,离得最远的两只豹子在距离最小的时候这个距离是多少。
当然这个时刻不仅仅可能发生在整数时刻,也就是说可能在1.2345时刻这个距离最小。
输入格式(chase.in)
第一行一个数n。
接下来n行,每行两个数分别是ti和vi。
输出格式(chase.out)
输出一个数表示答案,你只需保留小数点后两位有效数字就可以了。
输入样例
3
1 4
2 5
3 7
输出样例
0.33
样例解释
在第5+2/3这个时刻,第一只豹子在18+2/3这个位置,第二只豹子在18+1/3这个位置,第三只豹子在18+2/3这个位置,最远的两只豹子相距1/3的距离,因此答案是0.33。
数据范围
对于20%的数据n=2。
对于20%的数据n=3
对于60%的数据n<=100。
对于80%的数据n<=1000。
对于100%的数据n<=100000,1<=vi,ti<=100000。
这道题本来是弃了,后来发现20%还是很好写的。然后就写了一下。结果迷之运行时错误。(所以代码就不放了)
正解思路:
假如一条直线代表一只豹砸
可以分别描出每一时刻离原点最近和最远的豹砸
绿色部分就是他们的差,题目要求找的就是最短的绿线。
易证绿色的只会在交点处最小
60分算法:求出所有交点,然后再求每只豹砸
排除掉跑得慢又跑的晚的豹子后,我们发现斜率大的豹子占据右边一段
用单调栈实现
如图,三条斜率不同的线段最后的上凸壳是紫色部分。也就是说,在这种情况下,每遇到一个交点上凸壳方向就会改变。
下凸壳同理:对于两只豹子,一只跑得慢且后跑,一只跑得快一开始就跑。我们保留前面那只。
上凸壳流程:
- 插入一条线,把所有值都先赋给第一条线;
- 插入第二条线,把向上拐的那一段交给第二条线保管(也就是缝交点变保管线段)
- 以此类推…
标程(可以说是很可怕了):
1 #include<cstdio> 2 #include<cstring> 3 #include<cstdlib> 4 #include<cmath> 5 #include<iostream> 6 #include<algorithm> 7 8 using namespace std; 9 const long double INF=(long double)1000000000*10; 10 long double L,R,mid,ans,hh[100005]; 11 int r,rr,i,n,MAX,X,Y,cnt,vv[100005],vv2[100005]; 12 struct node2 {int t; long double l;} s[200005],S[200005]; 13 struct node {int t,v;} t[100005]; 14 int cmp(node i,node j) {return i.v<j.v || i.v==j.v && i.t>j.t;} 15 struct Node {long double x;int y,z;} p[200005]; 16 int CMP(Node i,Node j) {return i.x<j.x;} 17 long double work(int x,long double y) {return (long double)t[x].v*y-hh[x];} 18 int main() 19 { 20 freopen("chase.in","r",stdin); 21 freopen("chase.out","w",stdout); 22 while (1) 23 { 24 scanf("%d",&n); 25 // if (n==0) return 0; 26 MAX=0; 27 for (i=1; i<=n; i++) 28 { 29 scanf("%d%d",&t[i].t,&t[i].v); 30 MAX=max(MAX,t[i].t); 31 } 32 sort(t+1,t+n+1,cmp); int MIN=t[n].t; 33 for (i=n-1; i>=2; i--) 34 { 35 if (t[i].t>MIN) vv[i]=1; else 36 MIN=t[i].t,vv[i]=0; 37 } 38 for (i=1; i<=n; i++) hh[i]=(long double)t[i].t*t[i].v; 39 r=1; s[1].l=MAX; s[1].t=1; s[2].l=INF; vv[n]=0; 40 for (i=2; i<=n; i++) 41 if (!vv[i]) 42 { 43 while (r && work(i,s[r].l)>=work(s[r].t,s[r].l)) r--; 44 if (!r) {r=1; s[1].l=MAX; s[1].t=i; continue;} 45 L=s[r].l; R=s[r+1].l; mid=(L+R)/2.0; 46 for (int I=1; I<=80; I++) 47 { 48 if (work(i,mid)>=work(s[r].t,mid)) {R=mid; mid=(L+R)/2.0;} else {L=mid; mid=(L+R)/2.0;} 49 } 50 s[++r].l=mid; s[r].t=i; s[r+1].l=INF; 51 } 52 rr=1; S[1].l=MAX; S[2].l=INF; S[1].t=n; 53 MIN=t[1].t; 54 for (i=2; i<n; i++) 55 if (t[i].t<MIN) vv2[i]=1; else 56 MIN=t[i].t,vv2[i]=0; 57 for (i=n-1; i>=1; i--) 58 if (!vv2[i]) 59 { 60 while (rr && work(i,S[rr].l)<=work(S[rr].t,S[rr].l)) rr--; 61 if (!rr) {rr=1; S[1].l=MAX; S[1].t=i; continue;} 62 L=S[rr].l; R=S[rr+1].l; mid=(L+R)/2.0; 63 for (int I=1; I<=80; I++) 64 { 65 if (work(i,mid)<=work(S[rr].t,mid)) {R=mid; mid=(L+R)/2.0;} else {L=mid; mid=(L+R)/2.0;} 66 } 67 S[++rr].l=mid; S[rr].t=i; S[rr+1].l=INF; 68 } 69 cnt=0; 70 for (i=1; i<=r; i++) {p[++cnt].x=s[i].l; p[cnt].y=1; p[cnt].z=s[i].t;} 71 for (i=1; i<=rr; i++) {p[++cnt].x=S[i].l; p[cnt].y=0; p[cnt].z=S[i].t;} 72 sort(p+1,p+cnt+1,CMP); X=Y=0; ans=INF; //X是上凸指针Y是下凸指针 73 for (i=1; i<=cnt; i++) //遇到上凸X动,遇到下凸Y动 74 { 75 if (p[i].y==1) X=p[i].z; else Y=p[i].z; 76 // printf("%.5f\n",(double)p[i].x); 77 if (X && Y) ans=min(ans,work(X,p[i].x)-work(Y,p[i].x)); 78 } 79 printf("%.2f\n",fabs((double)ans)); 80 return 0; 81 } 82 }
今日专题——图论
图论
——算法简单,难点在于构图
l 竞赛图:任意两个人(点)打一场比赛,由赢的向输的连一条边。
l 邻接表的实现:
cin>>u>>v; (u->v)
f[u][++f[u][0]]=v;
f[u][0]表示u连出去能到几个点
f[u][i]表示u连出去第i个点是啥
l 邻接表的好处:在遍历一个点能连向哪些点时,复杂度是出度,而不是O(n)
l 邻接矩阵的好处:判断一条边是否存在,查询一条边的权值,复杂度是O(1)
l 边表(前向星):
cin>>u>>v;
o++;
e[o]=v; (u->v) 之前u还指向过一些点 head[u]来表示起点为u的边编号最大的那个编号是多少
next[o]=head[o] 当u处理完连向v的这条边之后,下一次处理的边编号是多少
head[u]=o;
便利u为起点,能连向哪些点
For(int i=head[u];i!=0;)
l 拓扑排序
给定一张拓扑图,求1号点走到n号点的方案总数
令dp[i]表示从1号点走到i号点的方案总数
有dp[i]=xigma(dp[j]) (j->i)
枚举i时,按照拓扑序进行枚举
Dp[n]就是答案
l 最短路
- Dijkstra
(1)令dis[i]表示当前u到i的最短路是多少。
(2)将dis[u]=0,dis[i]=inf(i!=u)。
(3)寻找最小的dis[x]且x曾经没被找到过。
(4)若x=v,输出答案并退出。
(5)枚举x的所有边,用dis[x]去更新其余dis[],回到步骤②。
时间复杂度为n^2。
使用范围:不存在负权边。
2. SPFA(poj1502)
- 令dis[i]表示当前u到i的最短路是多少。
- ①将dis[u]=0,dis[i]=inf(i!=u),并将u加入队列中。
- ②设当前队首为x,枚举x。
- ③枚举x的所有边,用dis[x]去更新其余dis[],若dis[i]此时被更新且i当前不在队列中,将其加入队列。
- ④将x弹出队列,若此时队列为空,结束,否则返回步骤②。
l 并查集
一行并查集(实现了路径压缩):
Int getf(int x) {return f[x]=x?f[x]:f[x]=getf(f[x]);} //不断找father
l 最小生成树
1.Prim:一开始随便找一个点当做集合中点,每次找一条最短的边,要求这条边链接的两个点,一个在集合中,一个不在集合中。把连接的另一个点和这条边加入到集合中。
2.kruskal:对边权进行排序,每次加入后判断是否存在环,若不存在环,则加入(并查集实现)
l 二分图(不存在奇环,即有奇数个点的环)
二分图染色:(col[i]=1->i这个点染成黑色 col[i]=2->i这个点染成白色 col[i]=0->i这个点还未被染色)
Vector <int> v[N];
Void dfs(int x,int y)
{
Col[x]=y;
For(int i=0;i<v[x].size();i++)
{
If(!col[v[x][i]]) dfs(v[x][i],3-y);
If(col[v[x][i]]==col[x]) FLAG=true; //如果一条边连接的两个点是同一种颜色,这是不被允许的(判定方法1)
}
}
For(int i=1;i<=n;i++) col[i]=0;
For(int i=1;i<=n;i++) if(!col[i])dfs(i,1);
if(FLAG) cout<<’no’; else cout<<’yes’;
二分图判定方法2:
- 将每个点A裂成两个点A与A’。
- 若A与B之间存在边,则连一条A与B’的边,A’与B的边。
- 若此时A与A’连通,或者B与B’连通,则该图不是二分图。(若连通则必然出现了奇环)
- 利用并查集实现即可。
[图例解释]标记完成后,若发现2与2`连通了,则不是二分图(奇环)
可通过染色来理解,会出现矛盾
For(int i=1;i<=n;i++) p[i][0]=++cnt,p[i][1]=++cnt;
For(int i=2;i<=cnt;i++) f[i]=I;
For(int i=1;i<=m;i++)
{
u-v
f[getf(p[u][0])]=getf(p[v][1]);
f[getf(p[u][1])=getf(p[v][0]);
if(getf(p[u][0])==getf(p[u][1])||getf(p[v][0])==getf(p[v][1]))FLAG=true;
}
l 二分图最大匹配
匈牙利算法:
l 强连通分量:任意两个点之间相互可达
l DPN时间戳(第几次被DFS到)
l ***LOW LOW[i] i及i的所有子孙能访问到的最小时间戳
求极大强连通分量(Tarjan):
Void dfs(int x)
{
DFN[x]=++Time;Low[x]=DFN[x];
St[++r]=x; /*压入栈*/int R=r; V[x]=true;
For(int i=0;i<v[x].size();i++) //枚举x能连向哪些点
{
if(!DFN[v[x][i]]){dfs(v[x][i]);LOW[x]=min(LOW[x],LOW[v[x][i]]);} //如果当前点还没dfs过,就dfs一遍。同时更新时间戳
if(V[v[x][i]])LOW[x]=min(LOW[x],DFN[v[x][i]]);
}
If(LOW[x]==DFN[x])
{
cnt++;
For(int i=R;i<=r;i++) p[st[i]]
r=R-1;
}
}
cnt 第cnt个强连通分量 p[i] i所处的的事哪个极大强连通分量
LOW[x]==DFN[x] 已出现一个极大强连通分量
**背代码最开始每天都要默一遍**
今天信息量简直太大,脑袋要炸掉了。。