板刷1600

1928C - Physical Education Lesson

讨论一下 n n n 出现在队列中的位置是上坡还是下坡,枚举所有 k k k 然后判断一下第 n n n 为是否为 x x x 即可

void solve(int cs) {
    int n, x;
    cin >> n >> x;
    set<int> s;
    auto jg = [&](int k) {
        if (k & 1 || k == 0) return;
        s.insert((k + 2) / 2);
    };
    for (int i = 1; i * i <= n - x; i++) {
        if ((n - x) % i == 0) {
            jg(i);
            jg((n - x) / i);
        }
    }
    for (int i = 1; i * i <= n + x - 2; i++) {
        if ((n + x - 2) % i == 0) {
            jg(i);
            jg((n + x - 2) / i);
        }
    }
    int ans = 0;
    for (auto k : s) {
        int r = (n % (2 * k - 2) ? n % (2 * k - 2) : 2 * k - 2);
        int t = (r <= k ? r : 2 * k - 2 - r + 2);
        ans += (t == x);
    }
    cout << ans << endl;
}   

1921E - Eat the Chip

两个棋子相向而行,必有一刻会到达到一行,而一个棋子想要吃掉另一个棋子,就要晚一步到达第二行。

于是我们可以发现,在A先手的情况下,如果他们两个直接相隔行数为奇数时,那么B会比A晚一步到达同一行,从而B有机会吃掉A,反之则A有机会吃掉B。

我们已经确定了谁吃谁,现在来讨论一下能否吃掉:

如果A吃B,那么A就要往B的方向移动,而B也要远离A,B吃A同理,如果吃方把被吃方逼到墙后追上他,就可以把被吃方吃掉,于是我们可以推出: A 和 B 相隔行数除以 2 ≥ 吃方与墙之间的列数 − 1 A和B相隔行数除以2\geq吃方与墙之间的列数-1 AB相隔行数除以2吃方与墙之间的列数1 时,吃方就能吃掉被吃方。

最后特判一下吃方能在路上就吃掉被吃方的情况即可

void solve(int cs) {
    int n = input(), m = input(), xa = input(), ya = input(), xb = input(), yb = input();
    if (xa >= xb) {cout << "Draw\n"; return;}
    int d = xb - xa - 1;
    if (d & 1) {    //h差奇数B追A
        if (abs(ya - yb) == 0) {
            cout << "Bob\n";
            return;
        }
        if (yb > ya) {
            if (d / 2 >= yb - 2) {
                cout << "Bob\n";
            }
            else {
                cout << "Draw\n";
            }
        }
        else {
            if (d / 2 >= m - yb - 1) {
                cout << "Bob\n";
            }
            else {
                cout << "Draw\n";
            }
        }
    }
    else {      //h差偶数A追B
        if (abs(ya - yb) <= 1) {
            cout << "Alice\n";
            return;
        }
        if (yb > ya) {
            if (d / 2 >= m - ya - 1) {
                cout << "Alice\n";
            }
            else {
                cout << "Draw\n";
            }
        }
        else {
            if (d / 2 >= ya - 2) {
                cout << "Alice\n";
            }
            else {
                cout << "Draw\n";
            }
        }
    }
}   

1920C - Partitioning the Array

两个不同的数模他们差的绝对值得到的结果相等

故我们枚举所有 k k k ,求取 m = gcd ⁡ ( ∣ a i − a i + k ∣ ) m=\gcd(|a_i-a_{i+k}|) m=gcd(aiai+k) m m m 满足要求则ans++

void solve(int cs) {
    int n = input();
    VI a(n);
    for (int i = 0; i < n; i++) {
        a[i] = input();
    }
    int ans = 0;
    for (int k = 1; k <= n; k++) {
        if (n % k == 0) {
            int m = 0;
            for (int j = k; j < n; j++) {
                m = __gcd(m, abs(a[j] - a[j - k]));
            }
            ans += (m >= 2 || m == 0);//m得0时所有数相等
        }
    }
    cout << ans << endl;
}   

1907E - Good Triples

分解方法必然为将每位单独分解,所以用隔板法预处理1~9所有数字分解成三个数字的情况数,然后将各个位上的情况数累乘起来

int ans[10];

void ini() {
    ans[0] = 1;
    for (int i = 1; i < 10; i++) {
        ans[i] = (i + 2) * (i + 1) / 2;
    }
}

void solve(int cs) {
    string s;
    cin >> s;
    int a = 1;
    for (auto c : s) {
        a *= ans[c - '0'];
    }
    cout << a << endl;
}   

1899F - Alex’s whims

首先构造一条链,我们拿好 n n n 号点,距离是多少我们就把他安到哪个点上

void solve(int cs) {
    int n = in(), q = in();
    for (int i = 2; i <= n; i++) {
        cout << i - 1 << ' ' << i << endl;
    }
    int now = n - 1;
    pii lk = make_pair(n, n - 1);
    while(q--) {
        int d = in();
        cout << lk.first << ' ' << lk.second << ' ' << d << endl;
        lk.second = d;
        now = d;
    }
} 

1886C - Decreasing String

前缀和比较一下确定删几个字符,并且更新一下pos,然后单调栈处理一下就好

void solve(int cs) {
    string s;
    cin >> s;
    int pos = in(), n = s.size();
    VI d(n + 1);
    d[0] = n;
    for (int i = 1; i <= n; i++) {
        d[i] = d[i - 1] + n - i;
    }
    int num = 0;
    for (int i = 0; i <= n; i++) {
        if (pos <= d[0]) break;
        if (pos > d[i] && pos <= d[i + 1]) {
            num = i + 1;
            pos -= d[i];
            break;
        }
    }
    //删num个 位置更新为删num个后字符串的pos
    deque<char> st;
    int cnt = 0;
    for(auto c : s) {
        if (st.empty()) {st.push_back(c); continue;}
        if (st.back() <= c) {st.push_back(c); continue;}
        while (st.size() && cnt < num && st.back() > c) {
            st.pop_back();
            cnt++;
        }
        st.push_back(c);
    }
    cnt = 1;
    for (auto c : st) {
        if (cnt == pos) {
            cout << c;
            return;
        }
        cnt++;
    }
}   

1878D - Reverse Madness

我们通过观察可以发现:对于每一个 x x x 的翻转都是在他所对应的区间里,以 l i l_i li r i r_i ri 的中点为轴, x x x 为边界进行翻转,所以对于每一个 x x x,我们二分找到他所对应的区间,然后对翻转区间的端点进行标记,最后根据标记进行翻转即可

void solve(int cs) {
    int n = in(), k = in();
    string s;
    cin >> s;
    VI l(k);
    VI r(k);
    for (int i = 0; i < k; i++) cin >> l[i];
    for (int i = 0; i < k; i++) cin >> r[i];
    int q = in();
    VI re(n + 5);
    while (q--) {
        int x = in();
        int pos = upper_bound(l.begin(), l.end(), x) - l.begin();
        pos--;
        int ll = l[pos], rr = r[pos];
        re[min(x, ll + rr - x)]++;
        re[max(x, ll + rr - x) + 1]--;
    }
    int cnt = 0;
    for (int i = 0; i < k; i++) {
        for (int j = l[i]; j <= r[i]; j++) {
            cnt += re[j];
            if (cnt & 1 && j <= (l[i] + r[i]) / 2) swap(s[j - 1], s[l[i] + r[i] - j - 1]);
        }
    }
    cout << s << endl;
}   

1875D - Jellyfish and Mex

我们首先可以得出,如果想删一个数就需要一直删他直到把他删完才是最优的,其次我们可以发现,在删数的过程中mex的值是单调不增的,于是我们可以先算出删数前的mex,然后往回dp即可。

我们设 d p i dp_i dpi 为将mex变成 i i i 需要的最小花费, c n t i cnt_i cnti 记录原数组中 i i i 的数量,对于 j < i j < i j<i 可以得出状态转移方程 d p j = m i n ( d p j ,   d p i + ( c n t j − 1 ) ∗ i + j ) dp_j = min(dp_j,\ dp_i+(cnt_j - 1)*i+j) dpj=min(dpj, dpi+(cntj1)i+j)

最后输出 d p 0 dp_0 dp0 即可。

void solve(int cs) {
    int n = in();
    VI a(n);
    VI cnt(n + 5);
    VI dp(n + 5, inf);
    for (int i = 0; i < n; i++) {
        a[i] = in();
        if (a[i] <= n) cnt[a[i]]++;
    }
    int mex = 0;
    for (int i = 0; i <= n; i++) {
        if (cnt[i] == 0) {mex = i; break;}
    }
    dp[mex] = 0;
    for (int i = mex; i >= 0; i--) {
        for (int j = i - 1; j >= 0; j--) {
            dp[j] = min(dp[j], dp[i] + (cnt[j] - 1) * i + j);
        }
    }
    print(dp[0]);
}   

1856C - To Become Max

由于最后一个数的值无法改变,所以我们可以确定所有数在无限操作下可到达的最大值,然后对于每个数二分出 k k k 次操作能到达的最大值,取max即可

void solve(int cs) {
    int n = in(), k = in();
    int ans = 0;
    VI a(n);
    for (int i = 0; i < n; i++) {cin >> a[i]; ans = max(ans, a[i]);}
    VI mx(n);
    mx[n - 1] = a[n - 1];
    for (int i = n - 2; i >= 0; i--) mx[i] = max(a[i], mx[i + 1] + 1);

    auto ok = [&](int val, int id) {
        int t = k;
        while(id < n) {
            if (a[id] >= val) break;
            t -= (val - a[id]);
            val--;
            id++;
        }
        return t >= 0; 
    };

    for (int i = 0; i < n; i++) {
        int l = a[i], r = mx[i];
        while (l < r) {
            int mid = (l + r + 1) >> 1;
            if (ok(mid, i)) {
                l = mid;
            }
            else {
                r = mid - 1;
            }
        }
        ans = max(ans, l);
    } 
    cout << ans << endl;
}   

1849C - Binary String Copying

我们通过观察可以发现,对于一个区间 [ l ,   r ] [l,\ r] [l, r],我们只需要找到他最左面的 1 1 1 的位置 l ′ l' l 和最右面的 0 0 0 的位置 r ’ r’ r,我们就可以把这个区间等价为 [ l ′ ,   r ′ ] [l',\ r'] [l, r] ,我们用set 存一下等价后的区间,记一下是否有输入的区间已经有序,最后输出种类即可

void solve(int cs) {
    int n = in(), op = in();
    string s;
    cin >> s;
    s = " " + s;
    set<pii > ans;
    VI z(n + 1), o(n + 1);
    int cnt = 0;
    for (int i = n; i > 0; i--) {
        if (s[i] == '0') z[i] = (++cnt);
        else cnt = 0;
    }
    cnt = 0;
    for (int i = 0; i <= n; i++) {
        if (s[i] == '1') o[i] = (++cnt);
        else cnt = 0;
    }
    int f = 0;
    while (op--) {
        int l = in(), r = in();
        l += z[l];
        r -= o[r];
        if (l >= r) f = 1;
        else ans.insert(make_pair(l, r));
    } 
    cout << ans.size() + f << endl;
}   

1941E - Rudolf and k Bridges

每座桥之间的关系是独立的,故我们可以单独处理每座桥的最小造价,然后用前缀和计算一下连续 k k k 个桥的最小造假即可

于是我们可以把问题转换成求单个桥的最小造假,我们用 d p i dp_i dpi 表示在第 i i i 个点上建支架,且小于 i i i 的点全部满足条件时的最小花费,我们用单调队列来储存 i i i 点前的 d d d 范围内的最小 d p dp dp 值的下标,状态转移方程为 d p i = d p q . f r o n t ( ) + a i + 1 dp_i = dp_{q.front()} + a_i+1 dpi=dpq.front()+ai+1

void solve(int cs) {
    int n, m, k, d;
    cin >> n >> m >> k >> d;
    VI sum(n + 1);
    for (int _ = 1; _ <= n; _++) {
        VI a(m);
        for (int i = 0; i < m; i++) cin >> a[i];
        VI dp(m);
        deque<int> q;
        q.pb(0);
        for (int i = 0; i < m; i++) {
            while(!q.empty() && i - q.front() - 1 > d) q.pop_front();
            dp[i] = dp[q.front()] + a[i] + 1;
            while(!q.empty() && dp[i] <= dp[q.back()]) q.pop_back();
            q.push_back(i);
        }
        sum[_] = sum[_ - 1] + dp[m - 1];
    }
    int ans = llinf;
    for (int i = k; i <= n; i++) {
        ans = min(ans, sum[i] - sum[i - k]);
    }
    cout << ans << endl;
}   
  • 7
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值