1、前言
今天是APIO2010的题目,主要目的依旧是练习搜索(当然说是这么说)。感觉总体而言还是可做的,前两道似乎都比较适合骗分。
2、Commando 特别行动队
大概题意:给出一个序列,请划分成若干个子段,要求每一个子段和x变换为ax^2+bx+c后的总和最大。(a,b,c均已知,a<0)
总结:50分的暴力DP还是很好写的,但是由于常数问题可能会被卡成40分,着主要集中在变换值的计算。考试的时候写了个贪心,即根据二次函数的性质,在x=-b/2a存在最大值,故在向前枚举的时候当越过最大值之后可以break掉,而不用枚举到最前面。但是这个正确性明显不足。交到CodeVS上之后我被吓坏了,竟然后6个点全部AC,然而前4个点却WA了。机智的我决定根据数据大小分类讨论,结果评测的时候只有70分,最后3个点全部TLE。后来自己手动测试的时候发现确实T成鬼了,实在膜拜CodeVS的评测速度啊!2.8s的数据跑出了0.4s。
题解:当然这道题是并不存在70分算法的。正解是斜率优化动态规划。【此处待补充!】
代码略。
3、Patrol 巡逻
大概题意:给出一棵树,连k条边(k∈[1,2]),使得从1号节点遍历全图的步数最少。
总结:k=1时固然好写,可以证得求得树上最长链即可(即树的直径),可得30分。而后k=2的情况,考场上没写,但是看其他同学的搞法之后觉得很好,虽然同样是不能保证正确性的贪心,但是分数就好看多了,所以以后还是一样,多想想一些稀奇古怪的得分方式吧。
题解:直接求树的直径(30分);求出树的直径之后将这条链删除,再次求直径(贪心算法,66分);树形DP/BFS(100分)。为什么前者只有66分呢?因为选取两个环是要分类讨论的,一种为两个环互不相交,没有重边,也就是贪心所能达到的;还有一种为两个环存在公共边,但是易得这样同样可以转换为一个环的情况,也没有看到什么证明方法,其实画一画就知道了。第二次必须用树形DP。
代码(66分):
-----------------------------------------------------------------------------------------------------
#include<cstdio>
#include<cstring>
#define MAXN 100005
int max(int a,int b) { return (a>b)?a:b; }
struct Edge
{
int v,next,flag;
};
Edge edge[MAXN*2];
int h[MAXN],now=1,n,k;
void addEdge(int u,int v)
{
now++;
edge[now].v=v;
edge[now].next=h[u];
h[u]=now;
}
int q[MAXN],vis[MAXN],dep[MAXN],flag[MAXN],nte[MAXN*2],fa[MAXN*2],bye;
void BFS(int now,int c1,int c2)
{
int head=1,tail=2; q[head]=now,vis[now]=1,dep[now]=0;
if (c1) flag[now]=1;
while (head!=tail)
{
for (int x=h[q[head]];x;x=edge[x].next)
{
if (vis[edge[x].v] || edge[x].flag) continue;
nte[edge[x].v]=x,q[tail++]=edge[x].v,vis[edge[x].v]=1;
dep[edge[x].v]=dep[q[head]]+1;
if (c2) fa[x]=nte[q[head]];
if (c1) flag[edge[x].v]=1;
}
head++;
}
}
int s,t,maxDep;
void getDep(int &now)
{
for (int i=1;i<=n;i++) if (maxDep<dep[i]) maxDep=dep[i],now=i;
memset(dep,0,sizeof(dep)),memset(vis,0,sizeof(vis));
}
int work1()
{
BFS(1,0,0),getDep(s),maxDep=0;
BFS(s,0,1),getDep(t);
return n*2-maxDep-1;
}
int work2()
{
int r1=0,r2=0;
BFS(1,0,0),getDep(s),maxDep=0;
BFS(s,0,1),getDep(t);
for (int x=nte[t];x;x=fa[x]) edge[x].flag=edge[x^1].flag=1;
r1=maxDep,maxDep=0;
for (int i=1;i<=n;i++)
if (!flag[i])
{
BFS(i,1,0),getDep(s);
if (!maxDep) continue;
maxDep=0,BFS(s,0,0),getDep(t),r2=max(r2,maxDep),maxDep=0;
if (bye) break;
}
return 2*n-r1-r2;
}
int u,v;
int main()
{
freopen("patrol.in","r",stdin);
freopen("patrol.out","w",stdout);
scanf("%d %d",&n,&k);
for (int i=1;i<=n;i++)
{
scanf("%d %d",&u,&v);
addEdge(u,v),addEdge(v,u);
}
printf("%d",k==1?work1():work2());
return 0;
}
-----------------------------------------------------------------------------------------------------
4、Signaling 信号覆盖
大概题意:给出n个点,每次取其中3个点作外接圆,记录在圆内或圆上的点个数,求所有情况的平均个数。
总结:计算几何,码农题吧。不多去考虑100分情况,直接暴力40分,其实也有差别很大的代码,值得分析一下。其实标准的计算几何代码是需要一大堆预处理的,需要用特殊的形式去表示向量等单位,以及各种操作。但是这道题的话用了可能有点小题大做吧,而我为了熟悉一下还是去用了。那么还是只给出一个比较简洁的代码吧。
题解略。
代码(40分,from z123z123d):
-----------------------------------------------------------------------------------------------------
#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define M 1510
#define dbl double
using namespace std;
int n, x[M], y[M], s, ans;
dbl ox, oy, r;
void get (int x1, int y1, int x2, int y2, dbl &ux, dbl &uy, dbl &sx, dbl &sy) {
sx = (x1 + x2 + 0.0) / 2, sy = (y1 + y2 + 0.0) / 2;
dbl vx = x2 - x1, vy = y2 - y1;
ux = -vy, uy = vx;
}
int cmp (dbl x, dbl t) {if (t < x + 1e-5 && t > x - 1e-5) return 0; return t - x > 0? -1 : 1;}
dbl dis (dbl x1, dbl y1, dbl x2, dbl y2) {return sqrt ((x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1));}
void sol (dbl a1, dbl b1, dbl c1, dbl a2, dbl b2, dbl c2, dbl &t) {
if (cmp (a1, 0) == 0)
swap (a1, a2), swap (b1, b2), swap (c1, c2);
dbl c = a2 / a1;
b1 *= c, c1 *= c;
t = (c1 - c2) / (b1 - b2);
}
void pre (int a, int b, int c) {
dbl x1, y1, ux, uy, x2, y2, vx, vy, t;
get (x[a], y[a], x[b], y[b], ux, uy, x1, y1);
get (x[b], y[b], x[c], y[c], vx, vy, x2, y2);
sol (ux, -vx, x2 - x1, uy, -vy, y2 - y1, t);
ox = x2 + t * vx, oy = y2 + t * vy, r = dis (x[a], y[a], ox, oy);
}
void check (int k) {
if (cmp (dis (x[k], y[k], ox, oy), r) <= 0) s ++;
}
int main()
{
freopen("signaling.in","r",stdin),freopen("signaling.out","w",stdout);
scanf ("%d", &n);
for (int i = 1; i <= n; i++)
scanf ("%d %d", &x[i], &y[i]);
for (int i = 1; i <= n; i++)
for (int j = i + 1; j <= n; j++)
for (int k = j + 1; k <= n; k++) {
pre (i, j, k);
s = 0;
for (int t = 1; t <= n; t++)
check (t);
ans += s;
}
printf ("%.3lf", (ans + 0.0) / (n * (n - 1) * (n - 2) / 6));
return 0;
}
-----------------------------------------------------------------------------------------------------