[题解]CSP2019 Solution - Part A

  • 至于为什么是 Part A \text{Part A} Part A 而不是 Day 1 \text{Day 1} Day 1
  • 那是因为 Day1 T3 还没改
  • (那这六题的 solution \text{solution} solution 就按难度顺序写吧)
  • 感觉今年的画风和 NOIP 2016 \text{NOIP 2016} NOIP 2016 有点像?

D1T1 code

Solution

  • 直接模拟
  • 如果 k < 2 n − 1 k<2^{n-1} k<2n1 就输出 0 0 0
  • 否则输出 1 1 1 并把 k k k 变成 2 n − 1 − k 2^n-1-k 2n1k
  • 然后 n n n 减掉 1 1 1 继续进行下去直到 n = 0 n=0 n=0 为止
  • 注意 k k k 要开 unsigned long long \text{unsigned long long} unsigned long long
  • O ( n ) O(n) O(n)

Code

#include <bits/stdc++.h>

typedef unsigned long long ull;

int n;
ull k;

void solve(int n, ull k)
{
	if (!n) return;
	ull mid = 1ull << n - 1;
	if (k < mid) putchar('0'), solve(n - 1, k);
	else putchar('1'), solve(n - 1, mid - 1 - (k - mid));
}

int main()
{
	std::cin >> n >> k;
	solve(n, k);
	return puts(""), 0;
}

D1T2 brackets

Solution

  • c n t u cnt_u cntu 表示根到 u u u 的路径组成的括号序列,以 u u u 为右端点的合法括号序列个数
  • 那么 k u k_u ku 就等于根到 u u u 的路径上所有点的 c n t cnt cnt 之和
  • 易得如果存在 u u u 的一个深度最大的祖先 v v v 使得 v v v u u u 的路径组成的括号序列是合法括号序列
  • 那么 c n t u = c n t f a v + 1 cnt_u=cnt_{fa_v}+1 cntu=cntfav+1
  • 对于求这个 v v v ,可以维护一个栈
  • 从根到 u u u ,如果是左括号则直接加入,如果是右括号且栈不空则弹栈
  • 那么如果 u u u 为右括号,那么 v v v 为这次弹出的括号对应的点
  • 而对于求出所有的 u u u ,可以在对树 DFS \text{DFS} DFS 的过程中维护这个栈,在 DFS \text{DFS} DFS 回溯时把栈操作也退回即可
  • O ( n ) O(n) O(n)

Code

#include <bits/stdc++.h>

template <class T>
inline void read(T &res)
{
	res = 0; bool bo = 0; char c;
	while (((c = getchar()) < '0' || c > '9') && c != '-');
	if (c == '-') bo = 1; else res = c - 48;
	while ((c = getchar()) >= '0' && c <= '9')
		res = (res << 3) + (res << 1) + (c - 48);
	if (bo) res = ~res + 1;
}

typedef long long ll;

const int N = 5e5 + 5;

int n, fa[N], ecnt, nxt[N], adj[N], go[N], stk[N], top, cnt[N];
char s[N];
ll sum[N], ans;

void add_edge(int u, int v)
{
	nxt[++ecnt] = adj[u]; adj[u] = ecnt; go[ecnt] = v;
}

void dfs(int u)
{
	int tf = 0;
	if (s[u] == '(') stk[++top] = u;
	else if (top) cnt[u] = cnt[fa[tf = stk[top--]]] + 1;
	sum[u] = sum[fa[u]] + cnt[u];
	ans ^= sum[u] * u;
	for (int e = adj[u], v = go[e]; e; e = nxt[e], v = go[e])
		dfs(v);
	if (s[u] == '(') top--;
	else if (tf) stk[++top] = tf;
}

int main()
{
	int x;
	read(n);
	scanf("%s", s + 1); n = strlen(s + 1);
	for (int i = 2; i <= n; i++) read(x), add_edge(fa[i] = x, i);
	dfs(1);
	return std::cout << ans << std::endl, 0;
}

D2T1 meal

Solution

  • 如果没有一半的限制,那么答案为
  • ∏ i = 1 n ( 1 + ∑ j = 1 m a i , j ) − 1 \prod_{i=1}^n(1+\sum_{j=1}^ma_{i,j})-1 i=1n(1+j=1mai,j)1
  • 而出现次数超过一半的主要食材最多 1 1 1
  • 故可以枚举超过一半的主要食材是哪种,并把对应的方案数从上式种扣掉即可
  • 假设确定了一种食材 x x x ,考虑如何求这种食材出现超过一半的方案数
  • u i = a i , x u_i=a_{i,x} ui=ai,x v i = ∑ j = 1 , j ≠ x m a i , j v_i=\sum_{j=1,j\ne x}^ma_{i,j} vi=j=1,j=xmai,j
  • 问题就转化成了有 n n n 个变量,对于第 i i i 个变量有 u i u_i ui 种方法使其为 1 1 1 v i v_i vi 种方法使其为 − 1 -1 1 1 1 1 种方法使其为 0 0 0,求有多少种方案使得 1 1 1 的个数严格大于 − 1 -1 1 (所有变量的和严格大于 0 0 0
  • 于是可以 DP \text{DP} DP :设 f [ i ] [ j ] f[i][j] f[i][j] 表示前 i i i 个变量和为 j j j 的方案数( j j j 可以为负),转移时枚举下一个变量的取值
  • O ( m n 2 ) O(mn^2) O(mn2)

Code

#include <bits/stdc++.h>

template <class T>
inline void read(T &res)
{
	res = 0; bool bo = 0; char c;
	while (((c = getchar()) < '0' || c > '9') && c != '-');
	if (c == '-') bo = 1; else res = c - 48;
	while ((c = getchar()) >= '0' && c <= '9')
		res = (res << 3) + (res << 1) + (c - 48);
	if (bo) res = ~res + 1;
}

const int N = 105, E = 205, M = 2005, rqy = 998244353;

int n, m, a[N][M], f[N][E], sum[N], tmp[N], ans = 1;

inline void add(int &a, const int &b)
{
	a += b; if (a >= rqy) a -= rqy;
}

inline void sub(int &a, const int &b)
{
	a -= b; if (a < 0) a += rqy;
}

int main()
{
	read(n); read(m);
	for (int i = 1; i <= n; i++) sum[i] = 1;
	for (int i = 1; i <= n; i++)
		for (int j = 1; j <= m; j++)
			read(a[i][j]), add(sum[i], a[i][j]);
	for (int i = 1; i <= n; i++) ans = 1ll * ans * sum[i] % rqy;
	sub(ans, 1);
	for (int i = 1; i <= m; i++)
	{
		for (int j = 1; j <= n; j++) tmp[j] = sum[j], sub(tmp[j], a[j][i]), sub(tmp[j], 1);
		for (int j = -n; j <= n; j++)
			for (int k = 0; k <= n; k++)
				f[k][j + n] = 0;
		f[0][n] = 1;
		for (int j = 1; j <= n; j++)
			for (int k = -n; k <= n; k++)
			{
				add(f[j][k + n], f[j - 1][k + n]);
				if (k > -n) add(f[j][k + n], 1ll * f[j - 1][k - 1 + n] * a[j][i] % rqy);
				if (k < n) add(f[j][k + n], 1ll * f[j - 1][k + 1 + n] * tmp[j] % rqy);
			}
		for (int j = 1; j <= n; j++) sub(ans, f[n][j + n]);
	}
	return std::cout << ans << std::endl, 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值