2024 国庆做题总结

Secret Santa

思路

这是一个需要深思熟虑的贪心,总之还算有点复杂。

首先,如果一个数不在它自己数值的下标上,就可以填进去,将剩下的还未填的数记录下来,此时情况如下(样例1,第一组):

当前:2 1 _
剩余:3

然后将剩余的数的那个数组反过来,即从大到小排序,填满空位,这样可能会有冲突,但是,可以证明只会有一个冲突。

证明:\(x,y,z\) 为下标,\(a,b,c\) 分别为 \(x,y,z\) 上的数值,假设 \(y = b\),因为 \(x < y < z\)\(a > b > c\),所以其余任何位置都无冲突。

那么如何解决掉冲突呢,将冲突位置上的值替换为之前这个位置上的值即可,再将之前这个位置上的值的现在的位置上的值改为冲突位置的值,这样就不会有冲突。

原来:2 1 3 位置 3 上有冲突
更改后:3 1 2 (第三个位置改为原来的值,第一个位置改为 3)

显然这种方案变换次数最少。

代码

#include<iostream>
#include<vector>
using namespace std;

inline int read(){register int x = 0, f = 1;register char c = getchar();while (c < '0' || c > '9'){if (c == '-') f = -1;c = getchar();}while (c >= '0' && c <= '9'){x = (x << 1) + (x << 3) + (c ^ 48);c = getchar();}return x * f;}
inline void write(int x){if (x < 0) putchar('-'), x = -x;if (x > 9) write(x / 10);putchar(x % 10 + '0');}

const int N = 2e5 + 10;
int T, n, ans;
int a[N], b[N], cnt[N], tot;
vector<int> v[N];
bool vis[N], f[N];

int main(){
    T = read();
    while (T--){
        n = read();
        for (int i = 1; i <= n; i++) a[i] = read(), v[a[i]].push_back(i);
        for (int i = 1; i <= n; i++){
            for (auto j : v[i]){
                if (!vis[j] && i != j){
                    b[j] = i;
                    vis[j] = 1;
                    f[i] = 1;
                    break;
                }
            }
            if (!f[i]) cnt[++tot] = i;
        }
        for (int i = 1, l = tot; i <= n && l >= 1; i++){
            if (!vis[i]) b[i] = cnt[l], l--;
        }
        for (int i = 1; i <= n; i++){
            if (b[i] == i) swap(b[i], b[v[a[i]][0]]);
        }
        for (int i = 1; i <= n; i++){
            if (a[i] == b[i]) ans++;
        }
        cout << ans << '\n';
        for (int i = 1; i <= n; i++) cout << b[i] << ' ';
        cout << '\n';
        for (int i = 1; i <= n; i++) a[i] = b[i] = cnt[i] = vis[i] = f[i] = 0;
        for (int i = 1; i <= n; i++) v[i].clear();
        tot = ans = 0;
    }
    return 0;
}

[ABC137D] Summer Vacation

思路

考虑从后往前做,只剩一天时,只能选取一天之后才能拿到工资的任务,显然选取能拿到工资最多的,那么假如还剩下 \(x\) 天,小于等于 \(x\) 天拿到工资的任务都可以,于是取工资最大值,加入答案,用一个优先队列维护这个最大值即可。

代码

#include<iostream>
#include<queue>
#include<algorithm>

using namespace std;

inline int read(){register int x = 0, f = 1;register char c = getchar();while (c < '0' || c > '9'){if (c == '-') f = -1;c = getchar();}while (c >= '0' && c <= '9'){x = (x << 1) + (x << 3) + (c ^ 48);c = getchar();}return x * f;}
inline void write(int x){if (x < 0) putchar('-'), x = -x;if (x > 9) write(x / 10);putchar(x % 10 + '0');}

const int N = 1e5 + 10;
int n, m, ans;
struct node{
    int a, b;
    bool operator < (const node &b) const{
        return a < b.a;
    }
}x[N];
priority_queue<int> q;

int main(){
    n = read(), m = read();
    for (int i = 1; i <= n; i++) x[i].a = read(), x[i].b = read();
    sort(x + 1, x + n + 1);
    int top = 1;
    for (int i = 1; i <= m; i++){
        while (x[top].a <= i && top <= n){
            q.push(x[top++].b);
        }
        if (!q.empty()) ans += q.top(), q.pop();
    }
    cout << ans;
    return 0;
}

P4377 [USACO18OPEN] Talent Show G

思路

错解:01背包,当前总才艺值时,最小的重量值,求答案。

为什么错误?因为总重量值有一个限制,要大于等于 \(W\),这个限制无法做。

正解:考虑二分答案,现在二分到了一个答案 \(X\),如果有一个方案的答案大于等于 \(X\),即最终答案可以更大,于是可以得到下列式子:

\[\frac{\sum_\limits{i}t_i}{\sum_\limits{i}w_i} \ge X \]

\[\sum_\limits{i}t_i \ge X\sum_\limits{i}w_i \]

\[\sum_\limits{i}t_i - X\times w_i \ge 0 \]

由此可知,选取一定的 \(t_i - X\times w_i\) 满足 \(\sum w_i \ge W\) 大于等于 \(0\),即用 01 背包实现,容量为 \(W\),重量总和大于 \(W\) 的也记入 \(W\),如果 \(dp[W]\) 大于等于 \(0\),则答案还能更大,否则答案小于 \(X\)

代码

#include<iostream>
#include<climits>
#define INF INT_MIN
#define int long long

using namespace std;

inline int read(){register int x = 0, f = 1;register char c = getchar();while (c < '0' || c > '9'){if (c == '-') f = -1;c = getchar();}while (c >= '0' && c <= '9'){x = (x << 1) + (x << 3) + (c ^ 48);c = getchar();}return x * f;}
inline void write(int x){if (x < 0) putchar('-'), x = -x;if (x > 9) write(x / 10);putchar(x % 10 + '0');}

const int N = 255, M = 1e3 + 10;
const double eps = 10e-10;
int n, W;
double ans;
int w[N], t[N], sum;
double p[N], dp[M];
bool check(double x){
    for (int i = 1; i <= W; i++) dp[i] = -INF;
    for (int i = 1; i <= n; i++) p[i] = (double)t[i] - x * w[i];
    for (int i = 1; i <= n; i++)
        for (int j = W + w[i]; j >= w[i]; j--)
            dp[min(j, W)] = max(dp[min(j, W)], dp[j - w[i]] + p[i]);
    return dp[W] >= 0;
}

signed main(){
    n = read(), W = read();
    for (int i = 1; i <= n; i++) w[i] = read(), t[i] = read(), sum += w[i];
    double l = 0, r = 1000000;
    while (l + eps <= r){
        double mid = (l + r) / 2;
        if (check(mid)){
            l = mid;
        }else{
            r = mid;
        }
    }
    cout << (int)(l * 1000);
    return 0;
}

[ARC110D] Binomial Coefficient is Fun

思路

题目 \(\prod \limits_{i = 1}^{N}\binom{b_i}{a_i}\) 可以转化为 \(\prod \limits_{i = 1}^{N}\binom{a_i + c_i}{a_i}\),即在 \(a_i\) 个数中插入 \(c_i\) 个数,于是,每个 \(a_i\)\(a_i + 1\) 个空可以插入,即将 \(M - \sum a_i\) 个数插入 \(\sum a_i + N + 1\) 个空中,最后加一保证了可以有剩余,即所有数加起来可以小于 \(M\),最后利用插板法公式,得:

\[\binom{M - \sum a_i + \sum a_i + N +1 - 1}{\sum a_i + N + 1 - 1} \]

\[\binom{M + N}{\sum a_i + N} \]

代码

#include<iostream>
#define int long long

using namespace std;

inline int read(){register int x = 0, f = 1;register char c = getchar();while (c < '0' || c > '9'){if (c == '-') f = -1;c = getchar();}while (c >= '0' && c <= '9'){x = (x << 1) + (x << 3) + (c ^ 48);c = getchar();}return x * f;}
inline void write(int x){if (x < 0) putchar('-'), x = -x;if (x > 9) write(x / 10);putchar(x % 10 + '0');}

const int N = 2e3 + 10, mod = 1e9 + 7;
int n, m, sum;
int a[N];
int qpow(int a, int b){
    int ans = 1;
    while (b){
        if (b & 1) ans = ans * a % mod;
        a = a * a % mod;
        b >>= 1;
    }
    return ans;
}
int C(int m, int n){
    int ans = 1;
    for (int i = n; i >= n - m + 1; i--) ans = ans * i % mod;
    for (int i = 1; i <= m; i++) ans = ans * qpow(i, mod - 2) % mod;
    return ans;
}

signed main(){
    n = read(), m = read();
    for (int i = 1; i <= n; i++) a[i] = read(), sum += a[i];
    cout << C(sum + n, m + n);
    return 0;
}

[AGC001E] BBQ Hard

思路

是个很妙的题目,直接暴力求必须要 \(O(n^2)\) 的时间,且是不可优化的,而正解直接把组合转化成了 \(dp\)

首先有一个结论,\(\binom{a+b}{a}\) 等于在平面直角坐标系上,从 \((0,0)\) 只能向上或向右走到 \((a,b)\) 的路径数,简单理解就是走 \(x+y\) 步能到 \((a,b)\),选出 \(x\) 步往上走,即为路径数。

所以设 \(dp_{i,j}\) 为从 \((0,0)\) 走到 \((i,j)\) 的方案数,转移即为 \(dp_{i,j} = dp_{i, j} + dp_{i - 1, j} + dp_{j, i - 1}\)

但是,统计答案还是需要 \(O(n^2)\) 的时间统计,这时又有一个技巧,将要求的两个点平移,即 \((0,0)\)\((a_i + a_j + b_i + b_j)\) 转为 \((-a_i, -b_i)\)\((a_j, b_j)\)

初始时将每个 \(dp_{-a_i}{-b_i}\)\(1\),然后 \(dp\),最后扫一遍将每个 \(dp_{a_i, b_i}\) 加入答案,题目中 \(i \not = j\),那么就用答案减去题目给出的组合 \(\binom{2\times a_i + 2\times b_i}{2\times a_i}\),由于 \(i \lt j\), 最后答案除以 \(2\)即可。

代码

#include<iostream>
#define int long long

using namespace std;

inline int read(){register int x = 0, f = 1;register char c = getchar();while (c < '0' || c > '9'){if (c == '-') f = -1;c = getchar();}while (c >= '0' && c <= '9'){x = (x << 1) + (x << 3) + (c ^ 48);c = getchar();}return x * f;}
inline void write(int x){if (x < 0) putchar('-'), x = -x;if (x > 9) write(x / 10);putchar(x % 10 + '0');}

const int N = 2e5 + 10, M = 4e3 + 20, mod = 1e9 + 7;
int n, sum;
int a[N], b[N], dp[M + 10][M + 10], v[M << 1 + 10], inv[M << 1 + 10];
int qpow(int a, int b){
    int ans = 1;
    while (b){
        if (b & 1) ans = ans * a % mod;
        a = a * a % mod;
        b >>= 1;
    }
    return ans;
}
void init(){
    v[0] = inv[0] = 1;
    for (int i = 1; i <= (M << 1); i++){
        v[i] = v[i - 1] * i % mod;
        inv[i] = qpow(v[i], mod - 2);
    }
}
int C(int m, int n){
    return v[n] * inv[n - m] % mod * inv[m] % mod;
}

signed main(){
    init();
    n = read();
    for (int i = 1; i <= n; i++) a[i] = read(), b[i] = read(), dp[M / 2 - a[i]][M / 2 - b[i]]++;
    for (int i = 1; i <= M; i++)
        for (int j = 1; j <= M; j++) dp[i][j] = (dp[i][j] + (dp[i - 1][j] + dp[i][j - 1]) % mod) % mod;
    for (int i = 1; i <= n; i++) sum = (sum + dp[M / 2 + a[i]][M / 2 + b[i]]) % mod;
    for (int i = 1; i <= n; i++) sum = (sum - C(2 * a[i], 2 * a[i] + 2 * b[i]) + mod) % mod;
    cout << sum * qpow(2, mod - 2) % mod;
    return 0;
}

P7044 「MCOI-03」括号

思路

对于一个位置为 \(i\) 左括号而言,与它匹配最近的一个右括号位置为 \(j\),那么它对 \(0\) 维的贡献就为 \(l\in [1,i],r\in [i, j - 1]\) 组成的区间,并且只会贡献 \(1\),考虑多维,就是后面的所有区间要包含第一个 \(0\) 维区间,即有 \(K\) 个嵌套的区间,左端点都满足 \(l\in [1,i]\),右端点满足第一个区间 \(r\in [i, j - 1]\),其余的所有右端点都满足 \(r\in [i, n]\),求这种嵌套区间的个数,左端点可以用插板法去做,\(K\) 个物品,要放入 \(i\) 个箱子,方案数为 \(\binom{K + i - 1}{i - 1}\),右端点有点不好做,考虑容斥,用所有区间右端点在 \([i, n]\) 的方案数,减去第一个右端点就在 \([j, n]\) 的方案数,就为右端点的方案数,即 \(\binom{K + n - i}{n - i} - \binom{K + n - j}{n - j}\),根据乘法原理,两个部分相乘就为左括号的答案,右括号同理。

\[\binom{K + i - 1}{i - 1} (\binom{K + n - i}{n - i} - \binom{K + n - j}{n - j}) \]

代码

#include<iostream>
#define int long long

using namespace std;

inline int read(){register int x = 0, f = 1;register char c = getchar();while (c < '0' || c > '9'){if (c == '-') f = -1;c = getchar();}while (c >= '0' && c <= '9'){x = (x << 1) + (x << 3) + (c ^ 48);c = getchar();}return x * f;}
inline void write(int x){if (x < 0) putchar('-'), x = -x;if (x > 9) write(x / 10);putchar(x % 10 + '0');}

const int N = 1e6 + 10, M = 4e6 + 10, mod = 998244353;
int n, k, ans;
char c[N];
int x[N], mul[M], inv[M];
int stk[N], top;
int qpow(int a, int b){
    int ans = 1;
    while (b){
        if (b & 1) ans = ans * a % mod;
        a = a * a % mod;
        b >>= 1;
    }
    return ans;
}
void init(){
    mul[0] = inv[0] = 1;
    for (int i = 1; i <= M - 10; i++) mul[i] = mul[i - 1] * i % mod, inv[i] = qpow(mul[i], mod - 2);
}
int C(int m, int n){
    if (n < 0 || m > n) return 0;
    return mul[n] * inv[n - m] % mod * inv[m] % mod;
}

signed main(){
    init();
    n = read(), k = read();
    cin >> c + 1;
    for (int i = 1; i <= n; i++){
        if (c[i] == '(') stk[++top] = i;
        else{
            if (!top) x[i] = 0;
            else x[i] = stk[top], x[stk[top--]] = i;
        }
    }
    while (top) x[stk[top--]] = n + 1;
    for (int i = 1; i <= n; i++){ 
        if (c[i] == '(') ans = (ans + C(i - 1, k + i - 1) * ((C(n - i, k + n - i) - C(n - x[i], k + n - x[i]) + mod) % mod) % mod) % mod;
        else ans = (ans + C(n - i, k + n - i) * ((C(i - 1, k + i - 1) - C(x[i] - 1, k + x[i] - 1) + mod) % mod) % mod) % mod;
    }
    cout << ans;
    return 0;
}

Anton and School - 2

思路

对于一个空处,左边有 \(a\) 个左括号,右边有 \(b\) 个右括号,为了不重不漏的计算,所以第一个左括号必选,答案就为 \(\sum \limits_{i=0}^{\min(a-1, b-1)}\binom{a - 1}{i}\binom{b}{i + 1}\),利用范德蒙德卷积式子转换为 \(\sum \limits_{i=0}^{\min(a-1, b-1)}\binom{a - 1}{a - 1 - i}\binom{b}{i + 1} = \binom{a + b - 1}{a}\),组合意义:要从大小为 \(a + b - 1\) 的集合选出 \(a\) 个数,就等同于将大小为 \(a + b - 1\) 的集合分成大小为 \(a - 1\)\(b\) 的集合,分别选 \(a - 1 - i\)\(i + 1\) 个数,所以得证。

于是,预处理一个空位左边左括号个数和右括号个数,再用公式统计即可。

代码

#include<iostream>
#include<cstring>
#define int long long

using namespace std;

inline int read(){register int x = 0, f = 1;register char c = getchar();while (c < '0' || c > '9'){if (c == '-') f = -1;c = getchar();}while (c >= '0' && c <= '9'){x = (x << 1) + (x << 3) + (c ^ 48);c = getchar();}return x * f;}
inline void write(int x){if (x < 0) putchar('-'), x = -x;if (x > 9) write(x / 10);putchar(x % 10 + '0');}

const int N = 2e5 + 10, M = 4e5 + 10, mod = 1e9 + 7;
int n, ans;
char c[N];
int pre[N], suf[N], mul[M << 1], inv[M << 1];
int qpow(int a, int b){
    int ans = 1;
    while (b){
        if (b & 1) ans = ans * a % mod;
        a = a * a % mod;
        b >>= 1;
    }
    return ans;
}
void init(){
    mul[0] = inv[0] = 1;
    for (int i = 1; i <= (n << 1); i++) mul[i] = mul[i - 1] * i % mod, inv[i] = qpow(mul[i], mod - 2);
}
int C(int m, int n){
    return mul[n] * inv[n - m] % mod * inv[m] % mod;
}

signed main(){
    cin >> c + 1;
    n = strlen(c + 1);
    init();
    for (int i = 1; i <= n; i++){
        pre[i] = pre[i - 1];
        if (c[i] == '(') pre[i] = pre[i - 1] + 1;
    }
    for (int i = n; i >= 1; i--){
        suf[i] = suf[i + 1];
        if (c[i] == ')') suf[i] = suf[i + 1] + 1;
    }
    for (int i = 1; i <= n; i++){
        if (c[i] == '('){
            ans = (ans + C(pre[i], pre[i] + suf[i] - 1)) % mod;
        }
    }
    cout << ans;
    return 0;
}

P9118 [春季测试 2023] 幂次

思路

\(k = 1\) 时,答案就为 \(n\)
\(k \ge 3\) 时,底数不会超过 \(10^6\),可以直接暴力枚举底数和指数,开一个 \(map\) 判重即可。
\(k = 2\) 时,不能用原来的方法,不然时间会爆炸,所以需要技巧。在 \(n\) 中,完全平方数的个数为 \(\lfloor \sqrt{n} \rfloor\) 个,先加入答案统计,然后再用上一种情况的方法统计,但是要判掉 \(k^2\) 的情况,我们发现,完全平方数进行因式分解后,每个因数的个数都是偶数个,利用这个性质,在枚举的时候,直接将这种数打上标记即可。

代码

#include<iostream>
#include<cmath>
#include<map>
#define int long long

using namespace std;

inline int read(){register int x = 0, f = 1;register char c = getchar();while (c < '0' || c > '9'){if (c == '-') f = -1;c = getchar();}while (c >= '0' && c <= '9'){x = (x << 1) + (x << 3) + (c ^ 48);c = getchar();}return x * f;}
inline void write(int x){if (x < 0) putchar('-'), x = -x;if (x > 9) write(x / 10);putchar(x % 10 + '0');}

int n, k, ans;
map<int, bool> vis;

signed main(){
    n = read(), k = read();
    if (k == 1){
        cout << n;
    }else if (k == 2){
        ans += sqrtl(n);
        for (int p = 2; p * p * p <= n; p++){
            for (int i = 2, sum = p * p; ; i++){
                if (i >= k && i % 2 != 0 && !vis[sum]) vis[sum] = 1, ans++;
                if (i % 2 == 0) vis[sum] = 1;
                if (sum <= n / p) sum *= p;
                else break;
            }
        }
        cout << ans;
    }else{
        for (int p = 2; p * p * p <= n; p++){
            for (int i = 2, sum = p * p; ; i++){
                if (i >= k && !vis[sum]) vis[sum] = 1, ans++;
                if (sum <= n / p) sum *= p;
                else break;
            }
        }
        cout << ans + 1;
    }
    return 0;
}

P9869 [NOIP2023] 三值逻辑

思路

\(T,F,U\) 分别为 \(n+1,n+2,n+3\) 号节点,每个节点都有最初赋值的那个节点,设 \(i\) 最初的节点为 \(a_i\)\(b_i\) 表示反转,由 \(0/1\) 表示,那么每个 \(i\) 仅有一个 \(a_i\) 对应,于是从 \(a_i\)\(i\) 连边,就是一个基环树森林和一堆树,如果是基环树就判断环是否满足条件,如果满足条件,则基环树上的各个树肯定能满足条件,否则,整个基环树都是 \(U\),如果是树,肯定是顶点出现了自环,所以只需判断自环是否满足条件即可,结果同基环树。

代码

#include<iostream>
#include<vector>

using namespace std;

inline int read(){register int x = 0, f = 1;register char c = getchar();while (c < '0' || c > '9'){if (c == '-') f = -1;c = getchar();}while (c >= '0' && c <= '9'){x = (x << 1) + (x << 3) + (c ^ 48);c = getchar();}return x * f;}
inline void write(int x){if (x < 0) putchar('-'), x = -x;if (x > 9) write(x / 10);putchar(x % 10 + '0');}

const int N = 1e5 + 10;
int id, T, n, m, ans;
int a[N];
bool b[N], vis[N], l[N];
vector<int> e[N];
void get_ans(int u){
    if (vis[u]) return;
    ans++;
    vis[u] = 1;
    for (auto v : e[u]) get_ans(v);
}
void modify(int u){
    if (vis[u]) return;
    vis[u] = 1;
    for (auto v : e[u]) modify(v);
}

int main(){
    id = read(), T = read();
    while (T--){
        n = read(), m = read();
        for (int i = 1; i <= n + 3; i++) a[i] = i, b[i] = 0, vis[i] = 0, l[i] = 0;
        for (int i = 1; i <= n + 3; i++) e[i].clear();
        ans = 0;
        for (int i = 1; i <= m; i++){
            char c; cin >> c;
            if (c == '+'){
                int x = read(), y = read();
                a[x] = a[y];
                b[x] = b[y];
            }else if (c == '-'){
                int x = read(), y = read();
                a[x] = a[y];
                b[x] = b[y] ^ 1;
            }else if (c == 'T'){
                int x = read();
                a[x] = a[n + 1];
                b[x] = b[n + 1];
            }else if (c == 'F'){
                int x = read();
                a[x] = a[n + 2];
                b[x] = b[n + 2];
            }else{
                int x = read();
                a[x] = a[n + 3];
                b[x] = b[n + 3];
            }
        }
        for (int i = 1; i <= n; i++) e[a[i]].emplace_back(i);
        for (int i = 1; i <= n; i++) if (a[i] == i && b[i]) get_ans(i);
        get_ans(n + 3);
        ans--;
        for (int i = 1; i <= n; i++){
            if (vis[i]) continue;
            int u = a[i];
            l[i] = 1;
            while (!l[u]){
                l[u] = 1;
                u = a[u];
            }
            int p = a[u], sum = b[u];
            while (p != u){
                sum += b[p];
                p = a[p];
            }
            if (sum & 1) get_ans(u);
            else modify(u);
        }
        cout << ans << '\n';
    }
    return 0;
}
posted @ 2024-09-28 21:42  bryce_yyds  阅读(18)  评论(0)    收藏  举报