题解:AT_arc129_c [ARC129C] Multiple of 7
题目大意
给定一个整数 1 到 9 组成的字符串
- 取出
s 的第l 个到第r 个字符组成的子串,将其视为一个十进制整数时,该数能被7 整除。
题目保证一定有解。
分析
转化思路:子串整除 7 \Leftrightarrow 后缀模值相等
假设一个合法构造的字符串为
及其模
区间
由于
因此问题转化为:构造一个长度为
统计思路
假设某个余数值
所有不同余数贡献的区间数之和即为总合法区间数:
于是,问题进一步转化为:将
构造方法
我们该如何构造
贪心拆分
为了使总长度尽可能短,每次应选取尽可能大的
贪心一定有效, 因为组合数
例:
填充模值
得到各段长度后,我们构造序列
为了让同一段内的元素具有相同的余数值(从而使该段内任意两个位置产生
由于段数
最后在末尾补上
还原数字串 a
得到后缀模值序列
移项:
设
利用乘法逆元:
在模
代码实现
由于只有
注意还原时需从右向左遍历(
复杂度分析
- 贪心拆分:每次
N 至少减小一半,迭代次数\mathcal O(\log N) 。 - 构造
s 与还原a :只要扫描一遍,总复杂度\mathcal O(L) 。 - 由于
L \le 10^6 ,且常数很小,足以在时限内通过。
参考代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
string ans="";
int s[1000005];
vector<int> lens;
int n,l,pos,pos,cur=1,;
signed main(){
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
cin>>n;
while(n>0){
int len = 2;
while(len*(len-1)/2<=n) len++;
len--;
lens.push_back(len);
n -= len*(len-1)/2;
}
for (int i=0;i<lens[i];i++) l += lens[i];
for (int i=0;i<lens.size();i++){
for (int j=0;j<lens[i];j++) s[pos++] = cur;
cur++;
}
s[l]=0;
for (int i=l-1;i>=0;i--){
int t=(s[i]-s[i+1]+7)%7,pow10=1,exp=l-1-i;
for (int k=0;k<exp;k++) pow10=(pow10*10)%7;
int val;
for (int d=0;d<7;d++) {
if ((d*pow10)%7==t) {
val = d;
break;
}
}
if (val==0) val = 7;
ans.push_back('0'+val);
}
reverse(ans.begin(),ans.end());//我们是从右往左构造,需要反转。
cout<<ans;
return 0;
}