HDU 5514(容斥+dp)

题意

M个石头围成圈(0-indexed),有N个青蛙,青蛙开始都在0处,每只青蛙可以每次跳x_i,可以跳无限次,求至少被一个青蛙覆盖的石头id和

题解

对于每只青蛙,易知其跳过的石头为<gcd(x_i,M))>加法群中元素

即先处理得到S=\{b_i = gcd(x_i,M)) \}

单个贡献为b * \frac{\frac{M}{b}(\frac{M}{b}-1))}{2},即b,2b,3b,...之和

通过容斥可以得到

ans\\ =\sum_{T \subseteq S} (-1)^{|T|+1} lcm\{T\} * \frac{\frac{M}{lcm}(\frac{M}{lcm}-1))}{2}\\ =\sum_{T \subseteq S} (-1)^{|T|+1} \frac{\frac{M^2}{lcm}-M}{2}\\ =\frac{-M+M\sum_{d|m}\frac{M}{d} \sum_{T \subseteq S, lcm\{T\}=d}(-1)^{|T|+1}}{2}

问题转变为求容斥系数

对于dp[d][0/1][n]表示加入n个数对于M的因子d,正负系数(0/1)为多少

可以发现没加入一个b_i, 容斥系数变化为继承、正负互换后贡献、单个贡献:

\\ dp[d][0/1][n+1]=dp[n][0/1][n])\\ dp[lcm(d,b_i))][1/0][n+1]+=dp[d][0/1][n]\\ dp[b_i][0][n+1]+=1

#include <algorithm>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <unordered_map>
#include <vector>
using namespace std;

using LL = long long;
const int MAXN = 1e5 + 5;
int T, N, M;
int p[MAXN];
vector<int> fac;
int gcd(int a, int b);
int dp[1505][2][2], lcm[1505][1505];
unordered_map<int, int> MP;

int main() {
  scanf("%d", &T);
  for (int t = 1; t <= T; t++) {
    scanf("%d%d", &N, &M);
    fac.clear();
    int P = M;
    for (int i = 1; i * i <= P; i++)
      if (P % i == 0) {
        fac.push_back(i);
        if (i != P / i) fac.push_back(P / i);
      }
    sort(fac.begin(), fac.end());
    MP.clear();
    for (int i = 0; i < fac.size(); i++) MP[fac[i]] = i;
    for (int i = 0; i < fac.size(); i++)
      for (int j = 0; j <= i; j++) {
        int tmp;
        tmp = 1LL * fac[i] * fac[j] / gcd(fac[i], fac[j]);
        // cout << fac[i] << " " << fac[j] << " " << tmp << endl;
        lcm[i][j] = lcm[j][i] = MP[tmp];
      }
    int now = 0, pre = 1;
    for (int j = 0; j < fac.size(); j++) memset(dp[j], 0, sizeof(dp[j]));
    for (int i = 1; i <= N; i++) {
      scanf("%d", p + i);
      int b = gcd(p[i], M);
      int id = MP[b];
      for (int j = 0; j < fac.size(); j++) {
        dp[j][0][now] = dp[j][0][pre];
        dp[j][1][now] = dp[j][1][pre];
      }
      dp[id][0][now] += 1;
      for (int j = 0; j < fac.size(); j++) {
        dp[lcm[id][j]][0][now] += dp[j][1][pre];
        dp[lcm[id][j]][1][now] += dp[j][0][pre];
      }
      // for (int j = 0; j < fac.size(); j++)
      // printf("%d: %d %d\n", fac[j], dp[j][0][now], dp[j][1][now]);
      // printf("\n");
      swap(now, pre);
    }
    LL ans = 0;
    for (int j = 0; j < fac.size(); j++) {
      int coffi = dp[j][0][pre] - dp[j][1][pre];
      ans += 1LL * M / fac[j] * coffi;
    }
    ans = M * ans - M;
    ans /= 2;
    printf("Case #%d: %lld\n", t, ans);
  }
  return 0;
}

int gcd(int a, int b) { return (!b) ? a : gcd(b, a % b); }

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值