AtCoderBeginnerContest132 DEF

AtCoderBeginnerContest132 DEF

https://atcoder.jp/contests/abc132

D题 排列组合
题意:
给定n,k,代表n个球中有k个蓝球,n-k个红球
将这n个球排成一列,每次可以将连续的一段蓝球取走,直到所有的蓝球全部被取走
输出k个数其中i(1<=i<=k)表示取 i 次才能取走所有的蓝球的摆放球的方案数

思路:
好久没写排列组合,不管是求法还是写法都有些忘了,温故而知新啊
定义m代表篮球个数,t代表红球个数,
假设要取 i 次,t个红球代表有t+1个地方可以给蓝球放,放 i 坨
所以是C(t+1,i)
如何分成i坨呢?隔板法,分成i坨要插入i-1个板子,有m-1个位置可以插
所以是C(m-1,i-1)
所以取第i次的方案数 = C(t+1,i) * C(m-1,i-1)
求组合的函数有点忘了,没有写 if(a<b) return 0; re了一发

const int N=2010,M=1e6+10;
int t,n,m;
ll fac[N],inv[N];
ll qmi(ll a,ll b){
    ll res = 1;
    while(b){
        if(b & 1) res = res * a % mod;
        a = a * a % mod;
        b >>= 1;
    }
    return res;
}
ll ck(ll a,ll b){
    if(a < b) return 0;
    return fac[a] * inv[a-b] % mod * inv[b] % mod;
}
int main(){
    fac[0] = inv[0] = 1;
    for(int i=1;i<N;i++){
        fac[i] = fac[i-1] * i % mod;
        inv[i] = inv[i-1] * qmi(i,mod-2) % mod;
    }
    read(n),read(m);
    t = n - m;
    for(int i=1;i<=m;i++){  
        printf("%lld\n", ck(t + 1,i) * ck(m - 1,i-1) % mod);
    }
    return 0;
}

E题 BFS
题意:
一个有向图,给定起点s和终点t,每次只能走三步,不限制走的次数。
问最终能否在第三步到达终点。
思路:
bfs的一个变形,定义dis[i][0~2]
dis[i][0] = 3步的倍数到 i 的 最短距离
dis[i][1] = 3步倍数多一步到 i 的最短距离
dis[i][2] = 3步倍数多两部到 i 的最短距离

int s,t,n,m;
int e[N],ne[N],h[N],idx;
int dis[N][3];
void add(int a,int b){
	e[idx] = b,ne[idx] = h[a],h[a]=idx++;
}
int main(){
	memset(h,-1,sizeof h);
	memset(dis,-1,sizeof dis);
	read(n),read(m);
	for(int i=0;i<m;i++){
		int u,v;
		read(u),read(v);
		add(u,v);
	}
	read(s),read(t);
	queue<pii> q;
	q.push({s,0});
	dis[s][0] = 0;
	while(q.size()){
		auto top = q.front();q.pop();
		int u = top.first;
		int step = top.second;
		for(int i=h[u];~i;i=ne[i]){
			int v = e[i];
			int nowstep = (step + 1) % 3;
			if(dis[v][nowstep] != -1) continue;
			dis[v][nowstep] = dis[u][step] + 1;
			q.push({v,nowstep});
		}
	}
	if(dis[t][0]==-1) puts("-1");
	else printf("%d\n", dis[t][0] / 3);
	return 0;
}

F题 分块 + dp
题意:
给定一个N,K,问生成长度为K的数组,且相邻两数的乘积不超过N的方案数。
思路:
dp ,
f [ i ] [ j ] 代表生成长度为i 的数组,且最后一位是 j 的方案数
i 的 范围是 1 - 100
j 的 范围是 1 - 1e9
巨大无比!
1e9考虑分块,就是100 * sqrt(1e9) 不超时
怎么搞
将数字分成s,b两部分,s代表小于等于sqrt(n)的数,b代表大于sqrt(n)的数
前sqrt(n)个数,也就是s里的数,怎么乘都不会超时,任意两个相乘都<=n,
将 b 里的数分成 sqrt(n) 块, 第 i 块 里的数 * i <= n,且这个i是他能乘的最大的数了。
比如考虑第1块,
其实所有b里的数都满足, 乘 1 <= n,
但是有些小一些的数满足乘2 <= n,那它就属于第2块。
可以知道 第 i 块里数的数量 = n / i - n / ( i + 1 );

定义
s[ i ][ j ]代表长度为i的序列,最后一个数是j的方案数,就类似于上面的f[i][j]
b[ i ][ j ]代表长度为i的序列,最后一个数属于第 j 块的方案数
那答案其实就是
r e s = ∑ i = 1 n ( s [ k ] [ i ] + b [ k ] [ i ] ) res = \sum_{i=1}^{\sqrt{n}} (s[k][i]+b[k][i]) res=i=1n (s[k][i]+b[k][i])

状态转移方程:
s [ i ] [ j ] = ∑ r = 1 n s [ i − 1 ] [ r ] + ∑ r = j n s [ i − 1 ] [ r ] s[i][j] = \sum_{r=1}^{\sqrt{n}} s[i-1][r] + \sum_{r=j}^{\sqrt{n}} s[i-1][r] s[i][j]=r=1n s[i1][r]+r=jn s[i1][r]
s里任意两数相乘都满足,b里是第j块及之后的数才能满足
b [ i ] [ j ] = ( n i − n i + 1 ) ∗ ∑ r = 1 j s [ i − 1 ] [ r ] b[i][j] = (\frac{n}{i} - \frac{n}{i+1} ) * \sum_{r=1}^{j} s[i-1][r] b[i][j]=(ini+1n)r=1js[i1][r]
第j块里的个数 * 前j个数

注意:如果n是平方数,则s[i][sqrt(n)]和b[i][sqrt(n)]等价,要去掉一个。

const int N=110,M=4e4+10;
const int mod = 1e9+7;
ll t,n,m;
ll s[N][M],b[N][M];
int main(){
	cin>>n>>m;
	ll tn = sqrt(n);
	s[0][1] = 1;//预处理
	for(int i=1;i<=m;i++){
		for(int j=1;j<=tn;j++){
			s[i-1][j] = (s[i-1][j] + s[i-1][j-1]) % mod;
			b[i-1][j] = (b[i-1][j] + b[i-1][j-1]) % mod;
		}
		for(int j=1;j<=tn;j++){
			s[i][j] = (s[i-1][tn] + ((b[i-1][tn] - b[i-1][j-1]) % mod + mod) % mod) % mod;
			b[i][j] = (n/j - n/(j+1)) * s[i-1][j];
			if(j == n/j) b[i][j] = 0;
		}
	}
	ll res = 0;
	for(int i=1;i<=tn;i++){
		res = (res + (s[m][i] + b[m][i]) % mod) % mod;
	}

	printf("%lld\n", res);
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值