2015 ACM/ICPC Asia Regional Shanghai

7 篇文章 0 订阅
5 篇文章 0 订阅

A. I Count Two Three

题目链接

题目大意

在平面上有一个实心圆,圆心为 (Ox,Oy) ,半径为 r ,圆外有一个大小可忽略的小球A,在 (Ax,Ay) 处,速度的方向向量为 (Vx,Vy) ,小球 A 碰到圆后会反弹,遵循反射定律,求小球A是否会经过点 B(Bx,By)

思路 - 计算几何

模版题,很容易就能想到判断方法:先判断 B 是否在原始的直线上:
①若在,再判断线段AB与圆是否相交(两个点),若相交则不会经过,否则会经过。
②若不在,设反射点为 C ,此时如果线段CB与圆不相交(两个点)且线段 CB 在反射直线上,则会经过,否则不会经过。

但是写的时候感觉反射直线不好求,队友提醒可以求出反射点 C ,再求出AB的中点 D ,判断OD与圆的交点是否为 C 即可。

代码

#include <cstdio>
#include <cstring>
#include <map>
#include <algorithm>

#define zero(x) (((x) > 0 ? (x) : -(x)) < EPS)

using namespace std;

const double EPS = 1e-8;

struct Point {
    double x, y;

    Point() {};

    Point(double xx, double yy) :x(xx), y(yy) {}

    Point operator + (const Point& a) const {
        return Point(x + a.x, y + a.y);
    }
};

inline double xmult(const Point& p1,const Point& p2,const Point& p0) {
    return (p1.x-p0.x)*(p2.y-p0.y)-(p2.x-p0.x)*(p1.y-p0.y);
}
inline double xmult(double x1,double y1,double x2,double y2,double x0,double y0) {
    return (x1-x0)*(y2-y0)-(x2-x0)*(y1-y0);
}

inline bool dots_inline(const Point& p1,const Point& p2,const Point& p3) {
    return zero(xmult(p1,p2,p3));
}

inline double distance(const Point& p1, const Point& p2) {
    return sqrt((p1.x-p2.x)*(p1.x-p2.x)+(p1.y-p2.y)*(p1.y-p2.y));
}

inline double distance(double x1,double y1,double x2,double y2) {
    return sqrt((x1-x2)*(x1-x2)+(y1-y2)*(y1-y2));
}

Point intersection(const Point& u1,const Point& u2,const Point& v1,const Point& v2) {
    Point ret=u1;
    double t=((u1.x-v1.x)*(v1.y-v2.y)-(u1.y-v1.y)*(v1.x-v2.x))
            /((u1.x-u2.x)*(v1.y-v2.y)-(u1.y-u2.y)*(v1.x-v2.x));
    ret.x+=(u2.x-u1.x)*t;
    ret.y+=(u2.y-u1.y)*t;
    return ret;
}

//计算直线与圆的交点,保证直线与圆有交点
//计算线段与圆的交点可用这个函数后判点是否在线段上
void intersection_line_circle(const Point& c,double r,const Point& l1,const Point& l2,Point& p1,Point& p2) {
    Point p=c;
    double t;
    p.x+=l1.y-l2.y;
    p.y+=l2.x-l1.x;
    p=intersection(p,c,l1,l2);
    t=sqrt(r*r-distance(p,c)*distance(p,c))/distance(l1,l2);
    p1.x=p.x+(l2.x-l1.x)*t;
    p1.y=p.y+(l2.y-l1.y)*t;
    p2.x=p.x-(l2.x-l1.x)*t;
    p2.y=p.y-(l2.y-l1.y)*t;
}

Point ptoline(const Point& p,const Point& l1,const Point& l2) {
    Point t=p;
    t.x+=l1.y-l2.y,t.y+=l2.x-l1.x;
    return intersection(p,t,l1,l2);
}

double disptoline(const Point& p, const Point& l1, const Point& l2) {
    return distance(p, ptoline(p, l1, l2));
}

bool intersect_seg_circle(const Point& c,double r, const Point& l1,const Point& l2) {
    double t1=distance(c,l1)-r,t2=distance(c,l2)-r;
    Point t=c;
    if (t1<-EPS||t2<-EPS)
        return t1>-EPS||t2>-EPS;
    t.x+=l1.y-l2.y;
    t.y+=l2.x-l1.x;
    return xmult(l1,c,t)*xmult(l2,c,t)<EPS&&disptoline(c,l1,l2)-r<-EPS;
}

Point cir, a, b, v;
Point p[4], tmp;
double r;

double judge() {
    if(dots_inline(b, a, a + v)) {
        return !intersect_seg_circle(cir, r, a, b);
    }
    intersection_line_circle(cir, r, a, a + v, p[0], p[1]);
    if(distance(a, p[1]) < distance(a, p[0])) {
        swap(p[0], p[1]);
    }
    tmp = ptoline(cir, a, b);
    intersection_line_circle(cir, r, cir, tmp, p[2], p[3]);
    if(distance(a, p[3]) < distance(a, p[2])) {
        swap(p[2], p[3]);
    }
    return zero(distance(p[0], p[2]));
}

int main() {
    int T, kase = 0;
    scanf("%d", &T);
    while(kase < T) {
        scanf("%lf%lf%lf", &cir.x, &cir.y, &r);
        scanf("%lf%lf%lf%lf", &a.x, &a.y, &v.x, &v.y);
        scanf("%lf%lf", &b.x, &b.y);
        printf("Case #%d: %s\n", ++kase, judge() ? "Yes" : "No");
    }
    return 0;
}

B. Binary Tree

题目链接

题目大意

有一个满二叉树,根结点数为1,设一个结点的数为 x ,则其左子结点的数是2x,其右子结点的数是 2x+1 ,现给定 n(n<=2k) k(k<=60) ,从根结点开始每次往下走,共走过 k 个结点,每个结点的数可取正或者负,求最终和为n时的一种路径极其符号的方案?

思路 - 构造

数据范围这么大,还极其富有规律,所以想到是利用二进制构造,但是没有注意到 n<=2k ,导致一直在用奇怪的方法 WA

看了题解后才注意到 n<=2k 这个条件,发现每次只走最左边且均取正,结果为 2k1 ,若最后一次往右走则可取到 2k ,刚好取到 n 的最大值。
所以只走最左边(1,2,4,,2k)就能构造出 1n 所有的数。但是在二进制下对应位为 1 的为正,对应位为0的不取,与本题不同,所以需要变形,即先令所有的位均取正,后面选择将某些位取负即可完成构造。

n 为奇数,则最后一步向左走,否则最后一步向右走(原因见下面分析),最后可得到对应的和sum。令 rem=sumn ,则此时 rem 必定为偶数,因为通过 n 的奇偶决定最后一步数的奇偶,使得sum n 奇偶性相同。现在考虑将某些位的正好改为负号,则在总的结果里将减去这个数的2倍,先将这个数从和中去除,在减去这个数,所以令 rem=rem/2 ,将此时 rem 二进制上为 1 的位的数标记为负号即可。

代码

#include <cstdio>
#include <cstring>
#include <string>
#include <iostream>

using namespace std;

const int MAXN = 200000;

int n, cnt, len;
string s;
long double ans[MAXN];

bool judge() {
    cnt = 0;
    len = s.size();
    while(s[cnt] == '0') {//除去前导0
        ++cnt;
    }
    if(len - cnt > 6) {
        return false;
    }
    n = 0;
    while(cnt < len) {
        n = n * 10 +s[cnt++] - '0';
    }
    return n < MAXN;
}

int main() {
    ans[0] = 0;
    for(int i = 1; i < MAXN; ++i) {
        ans[i] = ans[i - 1] + 1.0 / ((0LL + i) * i);
    }
    while(cin >> s) {
        if(judge()) {
            cout<<fixed;
            cout.precision(5);
            cout << ans[n] << endl;
        }
        else {
            cout<<fixed;
            cout.precision(5);
            cout << ans[MAXN - 1] << endl;
        }
    }
}

F. Friendship of Frog

题目链接

题目大意

有一个字符串,求任意两个相同字母间距离的最小值?

思路 - 枚举

字符串很短,所以直接枚举即可。

代码

#include <cstdio>
#include <cstring>
#include <algorithm>

using namespace std;

const int INF = 0x3f3f3f3f;

int dis, len;
char s[1003];

int main() {
    int T, kase = 0;
    scanf("%d", &T);
    while(kase < T) {
        scanf("%s", s);
        len = strlen(s);
        dis = INF;
        for(int i = 0; i < len; ++i) {
            for(int j = i +1; j < len; ++j) {
                if(s[i] == s[j]) {
                    dis = min(dis, j - i);
                    break;
                }
            }
        }
        printf("Case #%d: %d\n", ++kase, dis == INF ? -1 : dis);
    }
    return 0;
}

K. Kingdom of Black and White

题目链接

题目大意

有一个01,这个串的值为每一段连续的 0 1的长度的平方和,可以最多反转一个字符,使得串的值最大,求这个最大值?

思路 - 枚举

可以贪心地只反转每一段的两边的两个字符,然后预处理出每一段的长度,先算出初始的值,然后可以枚举反转的字符(注意当前段只有一个字符时,需要将三段合起来计算), O(1) 内即可算的反转后的值,总时间复杂度为 O(n)

我用的预处理出每一段的起始和终止的下标,然后枚举反转每一个字符,分类讨论,比较麻烦。

代码

#include <cstdio>
#include <cstring>
#include <algorithm>

using namespace std;

int len;
int sta[100003], des[100003];
long long ans, ori;
char s[100003];

inline long long square(long long a) {
    return a * a;
}

int main() {
    int T, kase = 0;
    scanf("%d", &T);
    while(kase < T) {
        scanf("%s", s + 1);
        len = strlen(s + 1);
        sta[1] = 1;
        des[len] = len;
        for(int i = 2, j = len - 1; i <= len && j > 0; ++i, --j) {
            sta[i] = (s[i] == s[i - 1] ? sta[i - 1] : i);
            des[j] = (s[j] == s[j + 1] ? des[j + 1] : j);
        }
        sta[0] = 1;
        des[0] = 0;
        sta[len + 1] = len + 1;
        des[len + 1] = len;
        ori = 0;
        for(int i = 2; i <= len + 1; ++i) {
            if(s[i] != s[i - 1]) {
                ori += square(des[i - 1] - sta[i - 1] + 1);
            }
        }
        ans = ori;
        s[0] = '\0';
        for(int i = 1; i <= len; ++i) {
            if(s[i] == s[i - 1]) {
                if(s[i] == s[i + 1]) {
                    ans = max(ans, ori - square(des[i] - sta[i] + 1LL)
                              + square(i - sta[i - 1]) + 1LL + square(des[i] - i));
                }
                else {
                    ans = max(ans, ori - square(des[i] - sta[i] + 1LL)
                              - square(des[i + 1] - i)
                              + square(i - sta[i - 1]) + square(des[i + 1] - i + 1LL));
                }
            }
            else {
                if(s[i] == s[i + 1]) {
                    ans = max(ans, ori - square(i - sta[i - 1])
                              - square(des[i] - sta[i] + 1LL)
                              + square(i - sta[i - 1] + 1LL) + square(des[i] - i));
                }
                else {
                    ans = max(ans, ori - square(des[i - 1] - sta[i - 1] + 1LL)
                              - 1LL - square(des[i + 1] - i)
                              + square(des[i + 1] - sta[i - 1] + 1LL));
                }
            }
        }
        printf("Case #%d: %I64d\n", ++kase, ans);
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值