AtCoder Beginner Contest 262 A-F

AtCoder Beginner Contest 262 A-F

https://atcoder.jp/contests/abc262
我太懒了,现在才发

A - World Cup

题意

给定y,找到>=y的离y最近的模4等于2的数

分析

如题

Code

#include <bits/stdc++.h>

using namespace std;

int main () {
    int x;
    cin >> x;
    int mod = x % 4;
    if (mod == 0)   x += 2;
    else if (mod == 1)  x ++;
    else if (mod == 3)  x += 3;
    cout << x;
    
}

B - Triangle (Easier)

题意

给定无向图,找有多少对点a,b,c,满足:
a和b之间有边,a和c之间有边,c和b之间有边

分析

n很小,可以直接三重循环枚举

Code

#include <bits/stdc++.h>

using namespace std;
const int N = 105;
bool f[N][N];

int main () {
    int n, m, ans = 0;
    cin >> n >> m;
    while (m --) {
        int x, y;
        cin >> x >> y;
        f[x][y] = f[y][x] = true;
    }
    for (int i = 1; i <= n; i ++)
        for (int j = i + 1; j <= n; j ++)
            for (int k = j + 1; k <= n; k ++)
                if (f[i][j] && f[j][k] && f[k][i])  ans ++;
                
    cout << ans << endl;
}

C - Min Max Pair

题意

给定长度为n的序列a,问有多少对i,j满足:

分析

要满足上述情况,要么:

  1. \(a_i=i,a_j=j\)
  2. \(a_i=j,a_j=i\)
    对于情况2,找出所有满足的点对即可
    对于情况1,统计\(a_i=i\)的数量x,然后\(\frac{x*(x-1)}{2}\)计入贡献

Code

#include <bits/stdc++.h>
#define int long long

using namespace std;
const int N = 5e5 + 5;
int a[N];
bool vis[N];

signed main () {
    int n;
    cin >> n;
    for (int i = 1; i <= n; i ++)   cin >> a[i];

    int pure = 0, ans  = 0;
    for (int i = 1; i <= n; i ++) {
        if (vis[i]) continue;
        if (a[i] == i)  pure ++;
        else {
            if (a[a[i]] == i) {
                ans ++;
                vis[a[i]] = true;
            }   
        }
    }
    ans += (pure-1)*pure/2;
    cout << ans;
}

//没开ll,卧槽

D - I Hate Non-integer Number

题意

长度为n的序列a中选1-n个数,问这些数之和除以所选数量为整数的组合有多少种

分析

组合有很多可能,且存在"可转移"的特点,考虑dp
dp[j][k][l]: 分母为i, 前j个数中选了k个数,之和的余数为l的方案数
转移:

  1. 不选:dp[j+1][k][l] += dp[j][k][l];
  2. 选:dp[j+1][k+1][(l+a[j])%i] += dp[j][k][l]; (j < i)

Code

#include <bits/stdc++.h>
#define int long long

using namespace std;
const int N = 105, mod = 998244353;
int a[N], f[N][N][N], n, ans;

signed main () {
    cin >> n;
    for (int i = 0; i < n; i ++)   cin >> a[i];
    
    for (int i = 1; i <= n; i ++) {
        memset (f, 0, sizeof f);
        f[0][0][0] = 1;
        for (int j = 0; j < n; j ++)
            for (int k = 0; k <= i; k ++)
                for (int l = 0; l < i; l ++) {
                    f[j+1][k][l] = (f[j+1][k][l] + f[j][k][l]) % mod; //不取
                    if(k != i) f[j+1][k+1][(l+a[j])%i] = (f[j+1][k+1][(l+a[j])%i] + f[j][k][l]) % mod; //取
                }
        ans = (ans + f[n][i][0]) % mod;
    }

    cout << ans << endl;
}

//是一个很难想的dp式子(是我见得少了)
//积累套路

//f[j][k][l]:当前分母为i时,前j个数选了k个时,余数为l的方案数 
//不取:   f[j+1][k][l] += f[j][k][l];
//取: f[j+1][k+1][(l+a[j])%i] += f[j][k][l];
//注意取得前提条件一定是 k!=i

E - Red and Blue Graph

题意

给定无向图,每个点都可染成红色或蓝色,求在满足:

  1. 有k个点被染成红色
  2. 两端点颜色不一样的边的个数为偶数
    的条件下,可能的方案数

分析

要尽可能与限制条件挂上关系,考虑设:

  1. 设两端点都是红点的边有 b 个,一红一蓝的边有 c 个
  2. 所有红色点的度之和为 s
    \(s = 2*b+c\)
    故 所有红色点的度之和为偶数

偶数个奇数点,组合数求解

原因:

假设选 \(i\) 个度数为奇数的点奇,因为奇数偶数=偶数,为了使得最终的度数之和是偶数,这里的 \(i\) 一定要是偶数,也就是选了偶数个奇数点,那么剩下 \(k-i\) 个点都是度数为偶数的点了(偶数任何数=偶数),一定满足度数和为偶数的条件。

Code

#include <bits/stdc++.h>

using namespace std;
typedef long long ll;
const int N = 2e5 + 5, mod = 998244353;
int d[N];
int n, m, k, odd;
ll ans;
int fact[N], infact[N];

int qmi(int a, int k, int p){
	int res = 1;
	while(k){
		if(k & 1)
			res = (ll)res * a % p;
		a = (ll)a * a % p;
		k >>= 1;
	}
	return res;
}

void init () {
    fact[0] = infact[0] = 1;
	for(int i = 1; i < N; i++){
		fact[i] = (ll)fact[i-1] * i % mod;
		infact[i] = (ll)infact[i-1] * qmi(i, mod - 2,mod) % mod;
	}
}

ll C (int a, int b) {
    return (ll)fact[a] * infact[b] % mod * infact[a - b] % mod;
}

void solve () {
    init ();
    cin >> n >> m >> k;

    while (m --) {
        int x, y;
        cin >> x >> y;
        d[x] ++, d[y] ++;
    }

    for (int i = 1; i <= n; i ++) {
        if (d[i] & 1)   odd ++;
    }

    //奇数个偶数
    for (int i = 0; i <= k; i += 2) {
        if (i > odd || k - i > n - odd)    continue;
        ans = (ans + C (odd, i) * C (n-odd, k-i) % mod) % mod;
    }

    cout << ans << endl;
}

int main () {
    solve ();
}

// 一共有 k 个点被染成红色
// 一共有偶数条边, 连接不同的颜色

// red点度数之和为s,边的两点都是red有b个,颜色不同的有c个
// 有: s = 2 * b + c
// 故 s 为偶数

// 寻找k个点,使他们的点的度数之和为偶数
// 找偶数个奇数点

F - Erase and Rotate

题意

可以对长度为n的序列p进行如下操作0-k次:

  1. 选择任一pi,删去他
  2. 把最末尾的pi挪到最前面去
    问最后得到的字典序最小的序列是啥

分析

如果操作合适的话,每次操作必然会使得字典序减小,故一定会操作k次
考虑两种方案:
先找到最小的数字所在的位置pos

  1. 纯删除(pos <= k):
    那么在pos之前的字符全部要删掉,然后剩余可操作的可利用单调栈来维护一个p[pos]打头,字典序最小的序列
    还有剩余的就从最后开始往前删

  2. 删除+旋转 (n-pos<=k)
    对于一个pi,删除+旋转/旋转+删除等价于直接删了他,所以任一pi要么删除,要么旋转。
    等价于先把pos之后的全删了(因为比p[pos]大,转到前面去也不会更优),p[pos]转到第一个
    后续的操作就和1.的后半部分一样了,单调栈+剩余尾删

Code

#include <bits/stdc++.h>

using namespace std;
const int N = 2e5 + 5;
int p[N], n, k, pos = -1;

vector<int> change1 () {
    vector <int> v1{n+1}; //默认该策略不可行
    //只删除
    if (pos <= k) {
        v1.clear();
        int res = k - pos;
        for (int i = pos; i < n; i++) {
            while (!v1.empty() && p[i] < v1.back() && res > 0) {
                res--;
                v1.pop_back();
            }
            v1.push_back(p[i]);
        }
        //还有剩余的次数删末尾的
        while (res > 0) {
            v1.pop_back();
            res--;
        }
    }
    return v1;
}

vector <int> change2 () {
    vector <int> v2{n+1};
    //旋转+删除
    if (n - pos <= k) {
        v2.clear();
        int res = k - (n - pos); //删除之后还剩多少次
        for (int i0 = 0; i0 < n; i0++) {
            int i = (i0 + pos) % n;
            while (!v2.empty() && p[i] < p[v2.back()] && res >= (v2.back() < pos)) {
                res -= (v2.back() < pos); //在删除点之前的,没旋转过,执行删除操作;在此之后的转了之后再删等价于直接删,故只减1次
                v2.pop_back();
            }
            v2.push_back(i);
        }
        //剩余尾删
        while (res >= (v2.back() < pos)) {
            res -= (v2.back() < pos);
            v2.pop_back();
        }
        for (auto &i : v2) {
            i = p[i];
        }
    }
    return v2;
}

int main() {
    cin >> n >> k;
    for (int i = 0; i < n; i++)     cin >> p[i];
    
    //找前k个和后k个范围内的最小值
    for (int i = 0; i < n; i ++) {
        if (i <= k || n - i <= k) {
            if (pos == -1 || p[i] < p[pos]) {
                pos = i;
            }
        }
    }
    
    auto ans = min(change1(), change2());
    n = ans.size();
    for (int i = 0; i < n; i++)
        cout << ans[i] << ' ';
    cout << endl;
}

//操作:
//1. 删掉一个pi
//2. 把最末尾的pi挪到队头

//策略:
//1. 只删除
//2. 旋转+删除:先转完再删

很有意思

posted @ 2022-08-02 22:26  Sakana~  阅读(243)  评论(5编辑  收藏  举报