CF1717 D. Madoka and The Corruption Scheme [思维题?]

传送门:CF

[前题提要]:近期在集中刷1900+的题,原本感觉这类题的思维难度对自己来说似乎没什么大问题,拿到手之后就开始乱贪心,然后就Wa4了,狠狠地被这道题给教育了,故记录一下


感觉这种做法之前在某道题中碰到过类似的,但是想不起来了…

对于本题,我们需要考虑的是那 k k k次操作到底能带来什么样的影响.考虑随便画一棵树(红线代表胜利,黑线表示失败):
在这里插入图片描述
我们会发现对于本棵决策树来说,当 k k k为0时,我们的冠军只有一个,是 a 1 a_1 a1;当 k k k为1时,我们可以格外的选择 a 2 , a 4 a_2,a_4 a2,a4作为冠军(显然 a 1 a_1 a1照样可以被指定为冠军),所以此时我们的冠军位置有3个;当 k k k为3的时候,我们的冠军位有4个.那么就针对于本情况来说,贪心的想,显然我们需要将编号小的选手放在需要的 k k k小的节点上,因为我们要尽量让编号小的选手胜利,这样做就可以更难的让编号大的被指定为冠军了.

但是上面只是针对于一颗特殊的决策树来说,对于其他的决策树该怎么办呢?幸运的是,我们会发现对于几颗层数相同的决策树,我们的冠军数和k的关系都是一定的.也就是对于 n n n为2的树来说,无论我们的这棵树的胜利边是如何选择,我们的冠军数都是成 1 , 2 , 1 1,2,1 1,2,1的规律随 k k k增长.为什么会出现上述这种情况呢?这种现象是不是可以推广到n是其他的决策树呢?

答案是肯定的,因为对于任意一颗决策树,我们都可以将其转化为一颗固定左边选手胜利的决策树(或者任意一颗形态的决策树)
下面简单来说明一下上述结论为什么是对的
假设存在一种序列是一种最优方案,并且满足不是所有红线都在左边.但是因为我们的序列是可以随便排列的,我们完全可以将红线在右边的子树和左边的子树进行一个调换,此时就将一个在右边的红线换到了左边,并且上述操作是不会影响 k k k次换边的,因为假设原本 < u , v 1 > <u,v_1> <u,v1>是红线,我们想将其换成 < u , v 2 > <u,v_2> <u,v2>,此时我们调换了 v 1 , v 2 v_1,v_2 v1,v2,此时的 v 1 , v 2 v_1,v_2 v1,v2依旧存在,此时我们依旧可以将红线改为 < u , v 2 > <u,v_2> <u,v2>.类似的,对于任意一颗不是所有红线都在左边的决策树,我们可以采取以下策略将其改为一颗红线都在左边的决策树:对于任意一条红线在右边的 < u , v > <u,v> <u,v>,我们可以将 v v v整颗子树和左子树进行交换,然后递归解决 v v v子树的红线分布问题.也就是说无论是那种序列,我们都可以找到一个满足所有红线都在左边的序列与之同构


那么为了讨论方便起见,我们可以直接针对固定左边选手胜利的决策树进行讨论.

在上述简化的前提下.我们会发现对于给定的 k k k,最终胜利的位置数是可以确定的.逆转一下,也就是说对于任意的位置,我们都可以求出该位置成为最终胜利者需要的最小的k.那么我们现在的问题就变成了怎么解决每个叶子节点需要的 k k k.其实不难发现,对于每个节点来说,他们的状态都是可以通过上一层的节点的状态递推过来的.如果一个节点,它相对于父亲节点位于左儿子的位置,那么它所需要的k和父节点相同,否则它需要的k是父节点+1.也就说每一个节点都会引出一个和本身权值相同的点以及一个权值+1的点,这个递推关系其实就是杨辉三角.当然通过手模多画几层也不难发现分布是杨辉三角.

那么我们现在的答案显然就是杨辉三角第 n n n层的前 k k k个数字之和.求一下组合数即可.


下面是具体的代码部分;

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define root 1,n,1
#define ls (rt<<1)
#define rs (rt<<1|1)
#define lson l,mid,rt<<1
#define rson mid+1,r,rt<<1|1
inline ll read() {
	ll x=0,w=1;char ch=getchar();
	for(;ch>'9'||ch<'0';ch=getchar()) if(ch=='-') w=-1;
	for(;ch>='0'&&ch<='9';ch=getchar()) x=x*10+ch-'0';
	return x*w;
}
inline void print(__int128 x){
	if(x<0) {putchar('-');x=-x;}
	if(x>9) print(x/10);
	putchar(x%10+'0');
}
#define maxn 1000000
#define int long long
const int mod=1e9+7;
const double eps=1e-8;
#define	int_INF 0x3f3f3f3f
#define ll_INF 0x3f3f3f3f3f3f3f3f
int qpow(int a,int b) {
	int ans=1;
	while(b) {
		if(b&1) ans=ans*a%mod;
		b>>=1;
		a=a*a%mod;
	}
	return ans;
}
int fac[maxn],in_fac[maxn];
void init(int limit=2e5) {
	fac[0]=1;
	for(int i=1;i<=limit;i++) {
		fac[i]=fac[i-1]*i%mod;
	}
	in_fac[limit]=qpow(fac[limit],mod-2);
	for(int i=limit-1;i>=0;i--) {
		in_fac[i]=in_fac[i+1]*(i+1)%mod;
	}
}
int C(int a,int b) {
	return fac[a]*in_fac[b]%mod*in_fac[a-b]%mod;
}
signed main() {
	init();
	int n=read();int k=read();
	if(k>=n) {
		cout<<qpow(2,n)<<endl;
		return 0;
	}
	int ans=0;
	for(int i=0;i<=k;i++) {
		ans+=C(n,i);ans%=mod;
	}
	cout<<ans<<endl;
	return 0;
}
  • 17
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值