2017山东一轮集训 Day1

2017山东一轮集训 Day1

Sum

题目描述:

​ 求有多少\(n\)位十进制数时\(p\)的倍数且每位之和小于等于$m_i  (m_i = 0,1,2, \dots , m - 1) $,允许前导零,答案对998244353取模。

数据范围:

$1 \leqslant n \leqslant 10^9  ,  1 \leqslant p \leqslant 50  ,  1 \leqslant m \leqslant 1000 $

题解:

​ 位数和?前导零?\(p\)的倍数?数位dp裸题???不好意思\(n\)是十亿。。。怎么办?怎么办?由于可以忽略掉前导零,那么考虑倍增一下:


\[ dp[i][j][k]:长度为i,数字和为j,\% p的余数为k的答案 \\ dp[i][j][k] =\sum_{x + y = j} \ \sum_{(a+b) \% p = k} dp[ \lfloor{ \frac{i}{2} }\rfloor ][x][a] * dp[ \lfloor{ \frac{i}{2} }\rfloor ][y][b] \]
​ 对于一个长度\(len\)我们先把\(\lfloor \frac{len}{2} \rfloor\)的答案即\(dp[\lfloor \frac{len}{2} \rfloor]\)求出来,这个直接递归算,然后先解决长度为偶数的情况。

​ 容易发现第一个和式是一个卷积的形式,那么我们将\(dp\)数组做一次\(DFT\),然后就可以愉快的直接暴力转移了。

​ 然后考虑长度为奇数的情况,其实就是少了一位(算出了\(len - 1\)的答案),那么只需要暴力枚举最高位即可。

​ 时间复杂度\(O((p^2m+mlogm)log n)\)

代码:
#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
using namespace std;
const int MOD = 998244353;
const int G = 3;
const int maxn = 3010;

inline int inc(int a, int b) { return (a + b >= MOD) ? (a + b - MOD) : (a + b); }
inline int dec(int a, int b) { return (a > b) ? (a - b) : (a + MOD - b); }
inline int mul(int a, int b) { return 1LL * a * b % MOD; }

inline int power(int x, int k) {
  int tmp = 1;
  while(k) {
    if(k & 1) tmp = mul(tmp,x);
    x = mul(x,x);
    k >>= 1;
    }
    return tmp;
}

namespace NTT{
  int len, invlen;
  
  inline void init(int n) {
    len = 1;
    while(len <= n) len <<= 1; len <<= 1;
    invlen = power(len,MOD - 2);
    }
    
    inline void rader(int *a, int n) {
      for(int i = 0, j = 0;i < n - 1;i ++) {
        if(i < j) swap(a[i],a[j]);
        int k = n >> 1;
        while(j >= k) {
          j -= k;
          k >>= 1;
            }
            if(j < k) j += k;
        }
    }
    
    inline void ntt(int *a, int kd) {
        int n = len;
      rader(a,n);
      for(int i = 2;i <= n;i <<= 1) {
        int pn = power(G,(MOD - 1) / i);
        for(int j = 0;j < n;j += i) {
          int p = 1;
          for(int k = 0;k < (i >> 1);k ++) {
            int x = a[k + j], y = mul(a[k + j + (i >> 1)],p);
            a[k + j] = inc(x,y);
            a[k + j + (i >> 1)] = dec(x,y);
            p = mul(p,pn);
                }
            }
        }
      if(kd == -1) {
        for(int i = 1;i < (n >> 1);i ++) swap(a[i],a[n - i]);
        for(int i = 0;i < n;i ++) a[i] = mul(a[i],invlen);
      }
  }
}

int n, p, m;
int g[maxn][maxn], f[maxn][maxn];

inline int work(int n) {
    if(!n) return 1;
  int mi = work(n >> 1);
  for(int i = 0;i < p;i ++) NTT::ntt(g[i],1);
  for(int i = 0;i < p;i ++) {
    for(int j = 0;j < p;j ++) {
      int now = (i + 1LL * j * mi % p) % p;
      for(int k = 0;k < NTT::len;k ++) f[now][k] = inc(f[now][k],mul(g[i][k],g[j][k]));
        }
    }
    for(int i = 0;i < p;i ++) NTT::ntt(f[i],-1);
    for(int i = 0;i < p;i ++) {
      for(int j = 0;j < NTT::len;j ++) g[i][j] = 0;
      for(int j = 0;j <= m;j ++) g[i][j] = f[i][j];
      for(int j = 0;j < NTT::len;j ++) f[i][j] = 0;
    }
    mi = 1LL * mi * mi % p;
    if(n & 1) {
      for(int i = 0;i < p;i ++) {
        for(int x = 0;x <= 9;x ++) {
          for(int j = 0;j + x <= m;j ++) {
            int now = (i + 1LL * x * mi % p) % p;
            f[now][j + x] = inc(f[now][j + x],g[i][j]);
                }
            }
        }
        for(int i = 0;i < p;i ++) {
          for(int j = 0;j <= m;j ++) g[i][j] = f[i][j];
          for(int j = 0;j < NTT::len;j ++) f[i][j] = 0;
        }
        mi = 1LL * mi * 10 % p;
    }
    return mi;
}

int main() {
    scanf("%d%d%d", &n, &p, &m);
    g[0][0] = 1; m ++;
    NTT::init(m);
    m --;
    work(n);
    int ans = 0;
    for(int i = 0;i <= m;i ++) {
        ans = inc(ans,g[0][i]);
        printf("%d ", ans);
    }
    puts("");
  return 0;
}

Set

题目描述:

​ 给出\(n\)个非负整数,将数划分成两个集合,记为一号集合和二号集合。\(x_1\)为一号集合中所有数的异或和,\(x_2\)为二号集合中所有数的异或和。在最大化\(x_1+x_2\)的前提下,最小化\(x_1\)

数据范围:

$1 \leqslant n \leqslant 10^5 $

题解:

​ 异或!选数的子集!线性基!!!

​ 设\(Sum\)为所有数的异或和,那么题目变成最大化Sum^x1+x1,且最小化x1。按套路,从大到小贪心的决策,假设当前在第k位:

​ 如果Sum的第k位为1:那么x1的第k位最好为1,这样就有\(2^{k - 1}\)的贡献

​ 如果Sum的第k位为0:那么x1的第k位最好为1,这样就有\(2^k\)的贡献

考虑用线性基来维护这个贪心,容易发现按Sum的二进制建出两个线性基:Sum为1的位,Sum为0的位。这样优先级也就很容易确定了:Sum为0的优先于Sum为1的,高位优先于低位,直接贪心即可。

时间复杂度:\(O(n log N)\)

代码:
#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
using namespace std;
typedef long long ll;
const int maxn = 100000 + 10;

int n, vis[maxn];
ll a[maxn], sum, s[70];

inline void ins(ll x) {
  for(ll i = 62;i >= 0;i --) {
    if(!(sum & (1LL << i)) && (x & (1LL << i))) {
      if(!s[i]) {
        s[i] = x;
        return;
            }
            else x ^= s[i];
        }
    }
    
    for(ll i = 62;i >= 0;i --) {
      if((sum & (1LL << i)) && (x & (1LL << i))) {
        if(!s[i]) {
          s[i] = x;
          return;
            }
            else x ^= s[i];
        }
    }
}

int main() {
    scanf("%d", &n); sum = 0;
    for(int i = 1;i <= n;i ++) scanf("%lld", &a[i]), sum ^= a[i];
    for(int i = 1;i <= n;i ++) ins(a[i]);
    ll ans = 0;
    for(ll i = 62;i >= 0;i --) {
        if(!(sum & (1LL << i))) {
          ll tmp = (ans ^ s[i]) + (ans ^ s[i] ^ sum);
          ll now = ans + (ans ^ sum);
          if(tmp > now || (tmp == now && ans < (ans ^ s[i])))
              ans ^= s[i];
        }
    }
    
    for(ll i = 62;i >= 0;i --) {
      if(sum & (1LL << i)) {
          ll tmp = (ans ^ s[i]) + (ans ^ s[i] ^ sum);
          ll now = ans + (ans ^ sum);
          if(tmp > now || (tmp == now && ans < (ans ^ s[i]))) ans ^= s[i];
        }
    }
    printf("%lld\n", sum ^ ans);
  return 0;
}

Sim:咕咕咕

转载于:https://www.cnblogs.com/ezhjw/p/9736685.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值