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
", ans[i].d, ans[i].m, ans[i].y);
else
printf("%d %d %d BC
", 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;
}