APIO2007-2015题解大集合(2010年篇)
2016-04-16 13:45
218 查看
好消息:这一篇没有上古警告!
关于斜率优化dp的式子有两种推法:第一种是按照直线Y=kx+b的形式,把与i有关的看成b,与i,j都相关的看成kx,其中与i相关的是k,与j相关的是x,然后画个图来推;第二种是考虑两个决策点j,k,对它们的决策做差,然后把i有关的移到一边,会形成(f[j]-f[k])/(j-k)>p*i(与i相关的一坨)的形式。其中第一种能够应用的情况比较广泛,因为第二种推法到最后可能会出现移项时不等式符号不好判断的问题。
这个题以第二种推法为例,第一种推法留给读者思考。
设f[i]为所求最大战斗力,则
f[i]=f[j]+a*(s[i]-s[j])^2+b(s[i]-s[j])+c
于是对于j优于k,有
f[j]+a*s[i]^2+a*s[j]^2-2as[i]s[j]+b*s[i]-b*s[j]+c>f[k]+a*s[i]^2+a*s[k]^2-2as[i]s[k]+b*s[i]-b*s[k]+c
f[j]-f[k]>a*(s[k]^2-s[j]^2)+2as[i](s[j]-s[k])+b(s[j]-s[k])
(f[j]+a*s[j]^2-b*s[j])-(f[k]+a*s[k]^2-b*s[j])>2as[i]*(s[j]-s[k])
然后把s[j]-s[k]除过去,就是所求的式子。照着这个式子写斜率优化即可。另外注意斜率用double很慢,有些毒瘤出题人可能会卡几个点必须用分子分母分开除法转乘法才能过……
巡逻
2、考虑k=1的情况,则只需要找树的最长链即可。考虑k=2,则现在图是一个环套树,仍然需要在图上找出最能节省路程的一条边。这个时候把树的最长链提出来,由于成环的那条路只能走一次,另一次是不能走那条边的,所以还是可以当成一条链来处理。于是有两种情况:
(1)在最长链上某个节点的子树内部进行拼接:直接对最长链上每个点子树进行最长链DP然后取最长+次长即可。
(2)中间要经过一段最长链,易知此时路程应该为节省路程-中间走过的路程长度。写出状态转移方程不难发现这是一个可以用单调栈解决的简单DP,具体方程留给读者自己思考。
3、先求一次最长链,把最长链上长度设为-1,再求一次最长链即可。(代码最后良心地给了几组数据,要的拿走就好……)
这个题如果考虑三角形反而不好做,应该考虑四边形。画图易知,考虑一个四边形任取3个点取4次,在每次一定落入的3个点的基础上,凸四边形会有2次4个点都在内,凹四边形会有1次(简记为凸四边形贡献为2,凹多边形贡献为1)。设凹四边形个数为a,凸四边形个数为b,那么b=C(n,4)-a。于是枚举凹四边形的中间点,以中间点为原点,把其他点按极角排序,枚举极角差刚刚不小于π的两条边,那么这两条边之间的点和其中一条边上的点不包含中间点。枚举的时间复杂度是O(n)。设p为不包含中间点的三角形个数,那么以该点为中间点的凹四边形个数为C(n-1,3)-p。最后的期望即为(a+2*b)/C(n,3)。
特别行动队
经典斜率优化DP。关于斜率优化dp的式子有两种推法:第一种是按照直线Y=kx+b的形式,把与i有关的看成b,与i,j都相关的看成kx,其中与i相关的是k,与j相关的是x,然后画个图来推;第二种是考虑两个决策点j,k,对它们的决策做差,然后把i有关的移到一边,会形成(f[j]-f[k])/(j-k)>p*i(与i相关的一坨)的形式。其中第一种能够应用的情况比较广泛,因为第二种推法到最后可能会出现移项时不等式符号不好判断的问题。
这个题以第二种推法为例,第一种推法留给读者思考。
设f[i]为所求最大战斗力,则
f[i]=f[j]+a*(s[i]-s[j])^2+b(s[i]-s[j])+c
于是对于j优于k,有
f[j]+a*s[i]^2+a*s[j]^2-2as[i]s[j]+b*s[i]-b*s[j]+c>f[k]+a*s[i]^2+a*s[k]^2-2as[i]s[k]+b*s[i]-b*s[k]+c
f[j]-f[k]>a*(s[k]^2-s[j]^2)+2as[i](s[j]-s[k])+b(s[j]-s[k])
(f[j]+a*s[j]^2-b*s[j])-(f[k]+a*s[k]^2-b*s[j])>2as[i]*(s[j]-s[k])
然后把s[j]-s[k]除过去,就是所求的式子。照着这个式子写斜率优化即可。另外注意斜率用double很慢,有些毒瘤出题人可能会卡几个点必须用分子分母分开除法转乘法才能过……
#include<iostream> #include<iomanip> #include<algorithm> #include<cstring> #include<cstdio> #include<cstdlib> #include<cmath> #define LL long long using namespace std; LL n,a,b,c,head,tail,q[1000005],f[1000005],BEGIN[1000005],s[1000005]; double Slope(LL j,LL k) {return double(f[k]-f[j]+a*(s[k]*s[k]-s[j]*s[j])-b*(s[k]-s[j]))/double(2*a*(s[k]-s[j])); } void Solve() {f[0]=0,head=1,tail=1;//2as单增 for(LL i=1;i<=n;i++) {while(head<tail&&Slope(q[head],q[head+1])<=double(s[i]))head++; f[i]=f[q[head]]+a*(s[i]-s[q[head]])*(s[i]-s[q[head]])+b*(s[i]-s[q[head]])+c; while(head<tail&&Slope(q[tail-1],q[tail])>Slope(q[tail],i))tail--; q[++tail]=i; } } void Input() {scanf("%lld%lld%lld%lld",&n,&a,&b,&c); s[0]=0; for(LL i=1;i<=n;i++) {scanf("%lld",&BEGIN[i]); s[i]=s[i-1]+BEGIN[i]; } } int main() {Input(); Solve(); printf("%lld\n",f ); return 0; } /* 4 -1 10 -20 2 2 3 4 */
巡逻
此题其实是一道NOIP难度的题目……这个题有三种做法:
1、树上的插头DP。这个题目可以转化为求树上不相交的最长链,于是可以用插头乱搞……然而我并不会做……2、考虑k=1的情况,则只需要找树的最长链即可。考虑k=2,则现在图是一个环套树,仍然需要在图上找出最能节省路程的一条边。这个时候把树的最长链提出来,由于成环的那条路只能走一次,另一次是不能走那条边的,所以还是可以当成一条链来处理。于是有两种情况:
(1)在最长链上某个节点的子树内部进行拼接:直接对最长链上每个点子树进行最长链DP然后取最长+次长即可。
(2)中间要经过一段最长链,易知此时路程应该为节省路程-中间走过的路程长度。写出状态转移方程不难发现这是一个可以用单调栈解决的简单DP,具体方程留给读者自己思考。
3、先求一次最长链,把最长链上长度设为-1,再求一次最长链即可。(代码最后良心地给了几组数据,要的拿走就好……)
#include<iostream> #include<iomanip> #include<algorithm> #include<cstring> #include<cstdio> #include<cstdlib> #include<cmath> #include<queue> using namespace std; int cnt=0,ans,wer,n,k,a,b,h[200005]={0},vis[200005],dist[200005],from[200005],stack[200005],D[200005],Maxx[200005],Sec[200005]; int qu[500005]; struct node{int next,to;}edge[400005]; queue<int>q; void Addedge(int x,int y) {cnt++,edge[cnt].to=y,edge[cnt].next=h[x],h[x]=cnt; } void Input() {scanf("%d%d",&n,&k); for(int i=1;i<n;i++) {scanf("%d%d",&a,&b); Addedge(a,b),Addedge(b,a); } } void Dfs(int x) {vis[x]=1; for(int i=h[x];i;i=edge[i].next) {int y=edge[i].to; if(!vis[y])dist[y]=dist[x]+1,from[y]=x,Dfs(y); } } void Dfs2(int x) {vis[x]=1,Maxx[x]=0,Sec[x]=0; for(int i=h[x];i;i=edge[i].next) {int y=edge[i].to; if(vis[y])continue; dist[y]=dist[x]+1,Dfs2(y); if(Maxx[y]+1>Maxx[x])Sec[x]=Maxx[x],Maxx[x]=Maxx[y]+1; else if(Maxx[y]+1>Sec[x])Sec[x]=Maxx[y]+1; } wer=min(wer,ans-Maxx[x]-Sec[x]+1); } void Dp()//链上的DP {int head=1,tail=1;qu[1]=0; for(int i=1;i<=stack[0];i++) {wer=min(wer,ans-(qu[head]+Maxx[stack[i]]-D[stack[i]]-1)); while(head<=tail&&qu[tail]<Maxx[stack[i]]+D[stack[i]])tail--; qu[++tail]=Maxx[stack[i]]+D[stack[i]]; } } void Solve() {int S=1,maxx=0,rec; memset(vis,0,sizeof(vis)),memset(from,0,sizeof(from)); dist[S]=0,from[S]=0; Dfs(S); for(int i=1;i<=n;i++) if(dist[i]>maxx)maxx=dist[i],rec=i; S=rec; memset(vis,0,sizeof(vis)),memset(from,0,sizeof(from)); dist[S]=0,from[S]=0; Dfs(S); maxx=0; for(int i=1;i<=n;i++) if(dist[i]>maxx)maxx=dist[i],rec=i; ans=2*(n-1)-maxx+1; wer=999999999; if(k==1){printf("%d\n",ans);return;} memset(vis,0,sizeof(vis)); while(!q.empty())q.pop(); int now=rec; while(now)stack[++stack[0]]=now,vis[now]=1,dist[now]=0,now=from[now]; for(int i=1,j=stack[0];i<j;i++,j--)swap(stack[i],stack[j]); for(int i=1;i<=stack[0];i++)D[stack[i]]=i-1; for(int i=1;i<=stack[0];i++)Dfs2(stack[i]); Dp(); printf("%d\n",wer); } int main() {//freopen("patrol.in","r",stdin); //freopen("patrol.out","w",stdout); Input(); Solve(); fclose(stdin); fclose(stdout); return 0; } /* 13 1 1 2 3 1 3 4 4 9 9 10 10 11 10 12 12 13 5 3 7 5 8 5 5 6 */ /* 8 2 1 2 3 1 3 4 5 3 7 5 8 5 5 6 */ /* 7 1 1 2 2 3 3 4 4 5 5 6 6 7 */ /* 21 2 1 2 2 3 3 4 1 5 5 6 6 7 1 8 8 9 9 10 8 11 8 12 9 13 9 14 1 15 15 18 18 21 15 16 15 17 18 19 18 20 */ /* 19 2 1 2 2 3 3 4 4 5 5 6 6 7 7 8 8 9 9 10 10 11 11 12 12 13 6 14 14 15 15 16 8 17 17 18 18 19 */
信号覆盖
我觉得这题黄学长说得比我好,于是就在黄学长基础上改了一下……这个题如果考虑三角形反而不好做,应该考虑四边形。画图易知,考虑一个四边形任取3个点取4次,在每次一定落入的3个点的基础上,凸四边形会有2次4个点都在内,凹四边形会有1次(简记为凸四边形贡献为2,凹多边形贡献为1)。设凹四边形个数为a,凸四边形个数为b,那么b=C(n,4)-a。于是枚举凹四边形的中间点,以中间点为原点,把其他点按极角排序,枚举极角差刚刚不小于π的两条边,那么这两条边之间的点和其中一条边上的点不包含中间点。枚举的时间复杂度是O(n)。设p为不包含中间点的三角形个数,那么以该点为中间点的凹四边形个数为C(n-1,3)-p。最后的期望即为(a+2*b)/C(n,3)。
#include<iostream> #include<iomanip> #include<algorithm> #include<cstring> #include<cstdio> #include<cstdlib> #include<cmath> #define LL long long using namespace std; LL n,ans1,ans2,top; struct node{LL x,y;double ang;}point[1505],temp[1505]; void Input() {scanf("%lld",&n); if(n==3){printf("3.000000\n");exit(0);} for(LL i=1;i<=n;i++) {scanf("%lld%lld",&point[i].x,&point[i].y); } } bool Comp(node x,node y) {return x.ang<y.ang; } LL Cross(node p1,node p2,node p0) {return (p1.x-p0.x)*(p2.y-p0.y)-(p2.x-p0.x)*(p1.y-p0.y); } LL Inner(LL x) {LL ret=(n-1)*(n-2)*(n-3)/6; top=0; for(LL i=1;i<=n;i++) if(x==i)temp[0]=point[i]; else temp[++top]=point[i]; for(LL i=1;i<=top;i++) temp[i].ang=atan2(temp[i].y-temp[0].y,temp[i].x-temp[0].x); sort(temp+1,temp+top+1,Comp); LL R=2,num=0; for(LL i=1;i<=top;i++) {while(Cross(temp[i],temp[R],temp[0])>=0) {R=R%top+1,num++; if(R==i)break; } ret-=num*(num-1)/2,num--; } return ret; } void Solve() {ans1=0; for(LL i=1;i<=n;i++) ans1+=Inner(i); ans2=n*(n-1)*(n-2)*(n-3)/24-ans1; double wer=(double(2*ans2+ans1))/double(n*(n-1)*(n-2)/6.0); printf("%.6lf\n",wer+3); } int main() {Input(); Solve(); return 0; }
相关文章推荐
- MVC之Razor语法
- [学习笔记]Java IO之字节流
- [学习笔记]Java日期类及其处理
- [学习笔记]Java常用工具类
- Eclipse_Java编码规范详细设置
- java入门
- [学习笔记]Java集合框架之Map集合
- 使用VS2010编译OpenSSL源码
- CentOS修改系统默认语言
- Mongodb无法访问28107的问题
- SpringMVC表单标签简介
- 加解密技术基础
- Exchange 2016集成ADRMS系列-4:配置RMS角色2
- Android UI基础之Spinner
- [学习笔记]Java集合框架之Set集合
- [学习笔记]Java基本数据类型包装类
- git-commit
- 看了这个图有种说不出的感觉
- Android动画之旅 一 Android动画总结
- IOS开发笔记——禁用手势滑动返回功能