P2714 循环不变式的基础应用

你听说过循环不变式吗?不妨来品鉴一下吧:WeLikeStudying 大佬的博客:循环不变式

而这篇文章,只是对大佬博客的小小注解,外加一点实际应用。


  • 我们可以把循环不变式理解成一组条件。在每次循环中,我们保证这组条件均为真。而最后循环就可以得出我们想要的结果。

  • 如果思考本质,这实际上可以一种数学归纳法。我们保证了循环任何时候都满足条件,只需要验证满足了这组条件就能求出答案。

  • 满足的条件可以是显示在代码中的,也可以是隐藏起来的。

举个例子,斐波拉契数列中,我们创造两个变量 a,b,且保证:

a=fib(i),b=fib(i+1)

那么就可以求出答案。

  • 我们可以发现,选择一组好实现的条件,是化繁为简的关键。

现在我们看到这道题:

  • 首先这道题和 gcd 有关,再看到数据范围,这意味着我们的复杂度只要和每个数的倍数有关,就能通过此题。

  • 最简单的条件是创造数组 fi,且 f(i)=ans(i)。其中 ans(i) 就是 gcd(a,b,c,d)=i 的答案。但是我们发现这不好实现。

  • 考虑扩大条件。我们不能求出恰好的答案,但是我们可以求出至少的答案。具体地,我们定义 gigcd(a,b,c,d)|i 的个数。我们枚举每个数的倍数,可以在 nlnn 的时间内求出来。

  • 最后,我们寻找这两者的关系。要得到恰好为 i 的答案,就要把所有比 i 大的答案都从 gi 剔除出去。因此,我们得出 fi=gij|ifj

  • 注意这个式子去除了一次 fi。但是由于此时 fi 没有求出,应该为 0,所以没有影响。

总结一下,在程序中,我们只要满足下面的条件,那么 f(1) 就是答案(设 i 在序列中出现了 numi 次)。

gi=j|inumj

fi=gij|ifj

下面是代码:

#include<iostream>
#include<algorithm>

using namespace std;

const int N = 1e4 + 5;

int n;
int a[N];

long long ton[N], f[N], g[N];

void solve() {
  for (int i = 1; i <= n; ++i) cin >> a[i];

  int mxa = 0;
  for (int i = 1; i <= n; ++i) mxa = max(mxa, a[i]);
  for (int i = 1; i <= mxa; ++i) f[i] = g[i] = ton[i] = 0;
  for (int i = 1; i <= n; ++i) ++ton[a[i]];

  for (int i = mxa; i >= 1; --i) {
    long long sum_g = 0, sum_f = 0;
    for (int j = 1; i * j <= mxa; ++j) {
      sum_g += ton[i * j];
      sum_f += f[i * j];
    }
    g[i] = (sum_g * (sum_g - 1) * (sum_g - 2) * (sum_g - 3)) / (4 * 3 * 2 * 1);
    f[i] = max(1LL * 0, g[i] - sum_f);
  }

  cout << f[1] << endl;
}

signed main() {
  ios::sync_with_stdio(false);
  cin.tie(0);
  cout.tie(0);

  while (cin >> n) solve();

  return 0;
}

posted @   _maze  阅读(19)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· C#/.NET/.NET Core技术前沿周刊 | 第 29 期(2025年3.1-3.9)
· 从HTTP原因短语缺失研究HTTP/2和HTTP/3的设计差异
点击右上角即可分享
微信分享提示