洛谷P1202 黑色星期五的蔡勒公式做法及原理介绍

前言

在刚做这道题时,我根本不知道蔡勒公式是什么,只是想自己总结一下规律,就问了Deepseek几个日期是星期几——结果他直接用蔡勒公式秒了.

我大为惊奇,就查了下资料,向大家介绍这个公式,以及这道题的蔡勒公式做法,希望对大家有帮助。

题目传送门

正文

蔡勒公式(Zeller's Congruence)是一种快速计算给定日期是星期几的数学公式,由德国数学家克里斯蒂安·蔡勒(Christian Zeller)于1883年提出。它适用于格里高利历(公历),能高效处理历史或未来日期的星期计算。


一、公式的两种形式

蔡勒公式有两种版本,分别对应 月份的不同处理方式:

简化版(月份从3月开始计数)

  • 将1月和2月视为上一年的13月和14月。(重点!)

  • 例如:1900年1月 → 视为1899年13月;1900年2月 → 视为1899年14月

  • 公式

h = \left( q + \left\lfloor \frac{13(m+1)}{5} \right\rfloor + K + \left\lfloor \frac{K}{4} \right\rfloor + \left\lfloor \frac{J}{4} \right\rfloor + 5J \right) \mod 7

h: 星期几(0=星期六, 1=星期日, 2=星期一, ..., 6=星期五

q: 日期(1-31)

m: 月份(3-14,对应1月到12月)

K: 年份的后两位(如1899年→K=99)

J: 年份的前两位(如1899年→J=18)


扩展版(月份从1月开始计数)

扩展版的蔡勒公式(Zeller's Congruence)直接使用原始年份和月份,公式如下:

h = \left( q + \left\lfloor \frac{13(m + 3)}{5} \right\rfloor + K + \left\lfloor \frac{K}{4} \right\rfloor + \left\lfloor \frac{J}{4} \right\rfloor + 5J \right) \mod 7

h: 星期几(0=星期日, 1=星期一, 2=星期二, ..., 6=星期六,注意与简化版映射不同!

q: 日期(1-31)

m: 月份(1-12,无需调整,1月=1,2月=2)

J: 年份的前两位(如2023年→J=20)

K: 年份的后两位(如2023年→K=23)

扩展版与简化版的对比

特性简化版扩展版
月份处理1月和2月需调整为13和14月直接使用1-12月,无需调整
结果映射0=星期六, 1=星期日, ..., 6=星期五0=星期日, 1=星期一, ..., 6=星期六
公式项差异使用 13(m+1)/5使用 13(m+3)/5

、公示证明

(这里给出的是简化版证明)

一、基准日与目标日期的天数差

  1. 选择基准日
    通常选择某年的3月1日作为基准(因为1月和2月被视为上一年的13月和14月,简化了闰年计算)。

  2. 目标日期
    设目标日期为 q年m月d日(若m=1或2,则视为m+12月,年份减1)。

  3. 计算总天数差
    总天数差 = 年份贡献 + 月份贡献 + 日期贡献。


二、分解各部分的贡献

1. 年份贡献
  • 平年贡献:每平年贡献365天,即365mod  7=1365mod7=1天。

  • 闰年贡献:每闰年多1天(2月29日),即额外贡献1天。

  • 总年份贡献

    年份天数=(年份差×365+闰年数)mod  7年份天数=(年份差×365+闰年数)mod7

    其中,闰年数由公式中的 ⌊K/4⌋+⌊J/4⌋体现。

2. 月份贡献
  • 各月的天数差异通过 月份修正项 ⌊13(m+1)/5⌋近似。

  • 推导逻辑
    假设每月平均约30.6天,通过调整系数抵消月份天数的波动。例如:

    • 3月(m=3):修正项为 ⌊13×4/5⌋=10,对应3月贡献0天(基准月)。

    • 4月(m=4):修正项为 ⌊13×5/5⌋=13,对应4月贡献3天(31天)。

    • 依次类推,修正项将月份差异转化为整数偏移。

3. 日期贡献
  • 直接使用日期 q,即 d 天。


三、整合公式

将年份、月份和日期的贡献相加,并取模7:

代码展示

(仅供学习用)

#include<bits/stdc++.h>
using namespace std;
int ll[10];
//蔡勒公式
int zeller(int q, int m, int J, int K) {
    if (m == 1 || m == 2) {
        m += 12;
        int year = J * 100 + K - 1;  
        J = year / 100;              
        K = year % 100;              
    }
    int h = (q + 13*(m+1)/5 + K + K/4 + J/4 + 5*J) % 7;
    h%=7;
    return h;
}
int main(){
	int n;
	cin>>n;
	int y=1900;
	while(1900+n-1>=y){
		for(int i=1;i<=12;i++){
			ll[zeller(13,i,y/100,y%100)]++;
		}
		y++;
	}
	for(int i=0;i<=6;i++){
		cout<<ll[i]<<" ";
	}
	return 0;
}

后记

(求点赞,关注)

### P3466 使用 `set` 数据结构 解题思路 对于不重复数字的问题,核心在于如何高效地维护一组无重复元素并支持快速插入、删除以及查找操作。使用 C++ STL 中的 `set` 容器能够很好地满足这些需求[^1]。 #### 关键特性 - **自动排序**:`set` 内部会保持所有元素有序排列; - **唯一性约束**:不允许存在相同的关键字; - **高效的增删查改**:平均时间复杂度为 O(log n),其中 n 是集合大小。 基于以上特点,可以通过如下方式解决问题: 1. 创建一个空的 `set<int>` 来存储已经遇到过的整数值。 2. 遍历给定序列中的每一个数 x: - 如果该数不在当前 set 当中,则表示这是一个新的独一无二的值,应该被加入到结果列表里,并且也添加至 set 中以便后续判断。 - 若已存在于 set 则跳过此轮迭代继续下一个数。 3. 输出最终的结果向量即为所求去重后的数组形式。 下面是具体的代码实现示例: ```cpp #include <iostream> #include <set> #include <vector> using namespace std; int main() { int n; cin >> n; // 输入n个整数 set<int> s; // 用于保存唯一的整数 vector<int> ans; // 存储答案 while (n--) { int num; cin >> num; if (!s.count(num)) { // 如果这个数还没有出现过 s.insert(num); // 插入到set里面 ans.push_back(num); // 同时追加到答案list后面 } } cout << ans.size() << endl; // 打印出去重之后的数量 for(auto &item : ans){ cout<< item <<" "; } // 显示所有的非重复项 return 0; } ``` 这段程序实现了对输入流的一次遍历,在线处理每一条记录的同时完成数据清洗工作,最后输出整理好的不含任何冗余成分的新序列。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值