CSP-S 2019 题解(部分)& 游记(伪)

本文详细介绍了CSP-S 2019的解题过程,涵盖格雷码、括号树和树上数的解题思路,包括关键算法的实现与代码展示。此外,作者分享了考试期间的心路历程,探讨了题目难度与解题策略,适合备考CSP-S的选手参考学习。
摘要由CSDN通过智能技术生成

CSP-S 2019 题解(部分)& 游记(伪)

文章同步发表在 cnblogsHexo

day 0 - 8:00 a.m. \text{day 0 - 8:00 a.m.} day 0 - 8:00 a.m.

GM: 欸童鞋们我们今天不学新知识点哦!

We: ヾ(✿゚▽゚)ノ好吔!

GM: ⋯ \cdots 我们要考 CSP-S 2019 \text{CSP-S 2019} CSP-S 2019

We: 鄵儞亇cào nĭ mā

day 1 - 8:10 a.m. \text{day 1 - 8:10 a.m.} day 1 - 8:10 a.m.

一眼望去,啊,这就是传说中的 S \text S S 组难度吗?

T1 格雷码

Problem Link 双倍经验 三倍经验

心路历程

看题比做题久系列

好险,还好敲完了闲着没事自己乱造数据玩,玩出来一组hack

估分 100 pts \text{100 pts} 100 pts 吧。

Solution

题目大意:

给定 n n n k k k ,求按指定规则生成的 n n n 0 ∼ 2 n − 1 0\sim 2^n-1 02n1 的二进制码中的第 k k k 个元素。规则如下:

对于长度为 2 n 2^n 2n n n n 位二进制码序列, 0 ∼ 2 n − 1 − 1 0\sim 2^{n-1}-1 02n11 个元素为长度为 2 n − 1 2^{n-1} 2n1 的序列做高位加上一个 0 0 0 的结果;

后面的元素为长度为 2 n − 1 2^{n-1} 2n1 的二进制序列倒置后最高位加上一个 1 1 1 的结果。

1 1 1 位二进制码序列为 0 , 1 0,1 0,1


数据范围: 1 ⩽ n ⩽ 64 , 0 ⩽ k < 2 n 1\leqslant n\leqslant 64,0\leqslant k<2^n 1n64,0k<2n

不可能模拟啦~

众所周知,数据范围在 long long 规模的,基本上就是 log ⁡ \log log 级的做法了。

这里提供一个码量少的做法。(不要跟我扯那些大佬们的 Θ ( 1 ) \Theta(1) Θ(1) 做法)

对于 n n n 位二进制序列,我们简称 0 ∼ 2 n − 1 − 1 0\sim 2^{n-1}-1 02n11 这一段为前半段,剩余部分为后半段。

如果要求的 k k k 在前半段,那说明不是由 n − 1 n-1 n1 位序列翻转最高位置 1 1 1 得到的,那么最高位就会被置 0 0 0,也就是说第 n n n 位为 0 0 0

否则,最高位为 1 1 1 k k k 会变成 2 n − 1 − [ k − ( 2 n − 1 − 1 ) ] = 2 n − 1 − k 2^{n-1}-[k-(2^{n-1}-1)]=2^n-1-k 2n1[k(2n11)]=2n1k

然后就没了。

代码
#include<cstdio>
#define int long long
int n,k;
signed main(){
   
	scanf("%lld%lld",&n,&k);
	while(n){
   
		if(k>=(1<<n-1)){
   
			putchar('1');
			k=(1<<n)-k-1;
		}
		else putchar('0');
		n--;
	}
	return 0;
}

你 以 为 这 题 就 这?

有两个问题。

  1. 题目中的 k < 2 n k<2^n k<2n,而 n n n 最多可以取到 64 64 64

    long long 的范围是 − 2 63 ∼ 2 63 − 1 -2^{63}\sim 2^{63}-1 2632631,而 unsigned long long 才能达到 0 ∼ 2 64 − 1 0\sim 2^{64}-1 02641

    所以 k k k 要用 unsigned long long 存。

    这样想,代码中的 1<<n-1 范围 int 类型的结果,会直接溢出然后 UB \text{UB} UB

    所以保险和节省码量起见,使用 const unsigned long long x 存储 (unsigned long long)1 的值,然后将 1<<n-1 替换为 x<<n-1

  2. 注意到这一行语句:

    k=(x<<n)-k-1;
    

    数据出的好,可以卡住 x<<n。(事实上第 20 20 20 个点确实卡了)

    x<<64ull 刚好溢出。直接自然溢出啥事没有

    所以只好把 x<<64 拆成 (x<<63)+(x<<63)

    但是这并没有实质性地解决问题,虽然两个加数都没有爆,但它们之间的和却挂了。

    我们开心地发现后面做了一个减法,于是我们完全可以先 − - + + +

真代码
#include<cstdio>
#define int unsigned long long
const int x=1;
int n,k;
signed main(){
   
	scanf("%llu%llu",&n,&k);
	while(n){
   
		if(k>=(x<<n-1)){
   
			putchar('1');
			k=(x<<n-1)+((x<<n-1)-1)-k;
		}
		else putchar('0');
		n--;
	}
	return 0;
}

关于 hack:

问题一可以用 64 99999999964 10000000000 hack,你会发现它们的值差的不是一点两点。。。

(题目中提到格雷码的一个性质:相邻两个数码恰好一位的值不同)

问题二,可以用 64 18446744073709551615 hack,标答是 1 1 1 后面全是 0 0 0


T2 括号树

Problem Link 双倍经验 三倍经验

心路历程

cào,T2难度骤增。

先打了个链的部分分,推广到树上就是正解了。

但是我的树形DP(or 树上DP?反正都差不多)传参传的是个长度为 n n nstack,应该会T。

(然后下来我就去跟 @Nefelibata 吹自己常数5e5)

链应该是 Θ ( n ) \Theta(n) Θ(n) 的,没问题,树有点险。

估分 55+rp pts \text{55+rp pts} 55+rp pts

Solution

先把一条链的情况打出来再说。

因为已确定根节点为 1 1 1 f i = i − 1 f_i=i-1 fi=i1,所以 s i = S 1 ∼ S i s_i=S_1\sim S_i si=S1Si,其中 s s s 的含义题目中有给出, S S S 为给定括号串。

k i k_i ki 的含义就可简化为 S 1 ∼ S i S_1\sim S_i S1Si 中的合法子串个数。

相信各位都做过括号匹配之类的题目,这里我们也用一个栈处理。

每遇到一个 ( \texttt ( (push 进去,每遇到一个 ) \texttt ) ) 就判断栈内是否有 ( \texttt ( ( ,有就计算然后 pop

因为要统计从 S 1 ∼ S i S_1\sim S_i S1Si 所有合法括号串,所以定义一个 c n t cnt cnt 表示从 S 1 ∼ S i S_1\sim S_i S1Si 的合法括号串个数,若当前字符可以完成一个匹配,则 cnt+=以第i个元素结尾的合法子串个数

那么,这个“合法子串个数”应该怎样计算呢?

定义 l s t lst lst,表示以当前元素的上一个元素结尾的合法子串个数。

若当前元素是 ( \texttt ( ( ,把 l s t lst lst 丢进栈里,记录如果当前 ( \texttt ( ( 能被匹配,新增的合法括号串个数;

否则当前元素就和之前的合法子串挨不到一起了, l s t lst lst 清零。(突然飚方言)

如果当前元素可以完成一个匹配,则栈顶记录的数就是和当前匹配上的括号紧挨着的之前的合法子串个数,由于当前又新增了一个匹配,记录最新的 l s t lst lst 为栈顶元素 + 1 +1 +1,同时这也是当前的 k i k_i ki。此时 cnt+=lst

可得出以下代码:

//55pts 链 
namespace Task1{
   
	int lst,cnt;
	inline void solve(void){
   
		for(int i=1;i<=n;++i){
   
			if(s[i]=='('){
   
				stk.push(lst);
				lst=0;
			}
			else if(stk.size()){
   
				lst=stk.top()+1;
				cnt+=lst;
				stk.pop();
			}
			else lst=0;
			ans^=cnt*i;
		}
		printf("%lld",ans);
		return;
	}
}

然后直接把这些搬到树上即可。

其中, c n t cnt cnt 不能混合起来记了,必须每个点继承一下自己父亲结点的 c n t cnt cnt,然后自个儿记自个儿的。

l s t lst lst 同理。

但是,由于作者清奇的思考方式与码风,她选择了把 c n t cnt cnt 改成数组, l s t lst lst 改成 dfs 时传参。。。

总之,因为dfs在匹配过程中可能出现新增的 ( \texttt ( ( 还没匹配完,留在栈里,或是后面一大堆 ) \texttt ) ),把之前的给匹配掉了,所以必须即时传参。

然后最后统一计算 cnt 值即可。

Code

差点被常数从 Θ ( n ) \Theta(n) Θ(n) 卡成 Θ ( n 2 ) \Theta(n^2) Θ(n

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值