Loading

AtCoder Beginner Contest 310




B - Strictly Superior

难度: ⭐

题目大意

给定n个商品的价格, 每个商品还有若干个属性, 请问是否存在一个商品是另外一个商品的上位品; 上位品的定义分两种, 一是价格相同, 但是商品A的属性不仅包括了商品B的属性, 还比商品B多了至少一个属性; 二是如果两商品的属性相同, 但是商品A的价格比商品B便宜, 则A也是B的上位品;

解题思路

数据不大, 暴力即可;

神秘代码

#include<bits/stdc++.h>
//#define int long long
#define IOS ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
#define endl '\n'
using namespace std;
const int N = 1e3 + 10;
typedef pair<int, int> PII;
int n, m;
int p[N], c[N],f[N][N];
int check(int a, int b) {
    for (int i = 1, j=1; i <= c[a]; i++) {
        while (j <= c[b] && f[b][j] < f[a][i]) j++;
        if (f[b][j] > f[a][i] || j > c[b]) return 0;
        else j++;
    }
    if (c[a] == c[b]) return 1;
    else return 2;
}
signed main(){
    cin >> n >> m;
    for (int i = 1; i <= n; i++) {
        cin >> p[i] >> c[i];
        for (int j = 1; j <= c[i]; j++) {
            cin >> f[i][j];
        }
        sort(f[i] + 1, f[i] + 1 + c[i]);
    }
    bool f = false;
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= n; j++) {
            if (p[j] < p[i]) {
                int res = check(i, j);
                if (res != 0) {
                    f = true;
                    break;
                }
            }
            else if (p[j] == p[i]) {
                int res = check(i, j);
                if (res == 2) {
                    f = true;
                    break;
                }
            }
        }
        if (f) break;
    }
    if (f) cout << "Yes";
    else cout << "No";
    return 0;
}




C - Reversible

难度: ⭐

题目大意

给定n个字符序列, 问这n个序列中一共有多少个不同的序列; 对于序列的相同定义为如果一个序列与另一个序列或它的反转序列相同, 那么就可以判定为相同: abc=cba;

解题思路

对于每个序列, 我们可以保存它和它反转序列中字典序较大的那个即可;

神秘代码

#include<bits/stdc++.h>
//#define int long long
#define IOS ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
#define endl '\n'
using namespace std;
const int N = 2e5 + 10;
typedef pair<int, int> PII;
int n, m;
set<string> st;
signed main(){
    cin >> n;
    for (int i = 1; i <= n; i++) {
        string s;
        cin >> s;
        string str = s;
        reverse(s.begin(), s.end());
        st.insert(max(str,s));
    }
    cout << st.size();
    return 0;
}




D - Peaceful Teams

难度: ⭐⭐⭐⭐

题目大意

给定编号为1~n的n个人, 要求把他们分到t个组中, 但是其中有m对死对头, 死对头不能在一个组中, 请问一共有多少种分组方法, 由于组没有编号, 所以要注意去重;

解题思路

由于n的范围很小, 所以可以考虑用dfs; 该题的难点主要在于去重, 笨方法是遍历所有可能, 最后除以t的全排列, 但是这样大概率会超时; 所以要考虑在dfs的时候去去重; 对于每个人, 我们要限制住他可以选择的组别范围; 因为要去重, 所以我们要按顺序来, 不可以跳; 如果现在已经有x个组, 那么当前这个人可以选择这x个组, 或者去一个没人的组: x+1, 但注意按照去重规则他不能去x+2; 按照这样的规则最后出来的就不会有重复;

神秘代码

#include<bits/stdc++.h>
#define int long long
#define IOS ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
#define endl '\n'
using namespace std;
const int N = 4e5 + 10, mod = 998244353, inf = 1e18;
typedef pair<int, int> PII;
int n, m, res;
map<PII, int> mp;
vector<int> v[20];
void dfs(int u, int x){
    if(x > n){
        if(u == m) res++;
        return;
    }
    for(int i = 1; i <= u; i++){
        bool f = true;
        for(int t : v[i]){
            if(mp[{x, t}]){
                f = false;
                break;
            }
        }
        if(f){
            v[i].push_back(x);
            dfs(u, x + 1);
            v[i].pop_back();
        }
    }
    v[u + 1].push_back(x);
    dfs(u + 1, x + 1);
    v[u + 1].pop_back();
}
signed main(){
    int k;
    cin >> n >> m >> k;
    for(int i = 1; i <= k; i++){
        int a, b;
        cin >> a >> b;
        mp[{a, b}] = mp[{b, a}] = 1;
    }
    dfs(0, 1);
    cout << res;
    return 0;   
}




E - NAND repeatedly

难度: ⭐⭐⭐⭐

题目大意

现在给定一个长为n的由0/1组成的数列P; 我们定义一种运算A^B: 0^1=1, 1^0=1, 0^0=1, 1^1=0; f(1,n)意思是((((P1 ^ P2) ^ P3) ^ P4) ... ^ Pn); 现在要遍历该数列, 把所有f(i, j)的值加起来输出, 其中1 <= i <= n, i <= j <= n; 注意: 当i = j时不会进行^运算, 其结果就是Pi本身;

解题思路

由于n的范围较大, 二重循环肯定会超时, 所以我们要去找规律; 根据定义我们可以看出, 0和任何数运算结果都是1; 也就是说如果Px=0, 那么所有f(i,x)的值都是1; 根据这个规律我们可以用dp进行求解; dp[i]的状态表示为所有右边界是Pi的区间的运算值的和; 根据i和j的范围可以知道所有右边界是第i位的区间(长度大于1)一共有i-1个; 当Pi=0时, 由前面得到规律知道此时dp[i] = i - 1; 当Pi=1时, 可以根据以Pi-1为右边界的区间进行求解; 如果该区间的运算结果为1, 那么再和1运算结果就是0, 反之如果为0的话结果就是1; 而dp[i-1]的值就意味着有多少个以Pi-1为右边界的区间的值是1, 所以我们用(i - 1) - dp[i - 1]就得到了以Pi-1为右边界的区间运算值为0的个数, 这就是以Pi为右边界的区间的运算值总和, 再加上Pi也是1, 所以dp[i] = (i - 1) - dp[i - 1] + 1

神秘代码

#include<bits/stdc++.h>
#define int long long
#define IOS ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
#define endl '\n'
using namespace std;
const int N = 1e6 + 10;
typedef pair<int, int> PII;
int n, m, res;
int f[N], dp[N];
signed main() {
    cin >> n;
    for (int i = 1; i <= n; i++) {
        char c;
        cin >> c;
        f[i] = c - '0';
    }
    for (int i = 1; i <= n; i++) {
        if (f[i] == 1) dp[i] = (i - 1) - dp[i - 1] + 1;
        else dp[i] = i - 1;
        res += dp[i];
    }
    cout << res;
    return 0;
}




F - Make 10 Again

难度: ⭐⭐⭐⭐

题目大意

给定n个骰子, 给定骰子可以掷出的点数范围: 1 ~ f[i]; 同时掷出这些骰子, 问出现满足以下条件的概率是多少;
条件为: 从这n个骰子从选出若干个骰子, 这些骰子的点数加起来是10;

解题思路

一道概率+状压dp; 虽然点数的范围最大是1e6, 但是大于10的点数对答案没有贡献; 所以我们只考虑小于等于10的点数, 这个范围就可以考虑用状压dp来做; 状态表示dp[i][j]表示用前i个骰子且此时状态为j的概率是多少; 二进制状态中, 第k位是0/1, 表示该状态是否可以凑出点数k-1, 因为第1位代表的点数是0, 第11位是10; 最终的答案就遍历所有dp[n][k]的和, k表示第11位是1的状态; 而状态转移分为两部分
首先是随机到1~10之间的数j, 这些数是对答案有贡献, 从前面的状态表示可以知道, 设当前二进制状态是k, 那么新的状态就是k | (k << j), 其中k指不选该骰子, 那就是本身, (k << j)指把之前可以凑出的数再加上j, 明显这两种情况都可以凑出, 所有做或运算; 由于我们只要前11位, 所有我们再并上一个((1 << 11) - 1)来规范范围; 所以状态转移为: dp[i][k | (k << j) & ((1 << 11) - 1)] += dp[i - 1][k] * qmid(f[i], mod - 2)(概率逆元);
再就是如果随机到大于10的数j, 其对状态变化不会产生影响, 所以状态还是k, 状态转移为dp[i][k] += dp[i - 1][k] * qmid(f[i], mod - 2) * (f[i] - 10) (注意这里直接乘f[i] - 10即可, 不用再循环了);

神秘代码

#include<bits/stdc++.h>
#define int long long
#define IOS ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
#define endl '\n'
using namespace std;
const int N = 1e3 + 10, mod = 998244353;
typedef pair<int, int> PII;
int n, m;
int f[N];
int dp[N][1 << 11];
int qmid(int a, int b) {
	int res = 1;
	while (b) {
		if (b & 1) res = res * a % mod;
		a = a * a % mod;
		b >>= 1;
	}
	return res;
}
signed main() {
	cin >> n;
	dp[0][1] = 1;
	for (int i = 1; i <= n; i++) {
		cin >> f[i];
		int q = qmid(f[i], mod - 2) % mod;
		for (int j = 1; j <= min(f[i], (int)10); j++) {
			for (int k = 1; k < (1 << 11); k++) {
				(dp[i][k | (k << j) & ((1 << 11) - 1)] += dp[i - 1][k] * q) %= mod;

			}
		}
		if (f[i] > 10) {
			for (int k = 0; k < (1 << 11); k++) {
				(dp[i][k] += dp[i - 1][k] * (f[i] - 10) % mod * q) %= mod;
			}
		}
	}
	int res = 0;
	for (int k = 1; k < (1 << 11); k++) {
		if ((k >> 10) & 1) {
			res = (res + dp[n][k]) % mod;
		}
	}
	cout << res;
	return 0;
}
posted @ 2023-10-28 13:55  mostimali  阅读(8)  评论(0编辑  收藏  举报