2019牛客暑期多校训练营(第七场)

湖南省第一和多校前50全被队友葬送了,BC那么简单却给我送了十几发罚时,而且还没过!何神轩少请客吃饭是没得跑了
A String

解法:每次暴力枚举最长的合法串就行,签到
#include<bits/stdc++.h>
using namespace std;
const int maxn = 5e4 + 10;
char s[maxn], t[maxn], tmp[maxn];
queue<char> q;
int ok(int i, int j) {

    int n = j - i + 1;
    for (int k = 1; k < n; k++) {
        for (int p = 0; p < n; p++) {
            int q = k + p + i;
            if (q > j)
                q = q - j - 1 + i;
            if (s[p + i] > s[q])
                return 0;
            else if (s[p + i] < s[q])
                break;
        }
    }
    return 1;
}
int main() {
    int T;
    scanf("%d", &T);
    while (T--) {
        scanf("%s", s);
        int n = strlen(s);
        int i = 0;
        while (i < n) {

            int j = i;
            for (int pos = i + 1; pos < n; pos++)
                if (ok(i, pos))
                    j = pos;
            for (int k = i; k <= j; k++)
                printf("%c", s[k]);
            if (j == n - 1)
                puts("");
            else
                printf(" ");
            i = j + 1;
        }
    }
}

B Irreducible Polynomial

猜的结论:n>=3一定可以因式分解(百度验证猜对了),接下来我们分析n <=1,一定是不能分解,n=2时就是显然只有一种情况无法因式分解,那就是多项式中b^2 - 4ac<0
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn = 5e4 + 10;
ll a[maxn];
int main() {
    int T;
    scanf("%d", &T);
    while (T--) {
        int n;
        cin>>n;
        for (int i = 0; i <= n; i++)
            cin>>a[i];
        if (n <= 1) {
            puts("Yes");
        }
        else if (n >= 3)
            puts("No");
        else {
            if (a[1] * a[1] < 4 * a[0] * a[2])
                puts("Yes");
            else
                puts("No");
        }
    }
}

C Governing sand

题意:有n种树,每种树有高度h,数量p,和砍一颗的代价c,现在要你去砍树,使得最高的树的数量和大于剩下的所有树的数量和,求出最小代价
解法:我们按照高度排序后枚举高度,假设该数高度为最高 i,比它高的树全部要砍掉,我们求出所有比 i 低的树总和sum,如果sum >= 该树的数量v,就意味着我们还有再砍sum - v + 1颗比 i 的低的树,我们肯定优先砍便宜的树,但是我们知道c<=200,意味着我们最多200次就可以算出砍这些树的最小花费,最后跟答案取最小值即可
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn = 1e5 + 10;
struct node {
    int h, c, p;
    bool operator<(const node& t) const {
        return h < t.h;
    }
} a[maxn];
ll suf[maxn], C[210];
int main() {
    int n;
    while (~scanf("%d", &n)) {
        for (int i = 1; i <= n; i++)
            scanf("%d%d%d", &a[i].h, &a[i].c, &a[i].p);
        sort(a + 1, a + 1 + n);
        ll ans = 1e18;
        ll sum = 0;
        suf[n + 1] = 0;
        for (int i = n; i; i--)
            suf[i] = suf[i + 1] + 1ll * a[i].c * a[i].p;
        for (int i = 1; i <= 200; i++)
            C[i] = 0;
        for (int i = 1, j; i <= n; i = j) {
            ll v = 0;
            for (j = i; j <= n; j++)
                if (a[j].h == a[i].h)
                    v += a[j].p;
                else
                    break;
            ll res = sum - v + 1;
            ll tmp = suf[j];
            if (res > 0) {
                for (int k = 1; k <= 200 && res; k++)
                    if (C[k] <= res)
                        tmp += C[k] * k, res -= C[k];
                    else
                        tmp += res * k, res = 0;
            }
            ans = min(ans, tmp);
            for (int k = i; k < j; k++)
                sum += a[k].p, C[a[k].c] += a[k].p;
        }
        printf("%lld\n", ans);
    }
}

E Find the median

题意:有一个初始序列(没有元素),有n次操作,每次操作给序列加这些元素: Li, Li+1,Li+2,,,Ri,然后求序列的中位数是多少,如果序列长度为偶数n,中位数为从小到大排序第n/2个数
解法:裸线段树,如果不卡空间,直接动态开点,如果不能动态开点,那我们把所有查询区间变成左开右闭区间然后离散化搞搞就行,没啥好讲的,线段树简单题
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn = 8e5 + 10;
ll sum[maxn * 4];
int S[maxn], L[maxn], R[maxn], tag[maxn * 4], n, sz;
#define ls o * 2
#define rs o * 2 + 1
#define m (l + r) / 2
void pushdown(int o, int l, int r) {
    if (tag[o] == 0)
        return;
    tag[ls] += tag[o]; tag[rs] += tag[o];
    sum[ls] += tag[o] * (S[m] - S[l - 1]);
    sum[rs] += tag[o] * (S[r] - S[m]);
    tag[o] = 0;
}
void up(int o, int l, int r, int ql, int qr) {
    if (l >=ql && r <= qr) {
        sum[o] += S[r] - S[l - 1];
        tag[o]++;
        return;
    }
    pushdown(o, l, r);
    if (ql <= m)
        up(ls, l, m, ql, qr);
    if (qr > m)
        up(rs, m + 1, r, ql, qr);
    sum[o] = sum[ls] + sum[rs];
}
int qu(int o, int l, int r, ll v) {
    if (l == r) {
        int k = sum[o] / (S[r] - S[l - 1]);
        return (v + k - 1)/ k + S[l - 1];
    }
    pushdown(o, l, r);
    if (sum[ls] < v)
        return qu(rs, m + 1, r, v - sum[ls]);
    return qu(ls, l, m, v);
}
ll X[maxn], Y[maxn];
void init() {
    ll A1, B1, C1, M1;
    ll A2, B2, C2, M2;
    cin>>n;
    cin>>X[1]>>X[2]>>A1>>B1>>C1>>M1;
    cin>>Y[1]>>Y[2]>>A2>>B2>>C2>>M2;
    for (int i = 1; i <= n; i++) {
        if (i > 2) {
            X[i] = (A1 * X[i - 1] + B1 * X[i - 2] + C1) % M1;
            Y[i] = (A2 * Y[i - 1] + B2 * Y[i - 2] + C2) % M2;
        }
        L[i] = min(X[i], Y[i]);
        R[i] = max(X[i], Y[i]) + 1;
        S[++sz] = L[i];
        S[++sz] = R[i];
    }
    sort(S + 1, S + 1 + sz);
    sz = unique(S + 1, S + 1 + sz) - S - 1;
}
int main() {
    init();
    for (int i = 1; i <= n; i++) {
        L[i] = lower_bound(S + 1, S + 1 + sz, L[i]) - S;
        R[i] = lower_bound(S + 1, S + 1 + sz, R[i]) - S;
        up(1, 1, sz, L[i] + 1, R[i]);
        ll v = sum[1] / 2;
        if (sum[1] & 1)
            v++;
        printf("%d\n", qu(1, 1, sz, v));
    }
}

F Energy stones

题意:有n个石头,每个石头有初始能量Ei,每秒能增加Li能量,能量值上限Ci,初始时间为0,现在有m次操作,每次在 ti (ti < ti+1)时刻选择一段区间的石头,把他们能量全部拿走,然后这些石头当前能量就会变成0,求m次操作之后,我能得到的总能量。
解法:我们 i 从1开始枚举每个石头对所有操作的贡献,我们看哪些操作区间包含 i,并且用一个set把那些包含 i 的操作的时间存起来,我们知道 i 点从0开始加能量,最多需要 k = Ci / Li 的时间使得能量不会超过Ci,我们对这些时间排序,把所有时间间隔存到树状数组里,然后查询时间间隔小于等于k(即[1, k]) 的时间间隔的总和sum1,ans += sum1 * Li,k + 1的时间能量石肯定充满了,那么我们查询[k + 1, Max]看有多少个时间间隔sum2,然后ans += sum2 * Ei 即可,怎么维护这些时间间隔,假设当前我们存了时间 1 8,我们找一下所有操作左端点在 i 的时间 t,假设有个 t = 5,那么我们从树状数组中删除时间间隔 (8 - 7),插入时间间隔(5 - 1),(8 - 5),至于右端点,反过来即可。
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn = 1e5 + 10, N = 2e5;
set<int> st;
vector<int> S[maxn], T[maxn];
#define low(x) x&-x
int c[maxn * 2], E[maxn], L[maxn], C[maxn];
ll val[maxn * 2];
void up(int x, int v) {
    for (int i = x; i <= N; i += low(i))
        c[i] += v, val[i] += x * v;
}
int qu1(int l, int r) {
    int res = 0;
    for (int i = r; i; i -= low(i))
        res += c[i];
    for (int i = l - 1; i; i -= low(i))
        res -= c[i];
    return res;
}
ll qu2(int l, int r) {
    ll res = 0;
    for (int i = r; i; i -= low(i))
        res += val[i];
    for (int i = l - 1; i; i -= low(i))
        res -= val[i];
    return res;
}
int main() {
    int ccsu_cat, Case = 0;
    scanf("%d", &ccsu_cat);
    while (ccsu_cat--) {
        int n, l, r, t, m;
        scanf("%d", &n);
        for (int i = 1; i <= N; i++)
            c[i] = val[i] = 0;
        for (int i = 1; i <= n; i++)
            scanf("%d%d%d", &E[i], &L[i], &C[i]);
        scanf("%d", &m);
        while (m--) {
            scanf("%d%d%d", &t, &l, &r);
            S[l].push_back(t);
            T[r + 1].push_back(t);
        }
        ll ans = 0;
        for (int i = 1; i <= n; i++) {
            for (auto time : S[i]) {
                st.insert(time);
                auto it = st.find(time);
                auto pre = it, nxt = it;
                if (pre != st.begin()) {
                    pre--;
                    up(*it - *pre, 1);
                }
                nxt++;
                if (nxt != st.end())
                    up(*nxt - *it, 1);
                if (it != st.begin() && nxt != st.end())
                    up(*nxt - *pre, -1);
            }
            for (auto time : T[i]) {
                auto it = st.find(time);
                auto pre = it, nxt = it;
                if (pre != st.begin()) {
                    pre--;
                    up(*it - *pre, -1);
                }
                nxt++;
                if (nxt != st.end())
                    up(*nxt - *it, -1);
                if (it != st.begin() && nxt != st.end())
                    up(*nxt - *pre, 1);
                st.erase(time);
            }
            if (st.empty())
                continue;
            if (1ll * (*st.begin()) * L[i] + E[i] <= C[i])
                ans += *st.begin() * L[i] + E[i];
            else
                ans += C[i];
            if (L[i] == 0)
                continue;
            int k = C[i] / L[i];
            ans += 1ll * qu1(k + 1, N) * C[i];
            ans += 1ll * qu2(1, k) * L[i];
        }
        st.clear();
        for (int i = 1; i <= n + 1; i++)
            S[i].resize(0), T[i].resize(0);
        printf("Case #%d: %lld\n", ++Case, ans);
    }
}

H Pair

靠,赛场写完没过样例,就放弃了,赛后发现数位dp多算进了x或着y为0的贡献,补上这个一发切了,我们求出所有的均不满足条件的(x, y)数量,即满足这两个条件:(x & y) <= c && (x^y) >= c,然后用A*B 减去就是答案,设d[i][sta1][sta2][lim1][lim2]为二进制长度为 i,第一,二个数字的限制情况分别为lim1,lim2,sta1 = 0 表示当前的(x & y) = c,1表示(x & y) < c,sta2 = 0表示当前的(x ^ y) = c,1表示(x ^ y) > c,然后进行基础的数位dp转移,设,a[pos],b[pos],c[pos]分别表示三个数二进制第pos位的状态,如果当前的sta1 = 0,且a[i] & b[i] > c[i],肯定不合法,如果a[i] & b[i] < c[i],那么下个状态的sta1就变成了1,如果sta1 = 1,那就肯定没有任何限制直接转移,sta2同理。
#include<bits/stdc++.h>
#define ll long long
using namespace std;
int C, a[30], b[30];
ll d[31][2][2][2][2];
ll dfs(int pos, int sta1, int sta2, int lim1, int lim2) {
    if (pos < 0)
        return 1;
    if (d[pos][sta1][sta2][lim1][lim2] != -1)
        return d[pos][sta1][sta2][lim1][lim2];
    int up1 = lim1? a[pos] : 1;
    int up2 = lim2? b[pos] : 1;
    ll ans = 0;
    for (int i = 0; i <= up1; i++)
    for (int j = 0; j <= up2; j++) {
        int x = 0;
        if (C >> pos & 1)
            x = 1;
        if ((i & j) > x && !sta1)
            continue;
        if ((i ^ j) < x && !sta2)
            continue;
        int t1 = sta1 || ((i & j) < x);
        int t2 = sta2 || ((i ^ j) > x);
        ans += dfs(pos - 1, t1, t2, lim1 && (i == up1), lim2 && (j == up2));
    }
    d[pos][sta1][sta2][lim1][lim2] = ans;
    return ans;
}
int main() {
    int T;
    cin>>T;
    while (T--) {
        int x, y;
        cin>>x>>y>>C;
        ll ans = 1ll * x * y + max(x - C + 1, 0) + max(y - C + 1, 0);
        for (int i = 0; i < 30; i++)
            a[i] = b[i] = 0;
        memset(d, -1, sizeof(d));
        for (int i = 0; x; i++, x /= 2)
            a[i] = x % 2;
        for (int i = 0; y; i++, y /= 2)
            b[i] = y % 2;
        printf("%lld\n", ans - dfs(29, 0, 0, 1, 1));
    }
}

I Chessboard

题意:给你一个N,M,然后你可以任意构造一个 k * k的矩阵,使得矩阵内每个元素最少是M,且任意不同行不同列的 k 个元素总和不超过N且都相同,问有多少种构造方法。
解法:隔板法好题,我们枚举k,我们可以把每个元素减去M,那么就相当于N减去 k * M,简化问题并且不影响答案,然后我们枚举元素总和T(T <= N), 第二个限制条件的实质是我们抽取一些行一些列,使得这些行一整行加一些数,这些列一整列加一些数,使得加的数的总和是T,再转化一下,现在有 k * 2 + T个1摆在一排,我们要给他分成2 * k份,每一份1的个数x减去1对应某行或者某列整行整列加的数,我们都知道隔板法,不知道?那就继续看:每两个1之间有一个隔间,那么k *2 + T有k * 2 - 1 +T个隔间,我们选择k * 2 - 1个隔间就可以把这些1分成k * 2份,那么好像当前的答案就是C(k * 2 - 1 + T,k * 2 - 1),实则算多了,假设2 * 2的矩阵全部是1,那么是不是可以理解为选取两行+1或者选取两列+1?像这种情况,为了避免重复算,如果整个矩阵最小值都x>=1,我们假设只能是所有行都加了x,那么就可以这么去重,我们预先给所有列+1,相当于k + T个1分成2 * k份,那么真正的答案就要减去 C(k - 1 + T,k * 2 - 1)。
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn = 6010, mod = 998244353;
ll p[maxn], inv[maxn];
void add(int &x, int y) {
    x += y;
    if (x >= mod)
        x -= mod;
    if (x < 0)
        x += mod;
}
ll ksm(ll x, int y) {
    ll res = 1;
    while (y) {
        if (y & 1)
            res = res * x % mod;
        x = x * x % mod;
        y /= 2;
    }
    return res;
}
ll C(int n, int m) {
    if (n < m)
        return 0;
    return p[n] * inv[m] % mod * inv[n - m] % mod;
}
int main() {
    int T;
    cin>>T;
    p[0] = inv[0] = 1;
    for (int i = 1; i < maxn; i++) {
        p[i] = p[i - 1] * i % mod;
        inv[i] = ksm(p[i], mod - 2);
    }
    while (T--) {
        int n, m, ans = 0;
        cin>>n>>m;
        for (int k = 1; k <= n; k++) {
            int N = n - m * k;
            if (N < 0)
                break;
            for (int T = 0; T <= N; T++) {
                add(ans, C(2 * k - 1 + T, 2 * k - 1));
                add(ans, -C(k - 1 + T, 2 * k - 1));
            }
        }
        cout<<ans<<'\n';
    }
}
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

长沙橘子猫

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值