A、Browser Games

题目大意

给出个字符串,你需要输出行。

对于第个字符串来说,你需要在这些字符串里面分别找到一个前缀,并且满足这些前缀去重之后长度最小。

其次就是你曾经选择过的前缀不能做为前缀出现在这些字符串里面。

卡了空间只允许

Solution

考点:字符串hash

如果是正序的话比较容易考虑到字典树,但是字典树的空间这题卡空间过不去。

我们倒序的思考,对于最后一个字符串,只需要考虑前字符串里面第一个字符出现了几种就是答案了。

那么接下来如何处理加入的第个字符串。

我们把第个字符串的全部前缀都出来,接下来去一个里面把相同的前缀出现过的下标全部找出来,并且让这些下标在的位置前缀长度即可。然后处理完之后把第个字符串的前缀全部删掉,因为你已经有了它前缀的新字符串了。

这样一路往前递推就可以在的时间内完成这一系列操作。

const int N = 1e5 + 7, bas = 131;

ull p[N];
unordered_map<ull, vector<int>> mp;
int pos[N], ans[N];
string s[N];

int solve() {
    int n;
    cin >> n;
    for (int i = 1; i <= n; ++i) {
        cin >> s[i];
        pos[i] = 0;
        p[i] = s[i][0];
        mp[p[i]].push_back(i);
    }

    for (int i = n; i >= 1; --i) {
        ans[i] = mp.size();
        ull pre = 0;
        for (auto& c : s[i]) {
            pre = pre * bas + c;
            auto it = mp.find(pre);
            if (it == mp.end()) continue;

            for (auto& id : it->second) {
                if (id == i)    continue;
                ++pos[id];
                p[id] = p[id] * bas + s[id][pos[id]];
                mp[p[id]].push_back(id);
            }
            mp.erase(it);
        }
    }
    for (int i = 1; i <= n; ++i) {
        cout << ans[i] << endl;
    }

    return 1;
}

F、Train Wreck

题目大意

给你一个长度为的出栈入栈序列,你现在有辆列车,第辆列车颜色为,你要让每次停留在栈中颜色排列是唯一的。问是否可行,如果可行输出进栈颜色方案。

Solution

考点:堆

我们可以把出栈入栈转换成一棵树来考虑,初始栈为空的时候假设我们有一个节点的树并且这个结点编号为,接下来入栈就是当前节点新开辟一个儿子,出栈就是回到父亲,一直这样操作一个合理的出栈入栈顺序就会生成个节点。我们要做的就是给生成的个节点打上颜色,让每个点到的路径上构成的颜色序列唯一。

那么就很明显,我们每次让任意一个节点它的儿子节点们颜色互不相同就可以了。

我们用的方式填颜色,把同层次的颜色先选上不一样种颜色,并且每种颜色减一后插回原来的序列中。

我们使用堆可以很好的维护上面的数据结构。

#include <bits/stdc++.h>
using namespace std;
#define endl "\n"
#define pai pair<int, int>
#define ms(__x__,__val__) memset(__x__, __val__, sizeof(__x__))
#define debug(x) cout << #x << ":" << x << '\n'
typedef tuple<int, int> tup;
typedef long long ll; typedef unsigned long long ull; typedef long double ld;
inline ll read() { ll s = 0, w = 1; char ch = getchar(); for (; !isdigit(ch); ch = getchar()) if (ch == '-') w = -1; for (; isdigit(ch); ch = getchar())    s = (s << 1) + (s << 3) + (ch ^ 48); return s * w; }
inline void print(ll x, int op = 10) { if (!x) { putchar('0'); if (op)    putchar(op); return; }    char F[40]; ll tmp = x > 0 ? x : -x;    if (x < 0)putchar('-');    int cnt = 0;    while (tmp > 0) { F[cnt++] = tmp % 10 + '0';        tmp /= 10; }    while (cnt > 0)putchar(F[--cnt]);    if (op)    putchar(op); }
inline ll gcd(ll x, ll y) { return y ? gcd(y, x % y) : x; }
ll qpow(ll a, ll b) { ll ans = 1;    while (b) { if (b & 1)    ans *= a;        b >>= 1;        a *= a; }    return ans; }    ll qpow(ll a, ll b, ll mod) { ll ans = 1; while (b) { if (b & 1)(ans *= a) %= mod; b >>= 1; (a *= a) %= mod; }return ans % mod; }
const int dir[][2] = { {0,1},{1,0},{0,-1},{-1,0},{1,1},{1,-1},{-1,1},{-1,-1} };
const int MOD = 1e9 + 7;
const int INF = 0x3f3f3f3f;
const ll INF64 = 0x3f3f3f3f3f3f3f3f;

const int N = 2e6 + 7;
ll n, m;
int p[N];
char s[N];

// (()()()()()())


multiset<pai, greater<pai>> st;
vector<int> len[N];

int a[N];

int solve() {
    n = read();
    scanf("%s", s + 1);
    unordered_map<int, int> cnt;
    for (int i = 1; i <= n; ++i)    p[i] = read(), ++cnt[p[i]];

    for (int i = 1; i <= n; ++i) {
        if (cnt.count(i)) {
            st.insert({ cnt[i],i });
        }
    }

    int now = 0;
    for (int i = 1; i <= 2 * n; ++i) {
        if (s[i] == '(') {
            ++now;
            len[now].push_back(i);
        }
        else --now;
    }

    for (int i = 1; i <= n; ++i) {
        int sz = len[i].size();
        if (sz) {
            multiset<pai, greater<pai>> tmp;
            for (int j = 1; j <= sz; ++j) {
                if (st.empty()) {
                    puts("NO");
                    exit(0);
                }
                auto it = *st.begin();
                if (it.first == 0) {
                    puts("NO");
                    exit(0);
                }
                st.erase(st.find(it));
                tmp.insert(it);
            }
            for (auto& pos : len[i]) {
                auto it = *tmp.begin();
                a[pos] = it.second;
                tmp.erase(tmp.find(it));
                st.insert({ it.first - 1, it.second });
            }
        }
    }


    return 1;
}

signed main() {
    //int T = read(); for (int i = 1; i <= T; ++i)
    {
        //solve();
        cout << (solve() ? "YES" : "NO") << endl;
        stack<int> stk;
        for (int i = 1; i <= 2 * n; ++i) {
            if (s[i] == '(')    print(a[i], 32);
        }
        puts("");
    }
    return 0;
}

G、Game of Death

题目大意

场上共有个人,现在每个人都会随机选择一个其他人开枪,并且成功命中其他人的概率为

你需要输出场上留下个人的概率,分式对取模。

Solution

考点:子集反演+NTT

首先考虑状态设计,我们让代表被击中的是集合的概率,我们让代表被击中的是子集的概率。

所以我们可以得出

接下来根据子集反演的公式,我们可以得出

我们就把问题转变成求击中概率是子集的概率了,这个是比较好求的,如果我们假设代表开枪没打中人的概率。

集合中的人只能开枪没打中或者打中了中除自己以外的某个人,由于每个人都是独立的,概率直接做乘法即可;对于集合外的人,他们只能开枪没打中或者打中了集合中的某个人,同样做出乘法就可以算到

然后又可以发现只和集合大小有关和具体选择了那个集合没有关系。

所以我们假设是被击中集合大小为的所有概率之和,那么他就是行的答案。

然后后面那个求和又可以转换成卷积的形式快速求解到,我们把看成,那么后面这个式子就变成了定值的多项式,直接让,把就可以求解到后面的答案了。

const int N = 3e5 + 7; // 2^21

int n, a, b, p, q;
int jc[N], inv[N], G[N];

namespace NTT {
    int limit;
    vector<int> A, B, rev;
    inline int add(int x, int y) { return x += y, x >= MOD ? x - MOD : x; }
    inline int mul(int x, int y) { return 1ll * x * y % MOD; }
    int qpow(int x, int y) {
        int res = 1;
        for (; y; y >>= 1, x = mul(x, x)) if (y & 1) res = mul(res, x);
        return res;
    }
    void init() {
        int ed = n * 2, bit = -1;
        for (limit = 1; limit <= ed; limit <<= 1) ++bit;
        A.resize(limit); B.resize(limit); rev.resize(limit);
        for (int i = 0; i < limit; ++i)    rev[i] = (rev[i >> 1] >> 1) | ((i & 1) << bit);
    }
    void ntt(vector<int>& P, int op) {
        for (int i = 0; i < limit; ++i) {
            if (i < rev[i])swap(P[i], P[rev[i]]);
        }
        for (int mid = 1; mid < limit; mid <<= 1) {
            int euler = qpow(3, (MOD - 1) / (mid << 1));
            if (op < 0)    euler = qpow(euler, MOD - 2);
            for (int i = 0, pos = mid << 1; i < limit; i += pos) {
                int wk = 1;
                for (int j = 0; j < mid; ++j, wk = mul(wk, euler)) {
                    int x = P[i + j], y = mul(wk, P[i + j + mid]);
                    P[i + j] = add(x, y), P[i + j + mid] = add(x, MOD - y);
                }
            }
        }
        if (op > 0)    return;
        int inv = qpow(limit, MOD - 2);
        for (int i = 0; i < limit; ++i)    P[i] = mul(P[i], inv);
    }
    void work() {
        for (int i = 0; i <= n; ++i) A[i] = i & 1 ? MOD - inv[i] : inv[i];
        for (int i = 0; i <= n; ++i) B[i] = mul(G[i], inv[i]);
        ntt(A, 1), ntt(B, 1);
        for (int i = 0; i < limit; ++i)    A[i] = mul(A[i], B[i]);
        ntt(A, -1);
    }
}using namespace NTT;


void init(int n) {
    jc[0] = 1;
    for (int i = 1; i <= n; ++i) {
        jc[i] = mul(jc[i - 1], i);
    }
    inv[n] = qpow(jc[n], MOD - 2);
    for (int i = n - 1; i >= 0; --i) {
        inv[i] = mul(inv[i + 1], i + 1);
    }
}
int C(int n, int m) {
    return 1ll * jc[n] * inv[n - m] % MOD * inv[m] % MOD;
}

int solve() {
    n = read(), a = read(), b = read();

    init(n);
    p = mul(a, qpow(b, MOD - 2)); // 没打中概率
    q = (1 - p + MOD) % MOD; // 打中概率

    int tmp_inv = mul(inv[n - 1], jc[n - 2]);
    for (int i = 0; i <= n; ++i) {
        G[i] = mul(qpow(add(q, mul(mul(p, i - 1), tmp_inv)), i), qpow(add(q, mul(mul(p, i), tmp_inv)), n - i));
    }
    init(), work();
    for (int i = n; i >= 0; --i) {
        print(1ll * C(n, i) * A[i] % MOD * jc[i] % MOD);
    }

    return 1;
}

H、War of Inazuma (Easy Version)

题目大意

你要构造一个长度为字符串,一个下标在和其他下标的二进制表示只有一个不相同的有一条边,和这个点直接相连的下标和同一种字符最多出现次数为

Solution

方法1.队列实现

我们让字符串最后一个为,它对应的下标就是一个二进制全为,接下来和它相连的就是有一位为的二进制表示,把这些全部标成。这样一层一层往下用队列防止多次标记,用去依次把某些位置变成相反的值。

ll n, m;
int p[N];
bool vis[N];

inline int lowbit(int x) { return x & (-x); }

int solve() {
    n = read();
    m = (1 << n) - 1;
    queue<int> pq;

    pq.push(m);
    p[m] = 1;

    while (pq.size()) {
        int now = pq.front();
        pq.pop();
        if (vis[now])   continue;
        vis[now] = 1;
        for (int x = now, y; x; x -= y) {
            y = lowbit(x);
            p[now ^ y] = !p[now];
            pq.push(now ^ y);
        }
    }
    for (int i = 0; i <= m; ++i) {
        printf("%d", p[i]);
    }

    return 1;
}

方法2.内置函数

我们把上面那种方法展开画图可以发现,二进制表示有的在一层,有的在一层,有的在一层,所以我们直接按每个数二进制里面有多少个分类就行。

int solve() {
    n = read();
    m = (1 << n) - 1;

    for (int i = 0; i <= m; ++i) {
        putchar('0'+(__builtin_popcount(i) & 1));
    }

    return 1;
}

多校补题暂时告一段落了,度过了一个充实的暑假