此时不搏何|

bryce_yyds

园龄:2年6个月粉丝:7关注:12

2024-09-28 21:42阅读: 12评论: 0推荐: 0

2024 国庆做题总结

Secret Santa

思路

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

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

当前:2 1 _
剩余:3

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

证明:x,y,z 为下标,a,b,c 分别为 x,y,z 上的数值,假设 y=b,因为 x<y<za>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,即最终答案可以更大,于是可以得到下列式子:

itiiwiX

itiXiwi

itiX×wi0

由此可知,选取一定的 tiX×wi 满足 wiW 大于等于 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

思路

题目 i=1N(biai) 可以转化为 i=1N(ai+ciai),即在 ai 个数中插入 ci 个数,于是,每个 aiai+1 个空可以插入,即将 Mai 个数插入 ai+N+1 个空中,最后加一保证了可以有剩余,即所有数加起来可以小于 M,最后利用插板法公式,得:

(Mai+ai+N+11ai+N+11)

(M+Nai+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(n2) 的时间,且是不可优化的,而正解直接把组合转化成了 dp

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

所以设 dpi,j 为从 (0,0) 走到 (i,j) 的方案数,转移即为 dpi,j=dpi,j+dpi1,j+dpj,i1

但是,统计答案还是需要 O(n2) 的时间统计,这时又有一个技巧,将要求的两个点平移,即 (00)(ai+aj+bi+bj) 转为 (ai,bi)(aj,bj)

初始时将每个 dpaibi1,然后 dp,最后扫一遍将每个 dpai,bi 加入答案,题目中 ij,那么就用答案减去题目给出的组合 (2×ai+2×bi2×ai),由于 i<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[1,i],r[i,j1] 组成的区间,并且只会贡献 1,考虑多维,就是后面的所有区间要包含第一个 0 维区间,即有 K 个嵌套的区间,左端点都满足 l[1,i],右端点满足第一个区间 r[i,j1],其余的所有右端点都满足 r[i,n],求这种嵌套区间的个数,左端点可以用插板法去做,K 个物品,要放入 i 个箱子,方案数为 (K+i1i1),右端点有点不好做,考虑容斥,用所有区间右端点在 [i,n] 的方案数,减去第一个右端点就在 [j,n] 的方案数,就为右端点的方案数,即 (K+nini)(K+njnj),根据乘法原理,两个部分相乘就为左括号的答案,右括号同理。

(K+i1i1)((K+nini)(K+njnj))

代码

#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 个右括号,为了不重不漏的计算,所以第一个左括号必选,答案就为 i=0min(a1,b1)(a1i)(bi+1),利用范德蒙德卷积式子转换为 i=0min(a1,b1)(a1a1i)(bi+1)=(a+b1a),组合意义:要从大小为 a+b1 的集合选出 a 个数,就等同于将大小为 a+b1 的集合分成大小为 a1b 的集合,分别选 a1ii+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
k3 时,底数不会超过 106,可以直接暴力枚举底数和指数,开一个 map 判重即可。
k=2 时,不能用原来的方法,不然时间会爆炸,所以需要技巧。在 n 中,完全平方数的个数为 n 个,先加入答案统计,然后再用上一种情况的方法统计,但是要判掉 k2 的情况,我们发现,完全平方数进行因式分解后,每个因数的个数都是偶数个,利用这个性质,在枚举的时候,直接将这种数打上标记即可。

代码

#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 最初的节点为 aibi 表示反转,由 0/1 表示,那么每个 i 仅有一个 ai 对应,于是从 aii 连边,就是一个基环树森林和一堆树,如果是基环树就判断环是否满足条件,如果满足条件,则基环树上的各个树肯定能满足条件,否则,整个基环树都是 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;
}

本文作者:bryce_yyds

本文链接:https://www.cnblogs.com/bryceyyds/p/18438463

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   bryce_yyds  阅读(12)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 25岁的心里话
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
收起