省选测试48

A 随机除法

题目大意 : 一个1e24的数,每次在因数里等概率选一个除去,问期望多少次变成1

  • 设f[i]表示i到1的期望次数,f[1]=0, 则有

\[f[n]=\frac{\sum_{d|n}f[d]}{\sum _{d|n}1}+1 \]

\[f[n]=\frac{1+\sum_{d|n,d<n}f[d]}{-1+\sum _{d|n}1} \]

  • 而且发现一个数的期望次数之与质因数分解后每项的幂次集合有关

  • 集合最大是18,状态数不超过200000,于是搜出所有状态,类似高维前缀和的预处理dp数组就好了

Code

Show Code
#include <map>
#include <cstdio>

using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const ll MN = 1e12;
const int N = 2e5 + 5, M = 1e9 + 7;

int read(int x = 0, int f = 1, char c = getchar()) {
    for (; c < '0' || c > '9'; c = getchar()) if (c == '-') f = -1;
    for (; c >='0' && c <='9'; c = getchar()) x = x * 10 + c - '0';
    return x * f;
}

int p[] = {0, 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67};
int dc[N], c[N][20], cnt, s[N][20], f[N], m;
ull pw[90], h[N];
map<ull, int> mp;
char ch[30];

int Pow(int a, int k, int ans = 1) {
    for (; k; k >>= 1, a = 1ll * a * a % M)
        if (k & 1) ans = 1ll * ans * a % M;
    return ans;
}

void Dfs(long double n, int m, int x, int lt) {
    for (int i = 1; i <= 18; ++i) h[lt] += pw[c[lt][i]];
    mp[h[lt]] = lt;
    for (int i = 1; i <= m && n * p[x] <= 1e24; ++i) {
        c[++cnt][x] = i; dc[cnt] = dc[lt] * (i + 1);
        for (int j = 1; j < x; ++j) c[cnt][j] = c[lt][j];
        Dfs(n *= p[x], i, x + 1, cnt);
    }
}

int main() {
    freopen("div.in", "r", stdin);
    freopen("div.out", "w", stdout);
    for (int i = pw[0] = 1; i <= 80; ++i)
        pw[i] = pw[i-1] * 13331;
    dc[1] = cnt = 1; Dfs(1, 80, 1, 1);
    for (int i = 2; i <= cnt; ++i) {
        for (int j = 18; j >= 1; --j)
            if (c[i][j] && (s[i][j] = s[i][j+1] + s[mp[h[i]-pw[c[i][j]]+pw[c[i][j]-1]]][j]) >= M) s[i][j] -= M;
        f[i] = 1ll * (s[i][1] + dc[i]) * Pow(dc[i]-1, M-2) % M;
        for (int j = 1; j <= 18; ++j)
            if ((s[i][j] += f[i]) >= M) s[i][j] -= M;
    }
    while (scanf("%s%d", ch, &m) == 2) {
        ll a = 0, b = 0; ull ha = 0;
        for (int i = 0; ch[i]; ++i)
            b = b * 10 + ch[i] - '0', a = a * 10 + b / MN, b %= MN;
        for (int i = 1; i <= m; ++i) {
            int x, tot = 0; scanf("%d", &x);
            while ((a % x * MN + b) % x == 0)
                b += a % x * MN, a /= x, b /= x, tot++;
            ha += pw[tot];
        }
        for (int i = m + 1; i <= 18; ++i) ha += pw[0];
        printf("%d\n", f[mp[ha]]);
    }
}

B 炮塔

题目大意 : 给一行格子,干扰器可以捡或放,炮台两边至少有一个干扰器才能通过,问在某一时刻最多能有多少个干扰器

  • 记录x表示当前手里有多少个干扰器,s1表示如果手里至少有一个干扰器,能回到前面拿多少个干扰器,s2表示如果手里至少有两个干扰器,能回到前面拿多少个干扰器

  • 然后就开始分类讨论

    • 如果i是空地,直接走

    • 如果i有干扰器,拿走

    • 如果i是炮台

      • 如果i+1是空地,需要在i-1放一个干扰器,后面拿到一个干扰器就能拿到这个干扰器

      • 如果i+1是干扰器,直接走过去,但之前的s1都需要在后面拿到两个干扰器才能拿到这些干扰器

      • 如果i+1是炮台,i+2是干扰器,那在i-1放下这个干扰器,别的都不变,若i+2不是干扰器,就走不动了

Code

Show Code
#include <cstdio>
#include <cstring>

using namespace std;
const int N = 4e5 + 5;

char c[N];

int main() {
    freopen("tower.in", "r", stdin);
    freopen("tower.out", "w", stdout);
    int T; scanf("%d", &T);
    while (T--) {
        scanf("%s", c);
        int n = strlen(c); c[n] = c[n+1] = c[n+2] = '#';
        int x = 0, s1 = 0, s2 = 0, ans = 0;
        for (int i = 0; c[i]; ++i) {
            if (c[i] == '.');
            else if (c[i] == '*') x++;
            else if (c[i+1] == '.') { if (x) x--, s1++; else break; }
            else if (c[i+1] == '*') s2 += s1, s1 = 0;
            else if (c[i+2] == '*') { if (x) x--,  i++; else break; }
            else break;
            if (x >= 1) x += s1, s1 = 0;
            if (x >= 2) x += s2, s2 = 0;
            if (x > ans) ans = x;
        }
        printf("%d\n", ans);
    }
    return 0;
}

C 最大子段和

题目大意 : 找最大子段和,小b写的是找最大的正数区间,给出一个序列的奇数位置的值,自己填偶数位置,问小b的误差最大是多少

  • 首先一个结论是要么填-1要么填k,填负数是为了把正数区间分开,所以要填一个最大的,填正数的话大了也不会是答案更差,但有可能会更优,所以也填最大的

  • 于是就可以枚举最大子段区间[l,r],设f[i][j]表示前i个位置,偶数位填了j个-1,小b计算值的最小值,s[i]表示偶数位置全填k的前缀和

  • 就有f[i][j]=min{max(f[p-1][j-!(p&1)],s[i]-s[p])},要满足a[p]<0,a[p+1~i]都为正数,如果p是偶数位置可以填一个-1,如果p是奇数位就要强制转移了

  • 这就是n4的dp

  • 然后发现在最大子段和区间两边放k,让区间扩为[1,n]是不会更劣的,如果1,n位置是负数就不能扩到最边上了

  • 于是就不用枚举区间,可以n3了

  • 然后会发现f[i][j]在j相同时,转移点p是单调的,而且对于p为偶数的情况,f[p-1][j-1]是单调不降的,s[i]-s[p]是单调不升的

  • 于是在两个函数交叉点两边会有可能是最优决策点,而发现i变大后,s[i]-s[p]一定会变大,不会变小,所有决策点p一定是单调的

  • 于是就可以n2了

Code

Show Code
#include <cstdio>
#include <cstring>
#include <algorithm>

using namespace std;
const int N = 1e4 + 5;

int read(int x = 0, int f = 1, char c = getchar()) {
    for (; c < '0' || c > '9'; c = getchar()) if (c == '-') f = -1;
    for (; c >='0' && c <='9'; c = getchar()) x = x * 10 + c - '0';
    return x * f;
}

int n, k, a[N], m, f[N][N>>1], ans, s[N], sum;

int main() {
    freopen("subsegment.in", "r", stdin);
    freopen("subsegment.out", "w", stdout);
    n = read(); k = read(); m = n * 2 - 1;
    for (int i = 1; i <= m; i += 2) 
        a[i] = read(), sum += a[i];
    if (a[1] < 0) sum -= a[1], a[1] = 0;
    if (a[m] < 0) sum -= a[m], a[m] = 0;
    for (int i = 1; i <= m; ++i)
        s[i] = s[i-1] + (i & 1 ? a[i] : k);
    memset(f, 0x3f, sizeof(f)); f[0][0] = 0;
    for (int j = 0; j < n; ++j) {
        int l = 0, p = 0;
        for (int i = 1; i <= m; ++i) {
            if (a[i] < 0) l = i, p = i - 1;
            if (l) f[i][j] = max(f[l-1][j], s[i] - s[l]);
            else f[i][j] = s[i];
            for (; p + 2 <= i && s[i] - s[p+2] >= f[p+1][j-1]; p += 2);
            if (l < p) f[i][j] = min(f[i][j], s[i] - s[p]);
            f[i][j] = min(f[i][j], f[p+1][j-1]);
        }
        ans = max(ans, sum + (n - j - 1) * k - j - f[m][j]);
    }
    printf("%d\n", ans);
    return 0;
}
posted @ 2021-03-26 18:35  Shawk  阅读(44)  评论(0编辑  收藏  举报