【2021杭电多校第八场】1005 Separated Number(思维)

本文详细解析了一道ACM竞赛中的数字串划分问题,涉及动态规划和组合数学的知识。作者分享了如何通过找规律和利用组合数公式推导解题方法,并提供了关键代码实现。文章强调了正确理解题意的重要性以及优化时间复杂度的策略。

题目链接:

https://acm.hdu.edu.cn/showproblem.php?pid=7060

题目大意:

给一个长度为n的数字串(n <= 10^{6})和(k <= n),表示长度为n的数字串可以被划分成1 - k个数字,求所有划分出来的数字和。

解题思路:

这题在赛场上我读错了两次题,两次都通过找规律把公式算出来了,但是题读错了。第三次读对题之后已经想不动了(读对题很重要!!!!)

其实找规律也能做,但是会很慢(可是我太笨了,不会看贡献

经过题解的指导,我悟了。

(默认首位为0)对于第i位数字s[i],当前的权值位是j,权值位为j的数字有num个,我们易得

ans = \sum_{i=0}^{n-1}\sum_{j=0}^{n-1-i}s[i] * 10^{j} *num[j]

我们不难发现需要把num[i][j]算出来

我们要分两类情况讨论

1、i + j =n

我们可以发现,还可以插入k-1个

num[i][j]=\sum_{m=0}^{k-1}\binom{n-j-1}{m}

2、i + j < n

num[i][j]=\sum_{m=0}^{k-2}\binom{n-j-2}{m}

F(a,b) = \sum_{m=0}^{b}\binom{a}{m} = 2 * F(a-1,b)-\binom{a-1}{b}

(这个转换可以通过组合数的公式化简得到)

\binom{m}{n}=\binom{m-1}{n}+\binom{m-1}{n-1}

但是这个时候我们发现时间复杂度还是不行,因为又有i又有j

我们可以发现对于10^{j}*num[j] 的答案只与j有关

又因为对于i + j < n的情况,有很多种,因此我们考虑用前缀和计算。

具体细节在可以直接看代码

注意:对于公式里的n和n-1可能说的不是很清晰,我的代码都是从0开始,具体以代码为准,理解这个思想就好。

代码实现:

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;

const int N = 1e6 + 3;
const int MOD = 998244353;
int n, k, T;
char s[N];
ll p10[N];
ll fac[N], inv[N];
ll f[N][2];
ll Qpow (ll a, ll b) {
    ll ans = 1ll;
    while (b) {
        if (b & 1)
            ans = ans * a % MOD;
        a = a * a % MOD;
        b >>= 1;
    }
    return ans;
}
void init() {
    fac[0] = 1;
    for (int i = 1; i <= N; i++)
        fac[i] = (ll) fac[i - 1] * i % MOD;
    inv[N] = Qpow (fac[N], MOD - 2);
    for (int i = N - 1; i >= 0; i--)
        inv[i] = (ll) inv[i + 1] * (i + 1) % MOD;
	p10[0] = 1;
	for (int i = 1; i <= N; i++) p10[i] = 10ll * p10[i - 1] % MOD;
}

ll C (int n, int m) {
    if (m > n) return 0;
    return (ll) fac[n] * inv[m] % MOD * inv[n - m] % MOD;
}
void work(){
	ll k;
	scanf("%lld",&k);
	scanf("%s",s);
	int n = strlen(s);int sum = 0;
	f[0][0]= (k >= 2) ? 1 : 0;
	f[0][1] = 1;
	ll ans = 0;
	for (int i = 1; i <= n; i++) {
		f[i][0] = ( (f[i - 1][0] + f[i - 1][0]) % MOD + MOD - C (i - 1, k - 2) ) % MOD;
		f[i][1] = ( (f[i - 1][1] + f[i - 1][1]) % MOD + MOD - C (i - 1, k - 1) ) % MOD;
	}
	for (int i = n - 1; i >= 0; i--) {
		if (i < n - 1)
			sum = (sum + 1ll * p10[n - 2 - i] * f[i][0] % MOD) % MOD;
		ans = (ans + 1ll * (s[i] - '0') * (sum + 1ll * p10[n - 1 - i] * f[i][1] % MOD) % MOD) % MOD;
	}
	printf ("%d\n", ans);

}
int main() {
	init();
	int T;
	scanf("%d",&T);
	while (T--)
		work();
	return 0;
}


在 Flutter 中,`ListView.separated` 是一种用于创建带有分隔符的列表组件。它允许开发者在每个列表项之间插入一个特定的分割线或分割部件,例如 `Divider` 或自定义的小部件。 ### 使用方式 `ListView.separated` 需要两个核心构建器函数: - `itemBuilder`:用于生成列表中的每一个条目。 - `separatorBuilder`:用于生成条目之间的分隔符。 以下是一个基础示例,展示如何使用 `ListView.separated` 来构建一个带有默认分割线的列表: ```dart import 'package:flutter/material.dart'; void main() => runApp(MyApp()); class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( home: Scaffold( appBar: AppBar(title: Text('ListView.separated 示例')), body: ListView.separated( itemCount: 20, itemBuilder: (context, index) { return ListTile( title: Text('条目 $index'), ); }, separatorBuilder: (context, index) { return Divider( height: 1, color: Colors.grey, ); }, ), ), ); } } ``` 在这个例子中,列表显示了从 0 到 19 的索引值,并且每个条目之间都有一个灰色的水平分割线[^3]。 ### 自定义分隔符样式 除了使用默认的 `Divider`,还可以根据需要自定义分隔符的外观和行为。例如,可以改变分割线的颜色、高度或者直接替换为其他小部件(如 `Container`)来实现更复杂的视觉效果。 ```dart separatorBuilder: (context, index) { return Container( height: 2, color: Colors.blue.withOpacity(0.3), ); }, ``` 通过这种方式,可以灵活地控制列表的外观,使其更加符合应用的整体风格[^4]。 ### 动态加载与条件判断 在某些情况下,可能希望根据特定条件动态加载数据或者更改分割线样式。例如,在滚动到底部时加载更数据,并在加载过程中显示进度指示器,此时可以通过 `itemBuilder` 和 `separatorBuilder` 结合逻辑判断来实现。 ```dart ListView.separated( itemCount: _dataList.length, itemBuilder: (context, index) { if (_dataList[index] == "LOADING") { return Center( child: CircularProgressIndicator(), ); } return ListTile( title: Text('数据 $index'), ); }, separatorBuilder: (context, index) { if (_dataList[index] == "LOADING") { return SizedBox.shrink(); // 不显示分割线 } return Divider(height: 1); }, ) ``` 上述代码片段展示了如何在遇到特定标记(如 `"LOADING"`)时显示进度指示器,并跳过绘制该位置的分割线[^4]。 ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值