作者: KSkun

[HAOI2012]高速公路 题解

[HAOI2012]高速公路 题解

题目地址:洛谷:【P2221】[HAOI2012]高速公路 – 洛谷、BZOJ:Problem 2752. — [HAOI2012]高速公路(road)

题目描述

Y901高速公路是一条重要的交通纽带,政府部门建设初期的投入以及使用期间的养护费用都不低,因此政府在这条高速公路上设立了许多收费站。
Y901高速公路是一条由N-1段路以及N个收费站组成的东西向的链,我们按照由西向东的顺序将收费站依次编号为1~N,从收费站i行驶到i+1(或从i+1行驶到i)需要收取Vi的费用。高速路刚建成时所有的路段都是免费的。
政府部门根据实际情况,会不定期地对连续路段的收费标准进行调整,根据政策涨价或降价。
无聊的小A同学总喜欢研究一些稀奇古怪的问题,他开车在这条高速路上行驶时想到了这样一个问题:对于给定的l,r(l<r),在第l个到第r个收费站里等概率随机取出两个不同的收费站a和b,那么从a行驶到b将期望花费多少费用呢?

输入输出格式

输入格式:
第一行2个正整数N,M,表示有N个收费站,M次调整或询问
接下来M行,每行将出现以下两种形式中的一种
C l r v 表示将第l个收费站到第r个收费站之间的所有道路的通行费全部增加v
Q l r 表示对于给定的l,r,要求回答小A的问题
所有C与Q操作中保证1<=l<r<=N

输出格式:
对于每次询问操作回答一行,输出一个既约分数
若答案为整数a,输出a/1

输入输出样例

输入样例#1:

4 5
C 1 4 2
C 1 2 -1
Q 1 2
Q 2 4
Q 1 4

输出样例#1:

1/1
8/3
17/6

说明

所有C操作中的v的绝对值不超过10000
在任何时刻任意道路的费用均为不超过10000的非负整数
所有测试点的详细情况如下表所示
Test N M
1 =10 =10
2 =100 =100
3 =1000 =1000
4 =10000 =10000
5 =50000 =50000
6 =60000 =60000
7 =70000 =70000
8 =80000 =80000
9 =90000 =90000
10 =100000 =100000

题解

既然所有情况是等概率的,那么期望可以定义成所有情况的和除以情况数量。我们考虑询问中单独一段路对所有情况的和的贡献,假如为第i段路(即连接第i个与第i+1个收费站的路),则为 (i-l+1)(r-i)v_i ,也就是说,答案是
\begin{aligned} \mathrm{E}(l, r) &= \frac{\sum_{i=l}^{r-1} (i-l+1)(r-i)v_i}{\mathrm{C}_{r-l+1}^2} \\ &= \frac{(r-lr)\sum_{i=l}^{r-1} v_i + (l+r-1) \sum_{i=l}^{r-1} iv_i - \sum_{i=l}^{r-1} i^2v_i}{(r-l)(r-l+1)/2} \end{aligned}
我们考虑用线段树维护v_i, iv_i, i^2v_i的值,每次询问把它们拿出来算一下即可。
当我们使用lazy标记的时候,肯定要求\sum_{i=l}^r i^2的值,平方和公式是\sum_{i=1}^n i^2 = \frac{n(n+1)(2n+1)}{6},可以做减法求出要求的值。

代码

// Code by KSkun, 2018/4
#include <cstdio>

#include <algorithm>

typedef long long LL;

inline char fgc() {
    static char buf[100000], *p1 = buf, *p2 = buf;
    return p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 100000, stdin), p1 == p2) ? EOF 
        : *p1++;
}

inline LL readint() {
    register LL res = 0, neg = 1;
    char c = fgc();
    while(c < '0' || c > '9') {
        if(c == '-') neg = -1;
        c = fgc();
    }
    while(c >= '0' && c <= '9') {
        res = res * 10 + c - '0';
        c = fgc();
    }
    return res * neg;
}

inline bool isop(char c) {
    return c == 'C' || c == 'Q';
}

inline char readop() {
    char c;
    while(!isop(c = fgc()));
    return c;
}

const int MAXN = 100005;

inline LL callr(LL l, LL r) {
    return (l + r) * (r - l + 1) / 2;
}

inline LL call2r2(LL l, LL r) {
    return r * (r + 1) * (2 * r + 1) / 6 - l * (l - 1) * (2 * l - 1) / 6;
}

inline LL gcd(LL a, LL b) {
    if(a < b) std::swap(a, b);
    LL t;
    while(b) {
        t = a; a = b; b = t % b;
    }
    return a;
}

#define lch o << 1
#define rch (o << 1) | 1
#define mid ((l + r) >> 1)

struct Node {
    LL v, iv, iiv, add;
} sgt[MAXN << 2];

inline void merge(Node &rt, Node ls, Node rs) {
    rt.v = ls.v + rs.v;
    rt.iv = ls.iv + rs.iv;
    rt.iiv = ls.iiv + rs.iiv;
}

inline void pushdown(int o, int l, int r) {
    if(sgt[o].add) {
        sgt[lch].add += sgt[o].add;
        sgt[rch].add += sgt[o].add;
        sgt[lch].v += sgt[o].add * (mid - l + 1);
        sgt[rch].v += sgt[o].add * (r - mid);
        sgt[lch].iv += sgt[o].add * callr(l, mid);
        sgt[rch].iv += sgt[o].add * callr(mid + 1, r);
        sgt[lch].iiv += sgt[o].add * call2r2(l, mid);
        sgt[rch].iiv += sgt[o].add * call2r2(mid + 1, r);
        sgt[o].add = 0;
    }
}

inline void add(int o, int l, int r, int ll, int rr, LL v) {
    if(l >= ll && r <= rr) {
        sgt[o].add += v;
        sgt[o].v += v * (r - l + 1);
        sgt[o].iv += v * callr(l, r);
        sgt[o].iiv += v * call2r2(l, r);
        return;
    }
    pushdown(o, l, r);
    if(ll <= mid) add(lch, l, mid, ll, rr, v);
    if(rr > mid) add(rch, mid + 1, r, ll, rr, v);
    merge(sgt[o], sgt[lch], sgt[rch]);
}

inline Node query(int o, int l, int r, int ll, int rr) {
    if(l == ll && r == rr) {
        return sgt[o];
    }
    pushdown(o, l, r);
    if(rr <= mid) {
        return query(lch, l, mid, ll, rr);
    } else if(ll > mid) {
        return query(rch, mid + 1, r, ll, rr);
    } else {
        Node res, ls = query(lch, l, mid, ll, mid), 
            rs = query(rch, mid + 1, r, mid + 1, rr);
        merge(res, ls, rs);
        return res;
    }
}

LL n, m, l, r, v;
char op;

int main() {
    n = readint(); m = readint();
    while(m--) {
        op = readop(); l = readint(); r = readint();
        if(op == 'C') {
            v = readint();
            add(1, 1, n - 1, l, r - 1, v);
        } else {
            Node res = query(1, 1, n - 1, l, r - 1);
            LL a = (r - l * r) * res.v + (l + r - 1) * res.iv - res.iiv,
                b = (r - l) * (r - l + 1) / 2, g = gcd(a, b);
            printf("%lld/%lld\n", a / g, b / g);
        }
    }
    return 0;
}
[SDOI2015]序列统计 题解

[SDOI2015]序列统计 题解

题目地址:洛谷:【P3321】[SDOI2015]序列统计 – 洛谷、BZOJ:Problem 3992. — [SDOI2015]序列统计

题目描述

小C有一个集合S,里面的元素都是小于M的非负整数。他用程序编写了一个数列生成器,可以生成一个长度为N的数列,数列中的每个数都属于集合S。小C用这个生成器生成了许多这样的数列。但是小C有一个问题需要你的帮助:给定整数x,求所有可以生成出的,且满足数列中所有数的乘积mod M的值等于x的不同的数列的有多少个。小C认为,两个数列{Ai}和{Bi}不同,当且仅当至少存在一个整数i,满足Ai≠Bi。另外,小C认为这个问题的答案可能很大,因此他只需要你帮助他求出答案mod 1004535809的值就可以了。

输入输出格式

输入格式:
一行,四个整数,N、M、x、|S|,其中|S|为集合S中元素个数。第二行,|S|个整数,表示集合S中的所有元素。

输出格式:
一行,一个整数,表示你求出的种类数mod 1004535809的值。

输入输出样例

输入样例#1:

4 3 1 2
1 2

输出样例#1:

8

题解

参考资料:bzoj 3992: [SDOI2015]序列统计 (NTT+快速幂+DP) – CSDN博客
本题需要的数学姿势:数学笔记:数论(欧拉函数、阶、原根) | KSkun’s Blog
首先,计数自然是一个动态规划,我们考虑设计状态dp[i][j]为选了i个数且结果mod M的值是j,转移如下
dp[i][j] = \sum_{k \in S} dp[i-1][j'] \ (j'k \bmod M = j)
直观上感觉直接做是一个O(n|S|)的,好像只能得10分。
接下来我们要考虑一个问题,模数很特殊,并不是常见的1000000007,而是1004535809,它的原根为3,是常用的NTT模数,难道出题人在指引我们使用NTT?
想用NTT做,首先得搞出卷积,卷积下标是加减的事情没办法像上面那样乘吧。那么考虑利用原根把乘法转换为加法,dp[i][j]表示选到第i个数结果mod M的值为g^j,改变以后的转移方程就成这样了
dp[i][j] = \sum_{g^k \bmod M \in S} dp[i-1][j'] \ ((j' + k) \bmod (M - 1) = j)
或者这样?用f[i]表示g^i是否在集合中。
dp[i][j] = \sum_{k=1}^{M-1} dp[i-1][j-k] \cdot f[k]
咦,后面那部分现在可以卷积了,而且你会发现每一层的转移中f数组都是不变的,也就是说这是个线性变换,参照矩阵快速幂的操作,我们可以做多项式的快速幂,即dp[n] = dp[0] \times f^n。答案便是 dp[n][i] \ (g^i=x)
于是我们的思路就很明确了:求原根、算出f数组、多项式快速幂。
没完呢!这里有一个坑点,集合内的元素可以有0,0显然对答案没贡献,所以你要跳过它处理。

代码

// Code by KSkun, 2018/4
#include <cstdio>
#include <cmath>
#include <cstring>

#include <algorithm>

typedef long long LL;

inline char fgc() {
    static char buf[100000], *p1 = buf, *p2 = buf;
    return p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 100000, stdin), p1 == p2) ? EOF 
        : *p1++;
}

inline LL readint() {
    register LL res = 0, neg = 1;
    char c = fgc();
    while(c < '0' || c > '9') {
        if(c == '-') neg = -1;
        c = fgc();
    }
    while(c >= '0' && c <= '9') {
        res = res * 10 + c - '0';
        c = fgc();
    }
    return res * neg;
}

inline LL fpow(LL n, LL k, LL p) {
    n %= p;
    LL res = 1;
    while(k) {
        if(k & 1) res = res * n % p;
        n = n * n % p;
        k >>= 1;
    }
    return res;
}


const int MAXN = 1 << 15, G = 3, MO = 1004535809;

int n, m, x, s, t;
LL f[MAXN], g[MAXN];
bool vis[MAXN];

LL rev[MAXN], dft1[MAXN], dft2[MAXN], dftr[MAXN];

inline void calrev(int n, int len) {
    for(int i = 0; i < n; i++) {
        rev[i] = (rev[i >> 1] >> 1) | ((i & 1) << (len - 1));
    }
}

inline void dft(int n, LL *arr, int f) {
    for(int i = 0; i < n; i++) {
        if(i < rev[i]) std::swap(arr[i], arr[rev[i]]);
    }
    for(int i = 1; i < n; i <<= 1) {
        LL gn = fpow(G, (MO - 1) / (i << 1), MO);
        if(f == -1) gn = fpow(gn, MO - 2, MO);
        for(int j = 0; j < n; j += i << 1) {
            LL g = 1;
            for(int k = 0; k < i; k++) {
                LL x = arr[j + k], y = g * arr[j + k + i] % MO;
                arr[j + k] = (x + y) % MO;
                arr[j + k + i] = ((x - y) % MO + MO) % MO;
                g = g * gn % MO;
            }
        }
    }
    if(f == -1) {
        LL invn = fpow(n, MO - 2, MO);
        for(int i = 0; i < n; i++) {
            arr[i] = arr[i] * invn % MO;
        }
    }
}

inline void ntt(LL *a, LL *b) {
    memset(dft1, 0, sizeof(dft1));
    memset(dft2, 0, sizeof(dft2));
    memset(dftr, 0, sizeof(dftr));
    int n, len = 0;
    for(n = 1; n <= (m - 1) << 1; n <<= 1) len++;
    for(int i = 0; i < n; i++) {
        dft1[i] = a[i];
        dft2[i] = b[i];
    }
    calrev(n, len);
    dft(n, dft1, 1); dft(n, dft2, 1);
    for(int i = 0; i < n; i++) {
        dftr[i] = dft1[i] * dft2[i] % MO;
    }
    dft(n, dftr, -1);
    for(int i = 0; i < m - 1; i++) {
        a[i] = (dftr[i] + dftr[i + m - 1]) % MO;
    }
}

inline LL calg(LL n) {
    int fact[8005], tot = 0, x = n - 1;
    for(int i = 2; i * i <= x; i++) {
        if(x % i == 0) {
            fact[tot++] = (n - 1) / i;
            while(x % i == 0) x /= i;
        }
    }
    if(x > 1) fact[tot++] = (n - 1) / x;
    bool success;
    for(int i = 2;; i++) {
        success = true;
        for(int j = 0; j < tot; j++) {
            if(fpow(i, fact[j], n) == 1) {
                success = false;
                break;
            }
        }
        if(success) return i;
    }
}

int main() {
    n = readint(); m = readint(); x = readint(); s = readint();
    for(int i = 1; i <= s; i++) {
        t = readint();
        vis[t] = 1;
    }
    int xpow, G = calg(m), gn = 1;
    for(int i = 0; i < m - 1; i++) {
        if(vis[gn]) f[i] = 1;
        if(gn == x) xpow = i;
        gn = gn * G % m;
    }
    g[0] = 1;
    for(int i = n; i; i >>= 1) {
        if(i & 1) ntt(g, f);
        ntt(f, f);
    }
    printf("%lld", g[xpow]);
    return 0;
}
[POI2000]病毒 题解

[POI2000]病毒 题解

题目地址:洛谷:【P2444】[POI2000]病毒 – 洛谷、BZOJ:Problem 2938. — [Poi2000]病毒

题目描述

二进制病毒审查委员会最近发现了如下的规律:某些确定的二进制串是病毒的代码。如果某段代码中不存在任何一段病毒代码,那么我们就称这段代码是安全的。现在委员会已经找出了所有的病毒代码段,试问,是否存在一个无限长的安全的二进制代码。
示例:
例如如果{011, 11, 00000}为病毒代码段,那么一个可能的无限长安全代码就是010101…。如果{01, 11, 000000}为病毒代码段,那么就不存在一个无限长的安全代码。
任务:
请写一个程序:
1.在文本文件WIR.IN中读入病毒代码;
2.判断是否存在一个无限长的安全代码;
3.将结果输出到文件WIR.OUT中。

输入输出格式

输入格式:
在文本文件WIR.IN的第一行包括一个整数n(n≤2000),表示病毒代码段的数目。以下的n行每一行都包括一个非空的01字符串——就是一个病毒代码段。所有病毒代码段的总长度不超过30000。

输出格式:
在文本文件WIR.OUT的第一行输出一个单词:
TAK——假如存在这样的代码;
NIE——如果不存在。

输入输出样例

输入样例#1:

3
01 
11 
00000

输出样例#1:

NIE

题解

这个题要找的是一个无限的安全代码,我们考虑用AC自动机的理论来做。
如果把AC自动机的fail和边全部看成有向边,这个图就叫做trie图,图上的某一条路径可以代表我们匹配的过程。安全代码的匹配过程中肯定是不会有不安全的代码段结尾的节点的,又因为无限长的安全代码匹配过程肯定是无限的,如果在trie图上找到一个环,使得环上不出现不安全代码的结尾,那么说明存在这样一个无限循环的安全代码。
因此本题的思路就是在trie图上DFS找环。

代码

// Code by KSkun, 2018/3
#include <cstdio>

#include <queue>

const int MAXN = 30005;

int ch[MAXN][2], fail[MAXN], cnt;
bool val[MAXN];
std::queue<int> que;

inline void insert(char *str) {
    int p = 0;
    for(int i = 0; str[i]; i++) {
        if(!ch[p][str[i] - '0']) ch[p][str[i] - '0'] = ++cnt;
        p = ch[p][str[i] - '0'];
    }
    val[p] = true;
}

inline void calfail() {
    for(int i = 0; i <= 1; i++) {
        if(ch[0][i]) {
            fail[ch[0][i]] = 0;
            que.push(ch[0][i]);
        }
    }
    while(!que.empty()) {
        int u = que.front(); que.pop();
        for(int i = 0; i <= 1; i++) {
            if(ch[u][i]) {
                fail[ch[u][i]] = ch[fail[u]][i];
                val[ch[u][i]] |= val[fail[ch[u][i]]];
                que.push(ch[u][i]);
            } else {
                ch[u][i] = ch[fail[u]][i];
            }
        }
    }
}

bool vis[MAXN], visd[MAXN], success = false;

inline void dfs(int p) {
    vis[p] = true;
    for(int i = 0; i <= 1; i++) {
        if(vis[ch[p][i]]) {
            success = true;
            return;
        }
        if(!val[ch[p][i]] && !visd[ch[p][i]]) {
            visd[ch[p][i]] = true;
            dfs(ch[p][i]);
            if(success) return;
        }
    }
    vis[p] = false;
}

int n;
char str[MAXN];

int main() {
    scanf("%d", &n);
    for(int i = 0; i < n; i++) {
        scanf("%s", str);
        insert(str);
    }
    calfail();
    dfs(0);
    puts(success ? "TAK" : "NIE");
    return 0;
}