ABC310 A-F

freee プログラミングコンテスト2023(AtCoder Beginner Contest 310) - AtCoder

我的评价是:不如看官解

A - Order Something Else

iD意:

 你有两种买饮料的方式:1、直接花P元购买,2、用券购买,需要点至少一份菜品之后花Q元购买(单点不送)

题解:

min(P,Q+min(Di))

#define _CRT_SECURE_NO_WARNINGS
#include
#include
#include
#include
#include
#include
using namespace std;
typedef long long LL;
typedef pair PII;
const LL N = 2e5 + 10, MOD = 998244353;

void solve()
{
	int n, p, q, mn = 0x3f3f3f3f;
	scanf("%d%d%d", &n, &p, &q);
	for (int i = 1, x; i <= n; ++i)
	{
		scanf("%d", &x);
		mn = min(mn, x);
	}
	printf("%d\n", min(p, q + mn));
}

int main()
{
	int T = 1;
	//scanf("%d", &T);
	while (T--)
	{
		solve();
	}
	return 0;
}

B - Strictly Superior

题意:

超市里共有N件商品,每件商品有一个价格Pi和Ci个功能,功能1<=Fi,j<=M,问是否存在一件商品是另一件商品的上位替代:i是j的上位替代指 Pi

题解:

暴力枚举i,j并判断,可以用bitset优化时间(没必要)

#define _CRT_SECURE_NO_WARNINGS
#include
#include
#include
#include
#include
#include
#include
using namespace std;
typedef long long LL;
typedef pair PII;
const LL N = 1e2 + 10, MOD = 998244353;
bitset<101>a[N];
int p[N];
void solve()
{
	int n, m;
	scanf("%d%d", &n, &m);
	for (int i = 1, c; i <= n; ++i)
	{
		scanf("%d%d", &p[i], &c);
		for (int j = 1, x; j <= c; ++j)
		{
			scanf("%d", &x);
			a[i][x] = 1;
		}
	}
	for (int i = 1; i <= n; ++i)
	{
		for (int j = 1; j <= n; ++j)if (i != j)
		{
			if (p[i] <= p[j])
			{
				bitset<101>t = a[i] & a[j];
				if (t == a[j] && (p[i] < p[j] || t != a[i]))
				{
					printf("Yes\n");
					return;
				}
			}
		}
	}
	printf("No\n");
}

int main()
{
	int T = 1;
	//scanf("%d", &T);
	while (T--)
	{
		solve();
	}
	return 0;
}

C - Reversible

题意:

定义字符串A==B为A与B相同或者A逆序之后相同("abc"="abc"="cba"),问在N个字符串中,有多少个字符串是不同的(去重之后还剩几个)

题解:

用set就行,也可以字符串哈希(没必要)

#define _CRT_SECURE_NO_WARNINGS
#include
#include
#include
#include
#include
#include
#include
using namespace std;
typedef long long LL;
typedef unsigned long long ULL;
typedef pair PII;
const LL N = 2e5 + 10, P = 131;
char ch[N];
setst;
ULL get_hash(int n)
{
	ULL h = 0;
	for (int i = 1; i <= n; ++i)
		h = h * P + ch[i];
	return h;
}
void solve()
{
	int n;
	scanf("%d", &n);
	for (int i = 1; i <= n; ++i)
	{
		scanf("%s", ch + 1);
		int len = strlen(ch + 1);
		ULL x = get_hash(len);
		reverse(ch + 1, ch + 1 + len);
		ULL y = get_hash(len);
		if (st.find(x) == st.end() && st.find(y) == st.end())
			st.insert(x);
	}
	printf("%lld", st.size());
}

int main()
{
	int T = 1;
	//scanf("%d", &T);
	while (T--)
	{
		solve();
	}
	return 0;
}

D - Peaceful Teams

题意:

有N个人,要把所有人分成T组,给出M组敌对关系,当把Ai和Bi分配到同一组时他俩会打起来(Ai和Bi不能分配到同一组),问能有多少种不同的分配方式

题解:

T和N最大才10,考虑直接爆搜,对于每个人i讨论分配到每一组的情况,这时时间复杂度为O(N^T)一眼寄,但是这种情况会有大量重复(例如{1,2},{3}和{3},{1,2}会被认为是两种不同的方案,具体就是有每种方案会重复T!次)如果我们让它去重时间复杂度就会降到O(N^T/T!),去重的方法可以让每组的第一个成员编号升序(这样类似于{3},{1,2}的重复情况将不再会被考虑)。

关于去重的:比如把1,2,3依次分配到两个集合的时候不去重的情况下会搜出来{ {1},{2,3} },{ {1,2},{3} },{ {1,3},{2} }, { {2,3},{1} },{ {3},{1,2} },{ {2},{1,3} },相当于会把集合全排列一遍,但是如果保证集合的第一个元素升序后三种方案就不会被搜出来了。那就只要限制把人放进集合的时候不跳过一个空集合就能保证集合的第一个元素升序。

#define _CRT_SECURE_NO_WARNINGS
#include
#include
#include
#include
#include
#include
using namespace std;
typedef long long LL;
typedef unsigned long long ULL;
typedef pair PII;
const LL N = 2e5 + 10, P = 131;
int n, t, m, book[11], f[11][11], ans = 0;
vectorv[11];//存每个集合中已有的人
void dfs(int i)//枚举人i放入每个集合的情况
{
	if (i > n)//所有人都放入了集合
	{
		for (int j = 1; j <= t; ++j)
			if (!v[j].size())return;//人没分够t组
		++ans;
		return;
	}
	for (int j = 1; j <= t; ++j)//考虑尝试把人放入集合j
	{
		int flag = 0;//先判断人i会不会与已经在集合j中的人冲突
		for (auto k : v[j])
			flag |= f[i][k];
		if (!flag)//不会冲突的情况
		{
			v[j].push_back(i);//把i加入集合j
			dfs(i + 1);//开搜下一个人
			v[j].pop_back();//把人i从集合j中取出继续判断i能不能放入下一个集合
		}
		if (!v[j].size())return;//去重用的
	}
}
void solve()
{
	scanf("%d%d%d", &n, &t, &m);
	for (int i = 1; i <= m; ++i)
	{
		int x, y;
		scanf("%d%d", &x, &y);
		f[x][y] = f[y][x] = 1;//存x,y冲突
	}
	dfs(1);
	printf("%d\n", ans);
}

int main()
{
	int T = 1;
	//scanf("%d", &T);
	while (T--)
	{
		solve();
	}
	return 0;
}

E - NAND repeatedly

题意:

给定一个长度为N的01串A,定义一种运算⊼,0⊼0=0⊼1=1⊼0=1,1⊼1=0,f(i,j)为Ai⊼Ai+1⊼...⊼Aj(i<=j)

所有f(i,j)相加的和(表达式看原题我懒得打了E - NAND repeatedly (atcoder.jp))

题解:

后缀和+DP(?)。考虑dp[i][j]为以i结尾的后缀和为j的数量,状态转移见代码,最终答案就为\sum_{i=1}^{N}dp[i][1]

#define _CRT_SECURE_NO_WARNINGS
#include
#include
#include
#include
#include
#include
using namespace std;
typedef long long LL;
typedef unsigned long long ULL;
typedef pair PII;
const LL N = 1e6 + 10, P = 131;
char ch[N];
void solve()
{
	int n, dp[2][2] = { 0 };
	scanf("%d%s", &n, ch + 1);
	LL ans = 0;
	for (int i = 1; i <= n; ++i)
	{
		if (ch[i] == '1')
		{
			dp[i & 1][0] = dp[i - 1 & 1][1];
			dp[i & 1][1] = dp[i - 1 & 1][0];
		}
		else
		{
			dp[i & 1][0] = 0;
			dp[i & 1][1] = dp[i - 1 & 1][1] + dp[i - 1 & 1][0];
		}
		dp[i & 1][ch[i] - '0']++;
		ans += dp[i & 1][1];
	}
	printf("%lld", ans);
}

int main()
{
	int T = 1;
	//scanf("%d", &T);
	while (T--)
	{
		solve();
	}
	return 0;
}

F - Make 10 Again

题意:

给定N个骰子,第i个骰子有Ai个面(能骰出点数1到点数Ai),问在同时投出这N个骰子后,存在选任意个骰子能使其点数和为10的概率

题解: 

状压DP。可以通过DP求出能使点数和为10的方案数然后除总方案数(A1*A2*...*An)求出概率。

状态F表示一个能被加出来的数的集合:从F二进制表示的第0位到第10位分别设为fi,若fi==1则表示i能被加出来,fi==0表示i不能被加出来。当将这个集合加上d,则状态转移为F' = F | (F <

开一个dp[N][1<<11],dp[i][j]表示考虑到第i个骰子时,状态为j的方案的个数。最终的合理的方案数即为dp[n][j],(j>>10&1==1)的和(就是说已经用到第i个骰子的时候这个状态是包含10的)

在已知考虑了前i个骰子的情况下求考虑第i+1个骰子的所有状态的方案数为:枚举考虑了前i个骰子的所有状态,对每种状态枚举当前骰子的可能点数进行一次状态转移dp[i+1][F']+=dp[i][F]。而当当前骰子骰出点数大于10时F' == F,所以对于点数大于10的部分可以一起转移dp[i+1][F] += dp[i][F]*(Ai-10),(Ai>10)

最终时间时间复杂度为O(N*2^11*10)。

(题解写的稀烂,建议结合代码看,或者直接随便找一份大佬的代码研究研究,比如说这位大佬的提出 #43605272 - freee プログラミングコンテスト2023(AtCoder Beginner Contest 310),这份题解(?)的思路基本就是照着这位大佬的代码抄的)

#define _CRT_SECURE_NO_WARNINGS
#include
#include
#include
#include
#include
#include
using namespace std;
typedef long long LL;
typedef pair PII;
const LL N = 1e2 + 10, MOD = 998244353;
LL a[N], dp[N][1 << 11];
LL mi(LL x, LL y)//快速幂
{
	x %= MOD;
	if (y == 0)return 1;
	if (y % 2)
		return mi(x * x, y / 2) * x % MOD;
	return mi(x * x, y / 2) % MOD;
}
LL inv(LL x)//求逆元
{
	return mi(x, MOD - 2);
}
void solve()
{
	int n;
	scanf("%d", &n);
	dp[0][1] = 1;
	for (int i = 1; i <= n; ++i)
		scanf("%lld", &a[i]);
	for (int i = 0; i < n; ++i)
	{
		for (int f = 0; f < 1 << 11; ++f)//枚举考虑了前i个骰子的所有状态
		{
			//当前点数为1到10
			for (int j = 1; j <= min(10LL, a[i + 1]); ++j)
				dp[i + 1][f | (f << j) & (1 << 11) - 1] = (dp[i + 1][f | (f << j) & (1 << 11) - 1] + dp[i][f]) % MOD;
			//当前点数大于10
			dp[i + 1][f] = (dp[i+1][f] + dp[i][f] * max(a[i + 1] - 10, 0LL)) % MOD;
		}
	}
	LL ans = 0;
	for (int i = 0; i < 1 << 11; ++i)
	{
		if (i >> 10 & 1)
			ans = (ans + dp[n][i]) % MOD;
	}
	for (int i = 1; i <= n; ++i)
		ans = ans * inv(a[i]) % MOD;
	printf("%lld\n", ans);
}
int main()
{
	int T = 1;
	//scanf("%d", &T);
	while (T--)
	{
		solve();
	}
	return 0;
}

你可能感兴趣的:(Atcoder,c++,算法)