CSP2020 题解

T1 儒略历

考过 \(\mathrm{CSP2020}\) 的都知道,这是一题超级恶心的大模拟。博主因为太菜,到现在为止还不会写正解做法,这里只放部分分,最多到 \(\mathrm{90pts}\).

40pts

大概这个分数?不难看出题目的本质就是在询问 \(-4713\)\(1\)\(1\) 日往后 \(x\) 天是哪一天。考虑最简单的暴力:\(Q\) 次询问,对于每次询问一天一天往后跳。

可以先写一个函数判断闰年:

bool runnian(int x) //判断闰年(英语太烂只能上汉语拼音
{
    if(x < 0) //公园前闰年判断
    {
        x = -x - 1;
        if(x % 4 == 0) return true;
        return false;
    }
    if(x <= 1582) //公元后分为两段
    {
        if(x % 4 == 0) return true;
        return false;
    }
    else
    {
        if(x % 4 == 0 && x % 100 != 0) return true;
        if(x % 400 == 0) return true;
        return false;
    }
}

再写一个函数叫 \(\mathrm{nxt_day}\),顾名思义:

date nxt_day(date x) //下一天 
{
    x.d++;
    bool fl = runnian(x.y);
    if(fl)
    {
        if(x.m != 2 && x.d > day_num[x.m]) x.m++, x.d = 1;
        else if(x.m == 2 && x.d > day_num[x.m] + 1) x.m++, x.d = 1;
    }
    else if(x.d > day_num[x.m]) x.m++, x.d = 1;
    if(x.m > 12)
    {
        if(x.y != -1) x.y++, x.m = 1; //没有公元 0 年 
        else x.y = 1, x.m = 1;
    }
    if(x.y == 1582 && x.m == 10 && x.d == 5) x.d = 15; //1582年删除的日期 
    return x;
}

一定要注意各种细节。这个函数比较简单,调过两个小样例基本上就足够了。

80pts

\(\mathrm{40pts}\) 完全一样有没有?只需要把询问离线,从小到大排个序,再进行上面的过程:这样复杂度就从 \(\mathrm{O(Qr)}\) 成功的被优化到了 \(\mathrm{O(r)}\)

就不放代码了。

90pts

考虑改成一年一年跳。如果 \(x>365\),就让 \(x\) 减去 \(365\),年份 \(+1\) 即可。

但是这只是一个基本的思路,代码实现还是有很多恶心的细节。对于 \(1582\) 年,可以直接暴力地一天一天跳过去。

还剩下一个 \(2\)\(29\) 日的问题。如果当前进来的时候是 \(2\)\(29\) 日,就不能直接往后跳 \(1\) 年,因为下一年可能没有 \(2\)\(29\) 日,处理方法是直接跳到下一天。这个地方一定要判断与上一次询问的变化量 x 是否等于 0!. 因为我太垃圾了,这里没有判断,炸了 \(20\) 分,沦为不如暴力老哥。

复杂度为 \(\mathrm{O(\frac{r}{365})}\),可以通过 \(90\%\) 的数据。

考场代码(赛后改动有标记):

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;

const int N = 2333333;
struct date { int y, m, d; } last, ans[N];
struct que { int r, id; } q[N];
int Q, r;
int day_num[23] = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};

bool cmp(que a, que b) { return a.r < b.r; }

bool runnian(int x) //判断闰年 
{
    if(x < 0)
    {
        x = -x - 1;
        if(x % 4 == 0) return true;
        return false;
    }
    if(x <= 1582) 
    {
        if(x % 4 == 0) return true;
        return false;
    }
    else
    {
        if(x % 4 == 0 && x % 100 != 0) return true;
        if(x % 400 == 0) return true;
        return false;
    }
}

date nxt_day(date x) //下一天 
{
    x.d++;
    bool fl = runnian(x.y);
    if(fl)
    {
        if(x.m != 2 && x.d > day_num[x.m]) x.m++, x.d = 1;
        else if(x.m == 2 && x.d > day_num[x.m] + 1) x.m++, x.d = 1;
    }
    else if(x.d > day_num[x.m]) x.m++, x.d = 1;
    if(x.m > 12)
    {
        if(x.y != -1) x.y++, x.m = 1; //没有公元 0 年 
        else x.y = 1, x.m = 1;
    }
    if(x.y == 1582 && x.m == 10 && x.d == 5) x.d = 15; //1582年删除的日期 
    return x;
}

/*date query(LL x, date tmp)
{
    while(x) tmp = nxt_day(tmp), x--;
    return tmp;
}*/

date nxt_year(date x)
{
    if(x.y == -1) x.y = 1;
    else x.y++;
    return x;
}

date query(int x, date tmp)
{
    //这一句加了 && x 
    if(tmp.m == 2 && tmp.d == 29 && x) x--, tmp = nxt_day(tmp);
    while(x)
    {    
        int num_year = 365;
        bool fl = runnian(tmp.y), fl2 = runnian(nxt_year(tmp).y);
         //如果当前包含了今年的 2 月 29 日
        if(tmp.m <= 2 && fl) if(fl) num_year++;
        if(tmp.m > 2 && fl2) num_year++;
        while(x && tmp.y + 1 == 1582) tmp = nxt_day(tmp), x--;
        while(x && tmp.y == 1582) tmp = nxt_day(tmp), x--;
        if(x < num_year) break;
        x -= num_year, tmp = nxt_year(tmp);
        
    }
    while(x) tmp = nxt_day(tmp), x--;
    return tmp;
}

int main()
{
    //freopen("julian.in", "r", stdin);
    //freopen("julian.out", "w", stdout);
    scanf("%d", &Q);
    memset(q, 0, sizeof(q));
    memset(ans, 0, sizeof(ans));
    last = (date) { -4713, 1, 1 };
    memset(q, 0, sizeof(q));
    for(int i = 1; i <= Q; i++) scanf("%d", &q[i].r), q[i].id = i;
    sort(q + 1, q + Q + 1, cmp);
    for(int i = 1; i <= Q; i++)
    {
        ans[q[i].id] = query(q[i].r - q[i - 1].r, last);
        last = ans[q[i].id];
    }
    for(int i = 1; i <= Q; i++)
    {
        if(ans[i].y > 0)
            printf("%d %d %d\n", ans[i].d, ans[i].m, ans[i].y);
        else
            printf("%d %d %d BC\n", ans[i].d, ans[i].m, -ans[i].y);
    }
    fclose(stdin);
    fclose(stdout);
    return 0;
}

T2 动物园

没想到 \(\mathrm{T2}\) 才是真正的签到题。考场上没有看到 \(q_i\) 互不相同的限制,写了奇奇怪怪的连边做法,炸成了 \(\mathrm{75pts}\). 至今未知为什么此做法的复杂度不正确。

考场代码:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <map>
#define LL long long //非常玄学,改成 unsigned long long 得分也不会有任何变化
using namespace std;

const int N = 2333333;
struct edge { LL nxt, to; } e[N];
LL n, m, c, k, num, cnt = 0, tot = 0, vis[N], b[N];
LL a[N], p[N], q[N], use[2333], can[2333], head[N];
unsigned long long ans = 1;

void add(LL x, LL y)
{
    e[++cnt] = (edge) { head[x], y };
    head[x] = cnt;
}

int main()
{
    scanf("%lld%lld%lld%lld", &n, &m, &c, &k);
    memset(vis, 0, sizeof(vis));
    memset(head, 0, sizeof(head));
    memset(p, 0, sizeof(p));
    memset(q, 0, sizeof(q));
    memset(use, 0, sizeof(use));
    memset(can, 0, sizeof(can));
    for(LL i = 1; i <= n; i++)
    {
        scanf("%lld", &a[i]);
        for(LL j = 0; j < k; j++)
            if(a[i] & (1 << j)) use[j] = 1;
    }
    for(LL i = 1; i <= m; i++)
    {
        scanf("%lld%lld", &p[i], &q[i]);
        b[++tot] = q[i];
    }
    sort(b + 1, b + m + 1);
    for(int i = 1; i <= m; i++)
    {
        q[i] = lower_bound(b + 1, b + m + 1, q[i]) - b - 1;
        add(p[i], q[i]);
        if(use[p[i]]) vis[q[i]] = 1;
    }
    for(LL i = 0; i < k; i++)
    {
        LL fl = 1;
        if(use[i]) { can[i] = 1; continue; }
        for(LL j = head[i]; j; j = e[j].nxt)
        {
            LL v = e[j].to;
            if(!vis[v]) { fl = 0; break; }
        }
        if(fl == 1) can[i] = 1;
    }
    for(LL i = 0; i < k; i++) if(can[i]) ans *= 2;
    printf("%lld", ans - n);
    return 0;
}

下面开始讲正解。

很显然,可以按位考虑。设当前位为 \(k\),如果 \(n\) 种动物中有第 \(k\) 位为 \(1\) 的动物,那么这一位一定有两种选法

如果当前位有限制条件,而且 \(n\) 种动物中没有当前位为 \(1\) 的动物,那么根据 \(q_i\) 不同,当前饲料一定未被购买。所以这一位只有一种选法

剩下没有当前位为 \(1\) 的、且没有限制条件的,有两种选法

根据格雷码的经验,一定要开 \(\mathrm{unsigned \space long \space long}\),并且需要特判 \(\mathrm{k = 64}\)\(\mathrm{n = 0}\) 的情况(我也不知道为啥)

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#define ULL unsigned long long
using namespace std;

const int N = 2333333, M = 2333;
ULL n, m, c, k, a, p, q, ans = 1;
ULL base[N], vis[M], del[M];

int main()
{
    scanf("%lld%lld%lld%lld", &n, &m, &c, &k);
    if(k == 64 && n == 0)
    {
        cout << "18446744073709551616";
        return 0;
    }
    memset(vis, 0, sizeof(vis));
    memset(del, 0, sizeof(del));
    base[0] = 1;
    for(int i = 1; i < k; i++) base[i] = base[i - 1] * 2;
    for(int i = 1; i <= n; i++)
    {
        scanf("%lld", &a);
        for(int j = 0; j < k; j++) if(a & base[j]) vis[j] = 1;
    }
    for(int i = 1; i <= m; i++)
    {
        scanf("%lld%lld", &p, &q);
        if(!vis[p]) del[p] = 1;
    }
    for(int i = 0; i < k; i++) if(!del[i]) ans <<= 1;
    printf("%lld", ans - n);
    return 0;
}

T3 函数调用 && T4 贪吃蛇 : 不会做

posted @ 2020-11-21 11:43  Nyxia  阅读(243)  评论(0编辑  收藏  举报