A-Bit Common

题意

计算满足长度为,每个元素小于,且存在至少一个子序列满足按位后是的序列的数量。

答案对正整数取模。

数据范围

思路

按位分析,对于一个长度为的序列,序列中的数分为个末尾是的数和个末尾是的数。

  • 末尾为的数中,除末位以外的数位(位),每一位的组合是种(要除去全为1的情况)。
  • 末尾为的数中,除末位以外数位上的取值是任意的。
  • 选择哪些数是末尾需要考虑组合数。

所以,总方案数是:

最后对取模即可。

代码

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll maxn = 5050;
 
ll qpow(ll a, ll b, ll m) {
    // 快速幂
    ll res = 1;
    while (b) {
        if (b & 1)res = res * a % m;
        a = a * a % m;
        b >>= 1;
    }
    return res % m;
}
 
ll C[maxn][maxn];
 
void solve() {
    ll n, m, q;
    cin >> n >> m >> q;
    C[0][0] = 1;
    for (ll i = 1;i <= n;i++) {
        C[i][0] = 1, C[i][i] = 1;
        for (ll j = 1;j < i;j++) {
            C[i][j] = (C[i - 1][j - 1] + C[i - 1][j]) % q;
        }
    }
    ll ans = 0;
    for (ll k = 1;k <= n;k++) {
        ll tmp = C[n][k];
        ll x = (qpow(2, n, q) - qpow(2, n - k, q) + q) % q;
        tmp *= qpow(x, m - 1, q);
        tmp %= q;
        ans = (ans + tmp + q) % q;
    }
    cout << ans % q << "\n";
}
 
int main() {
    int _ = 1;
    // cin >> _;cin.get();
    while (_--)
        solve();
 
    return 0;
}

B-A Bit More Common

题意

在A题的条件下,增加要求满足存在两个不同的非空子序列和是

数据范围

思路

有A题的基础,我们已知至少有个子序列的组合数,接下来我们计算只有个子序列的组合数。两者相减即可求出答案。

满足只有1个子序列的组合数所代表的情况是这样的:

需要满足每一个竖列至少有一个特殊的(这个0同时是该数位上唯一的0),且这k个数每个数至少有1个​(条件2)。

对于偶数的组合种数还是不变的,为

  • 时,子序列是,只有一个数,剩余的个数都是偶数。

  • 时,考虑:一共有个特殊,每个有对应的数位,要将这分配到个位置(位置并不互相区分),每个位置至少有个,也就是第二类斯特林数

考虑状态转移:

现在有个数,要从个特殊位的基础上再增加一个特殊0,这个特殊位要么增加到第个数(第个数已经有至少个特殊)上,要么增加到前个(已经至少包含一个特殊的)数上,这两种增加方法都有种不同的方案。

个数中要包含个特殊的方案数记为,则存在递推式:

考虑到只有个数中特殊的个数大于等于k时的方案数才是有效的,同时,每个有效方案中,每一列除了作为特殊的位置以外的位置都可以任意填(只要不是全1或者只有1个特殊的情况),故这个奇数的方案总数是,再乘此时的偶数方案数,即为时的总数。

代码

ll qpow(ll a, ll b, ll m) {
    // 快速幂
    ll res = 1;
    a %= m;
    while (b) {
        if (b & 1)res = res * a % m;
        a = a * a % m;
        b >>= 1;
    }
    return res % m;
}
 
ll C[maxn][maxn];
ll f[maxn][maxn];
 
void solve() {
    ll n, m, q;
    cin >> n >> m >> q;
    C[0][0] = 1ll;
    for (ll i = 1;i <= 5000;i++) {
        C[i][0] = 1, C[i][i] = 1;
        for (ll j = 1;j <= 5000;j++) {
            C[i][j] = (C[i - 1][j - 1] + C[i - 1][j]) % q;
        }
    }
    ll ans = 0;
    for (ll k = 1;k <= n;k++) {
        ll tmp = C[n][k];
        ll x = (qpow(2ll, n, q) - qpow(2ll, n - k, q) + q) % q;
        tmp *= qpow(x, m - 1ll, q);
        tmp %= q;
        ans = (ans + tmp + q) % q;
    }
 
    ll res = qpow(2ll, (n - 1) * (m - 1) % q, q) * n % q;
    // 存在1个子序列AND=1的,减去只有1个子序列AND=1的
    f[0][0] = 1ll;
    for (ll i = 1;i <= 5000;i++) {
        for (ll j = i;j <= 5000;j++) {
            f[i][j] = i * ((f[i - 1][j - 1] + f[i][j - 1]) % q) % q;
        }
    }
    for (ll k = 2;k <= n;k++) {
        ll tmp = qpow(2ll, (n - k) * (m - 1) % q, q) % q * C[n][k] % q;
        ll tmp2 = (qpow(2ll, k, q) - 1ll - k + q) % q;
        ll cur = 1ll;
        for (ll t = m - 1;t >= k;t--) {
            res = (res + f[k][t] * cur % q * C[m - 1][t] % q * tmp % q) % q;
            cur = tmp2 * cur % q;
        }
    }
 
    cout << (ans - res + q) % q << "\n";
}

C-Sum of Suffix Sums

题意

维护一个初始为空的非负整数序列,支持次如下操作:

每次移除末尾的个整数,然后在末尾假如一个整数,操作后输出当前序列所有后缀和的总和,答案对取模。

数据范围

思路

每个数对后缀和总和的贡献是它当前位置的序号乘以它本身的值,也就是的贡献是​。根据这个特点和数据范围,我们可以维护后缀和总和。

代码

void solve() {
    ll q;cin >> q;
    vector<ll>a;
    ll tot = 0;
    while (q--) {
        ll t, v;cin >> t >> v;
        v %= mo;
        for (ll i = 0;i < t;i++) {
            if (a.empty())break;
            ll x = a.back();
            tot -= x * a.size();
            tot = (tot + mo) % mo;
            a.pop_back();
        }
        a.push_back(v);
        ll n = a.size() % mo;
        tot = (tot + v * n % mo + mo) % mo;
        cout << tot << "\n";
    }
}

H-World Finals

签到题。

题意

lzr010506队伍可以参加46th的WF和47th的WF,这两个WF将同时举行,所以lzr010506必须选择其中一个参加。lzr010506可以预知每个队伍的在比赛中的成绩(过题数和罚时),排名规则为过题数多优先,过题数相同则罚时少优先。

除了lzr010506队伍,还有其他有资格参加两场比赛的队伍。提问,在给出所有队伍的预测成绩之后,lzr010506队伍能够获得的最好成绩排名是多少。

数据范围

思路

最优情况是,除了lzr010506队伍,同时可以参加两场比赛的队伍都参加lzr010506队伍没有参加那一场,然后计算最佳排名即可。

代码

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
 
struct team {
    string s;
    ll p, t;
    bool operator<(const team& other)const {
        if (other.p == p)return other.t > t;
        return other.p < p;
    }
};
 
void solve() {
    ll n;cin >> n;
    vector<team>t46(n);
    map<string, bool>mp46;
    for (ll i = 0;i < n;i++) {
        string s; ll p, t;
        cin >> s >> p >> t;
        t46[i] = { s,p,t };
        mp46[s] = true;
    }
    ll m;cin >> m;
    vector<team>t47(m);
    map<string, bool>both;
    for (ll i = 0;i < m;i++) {
        string s; ll p, t;
        cin >> s >> p >> t;
        t47[i] = { s,p,t };
        if (mp46.count(s)) {
            both[s] = true;
        }
    }
    sort(t46.begin(), t46.end());
    sort(t47.begin(), t47.end());
    string st = "lzr010506";
    ll rk = min(n, m);
    ll cnt = 1;
    for (ll i = 0;i < n;i++) {
        if (t46[i].s == st) {
            rk = min(cnt, rk);
            break;
        }
        if (both.count(t46[i].s))continue;
        cnt++;
    }
    cnt = 1;
    for (ll i = 0;i < m;i++) {
        if (t47[i].s == st) {
            rk = min(cnt, rk);
            break;
        }
        if (both.count(t47[i].s))continue;
        cnt++;
    }
    cout << rk << "\n";
}
 
int main() {
    int t = 1;
    // cin >> t;cin.get();
    while (t--)
        solve();
 
    return 0;
}

I-Mirror Maze

补出这题ddl也是死而无憾了。

题意

​的矩阵镜子迷宫里,给出一个点光源,光的传播与镜子的类型\|-/有关(详情戳这)。

提问经过足够长的时间后,这束光被多少个不同的镜子反射过。

数据范围

思路

考虑到光路可逆,在迷宫中的所有可能存在的光路必然是链或者环,不会出现分叉或者是汇集。考虑数据范围和光的方向有上下左右4个,可以通过记忆化搜索来记录已经搜索过的状态,保证每次搜索的复杂度在以内,总复杂度不超过

具体搜索方法见代码。

代码

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
 
map<pair<ll, char>, ll>redir;
 
void initRedir() {
    redir[{1, '|'}] = 1;
    redir[{1, '-'}] = 2;
    redir[{1, '/'}] = 4;
    redir[{1, '\\'}] = 3;
 
    redir[{2, '|'}] = 2;
    redir[{2, '-'}] = 1;
    redir[{2, '/'}] = 3;
    redir[{2, '\\'}] = 4;
 
    redir[{3, '|'}] = 4;
    redir[{3, '-'}] = 3;
    redir[{3, '/'}] = 2;
    redir[{3, '\\'}] = 1;
 
    redir[{4, '|'}] = 3;
    redir[{4, '-'}] = 4;
    redir[{4, '/'}] = 1;
    redir[{4, '\\'}] = 2;
}
 
bool vis[1005][1005][5] = { false }, visp[1005][1005] = { false };
ll n, m;
char maz[1005][1005];
ll mem[1005][1005][5] = { 0 };
 
ll circlelen;
 
ll dfs(ll u, ll v, ll dr, ll cnt) {
    if (u<1 || u>n || v<1 || v>m) {
        // 通过边界出射的点是链型光路
        vis[u][v][dr] = false;visp[u][v] = false;
        mem[u][v][dr] = cnt;
        return cnt;
    }
    if (vis[u][v][dr]) {
        // 因为访问过同样状态结束搜索的是环形光路
        vis[u][v][dr] = false;visp[u][v] = false;
        circlelen = cnt;
        return cnt;
    }
    if (mem[u][v][dr])return mem[u][v][dr];
 
    vis[u][v][dr] = true;
 
    ll rdr = redir[{dr, maz[u][v]}];
 
    if (!visp[u][v] && dr != rdr) {
        cnt++;
        visp[u][v] = true;
    }
 
    if (rdr == 1) cnt = dfs(u - 1, v, 1, cnt);
    else if (rdr == 2) cnt = dfs(u + 1, v, 2, cnt);
    else if (rdr == 3) cnt = dfs(u, v - 1, 3, cnt);
    else cnt = dfs(u, v + 1, 4, cnt);
 
    // 回溯时复原状态
    vis[u][v][dr] = false;
    visp[u][v] = false;
    // 若是环形光路,在dfs回溯时mem应该记录为环形路径的长度
    if (circlelen)
        mem[u][v][dr] = circlelen;
    return cnt;
}
 
void solve() {
    cin >> n >> m;
    for (ll i = 1;i <= n;i++) {
        for (ll j = 1;j <= m;j++) {
            cin >> maz[i][j];
        }
    }
    // pre();
    ll q;cin >> q;
    while (q--) {
        ll u, v;string dir;
        cin >> u >> v >> dir;
        ll dr;
        if (dir == "above") { dr = 1;u--; }
        else if (dir == "below") { dr = 2;u++; }
        else if (dir == "left") { dr = 3; v--; }
        else if (dir == "right") { dr = 4; v++; }
        circlelen = 0;
        ll ans = dfs(u, v, dr, 0);
        cout << ans << endl;
    }
}
 
int main() {
    initRedir();
    int t = 1;
    // cin >> t; cin.get();
    while (t--)
        solve();
 
    return 0;
}