2022牛客寒假算法基础集训营1
题意:给定n个数,随便组合,求出数字根分别为0-9的组合个数。
数字根:1、4、9 == 1 + 4 + 9 = 14 == 1 + 4 = 5, 所以数字1、4和9的数字根为5。
思路:dp, 01背包问题
结论性质:一个数的数字根等于这个数对9取模的结果(特别地,取模得
0则数字根为9)
定义:令[i][j]表示考虑了前个数,选择了一些数字使得求和对9取模得的方案
数
#include
using namespace std;
const int N = 100010;
const int mod = 998244353;
int n;
int f[N][10];
int main() {
scanf("%d", &n);
f[0][0] = 1;
for (int i = 1; i <= n + 1; i++) {
int x; scanf("%d", &x); x %= 9;
for (int j = 0; j < 9; j++) {
f[i][(j + x) % 9] = (f[i][(j + x) % 9] + f[i - 1][j]) % mod;
f[i][j] = (f[i][j] + f[i - 1][j]) % mod;
}
}
for (int i = 1; i < 9; i++) {
printf("%d ", f[n][i]);
}
printf("%d\n", f[n][0] - 1);
return 0;
}
线段树解法
#include
using namespace std;
const int M=2e5+5;
int n,q;
char str[M];
struct node{
int val[3];
}tree[M*4];
void Up(node &rt,node &ls,node &rs){
/*
rt根,ls左,rs右(假设两个点)
rt 的0对应的值应该是ls的0对应的值加上,rs的(ls已经有的0值+要模出的是0)互余(3)的值
*/
rt.val[0]=ls.val[0]+rs.val[(0+ls.val[0])%3];
rt.val[1]=ls.val[1]+rs.val[(1+ls.val[1])%3];
rt.val[2]=ls.val[2]+rs.val[(2+ls.val[2])%3];
}
void build(int L,int R,int p){
if(L==R){
if(str[L]=='W')tree[p].val[0]=tree[p].val[1]=tree[p].val[2]=1;
else if(str[L]=='D')tree[p].val[0]=tree[p].val[1]=tree[p].val[2]=0;
else {
tree[p].val[0]=0; // % 3 == 0, 不变
tree[p].val[1]=tree[p].val[2]=-1;
}
return;
}
int mid=L+R>>1;
build(L,mid,p<<1);
build(mid+1,R,p<<1|1);
Up(tree[p],tree[p<<1],tree[p<<1|1]);
}
node query(int l,int r,int a,int L,int R,int p){
if(l == L && R == r) return tree[p];
int mid=L+R>>1;
if(r<=mid)return query(l,r,a,L,mid,p<<1);
else if(mid<l)return query(l,r,a,mid+1,R,p<<1|1);
else {
node rt;
node ls=query(l,mid,a,L,mid,p<<1),rs=query(mid+1,r,a,mid+1,R,p<<1|1);
Up(rt,ls,rs);
return rt;
}
}
int main(){
scanf("%d %d",&n,&q);
scanf("%s",str+1);
build(1,n,1);
for(int i=1;i<=q;i++){
int l,r,s;
scanf("%d %d %d",&l,&r,&s);
if(l>r)printf("%d\n",s);
else{
node ans=query(l,r,s%3,1,n,1);
printf("%d\n",s+ans.val[s%3]);
}
}
return 0;
}
pass
问题一:由于 = ∗ ς(1 − 1/),之中取遍的所有种类的质因 子(注意是种类而不是个数,如素因子2出现两次在公式里也只乘上一次),则 = ς(1 − 1/),显然乘的项数越多、每一项越小()就越小,而按照2,3,5,7,11 … …的顺序取就可以达到这一目标。
问题二:对于素数有 () = − 1,因此 () = (p-1)/p,直觉可以感到
这是挺大一数(毕竟()最大不超过1),所以可以猜出取最大的素数即可
#include
using namespace std;
#define int long long
int prime[] = {2, 3, 5, 7, 11, 13, 17, 19, 23, 29};
int is_primer(int x) {
for (int i = 2; i <= x / i; i++) {
if(x % i == 0) return 0;
}
return 1;
}
int t;
signed main() {
cin >> t;
while(t--) {
int n; cin >> n;
if(n == 1) {
puts("-1");
continue;
}
int res1 = 1ll, res2 = n;
for (int i = 0; ; i++) {
if(res1 * prime[i] > n) break;
res1 *= prime[i];
}
while(is_primer(res2) == 0) res2--;
cout << res1 << ' ' << res2 << endl;
}
return 0;
}
给定一个长为n的数组a和一个整数m,你需要将其切成连续的若干段,使得每一段的中位数都大于等于m,求最多可以划分成多少段。
简单做法:直接找比m大的 - 比m小的
证明二(不严谨):先假设数组里只有≥ 的数字们(想象有一个只
有1个区间,且这些区间都是满足中位数
2的数组),他们都自己作为一个区间,则一共
≥ 的要求的;
含≥ 的数的长为 • 然后考虑插一个< 的数字进去,这个数字需要和两个≥ 的数组组
成一个区间才可以满足中位数要求,因此原来两个区间变成了一个区
间,区间数减一,然后删掉这个数和某个≥ 的数,数组长变为
2 − 1; • (你可能会说这2 − 1 里有一个数其实是在刚才新组成的区间里,
这没问题吗,但这种新组成的区间并不影响上述操作,所以可以看作
一个≥ 的数字)
• 以此类推,可以看出每个< 数字的作用就是说区间数减一
void solve() {
int n, m; cin >> n >> m;
int cnt1 = 0, cnt2 = 0;
rep(i, 0, n) {
int x; cin >> x;
if(x < m) cnt2++;
else cnt1++;
}
if(cnt1 - cnt2 <= 0) cout << -1 << endl;
else cout << cnt1 - cnt2 << endl;
}
不会
∑ i = 1 n ∑ j = i n ∣ a i + a j − 1000 ∣ {\sum_{i =1}^n{\sum_{j=i}^n{|a_i + a_j-1000|}}} i=1∑nj=i∑n∣ai+aj−1000∣
求上述式, ai <= 1000, n <= 1e6
思路:
1. 注意到 ≤ 106而 ≤ 1000,可以看到[]范围很小,或者说极
限数据下会有大量重复的值出现,我们想想怎么利用这一点
2. 记cnt[]表示出现的次数,枚举(,)对儿(共10^6种)
3. 不同情况直接相乘, 特殊处理相同情况
求贡献:
i == j
i != j
void solve() {
cin >> n;
vi cnt(1001);
rep(i, 0, n) {
int x; cin >> x;
cnt[x]++;
}
int ans = 0;
rep(i, 0, 1001) {
rep(j, i, 1001) {
int add;
if(i == j) add = cnt[i] + cnt[i] * (cnt[i] - 1) / 2;
else add = cnt[i] * cnt[j];
ans = ans + add * abs(i + j - 1000);
}
}
cout << ans << endl;
}
题意:n个人,m句话,每人都可以选择唱与不唱。某句话都没人唱,或都唱了算这句话唱失败,否则是唱成功。求成功唱出的句子数尽可能多的数量。
思路:
• 首先注意到句子与句子之间没有办法互相影响,因此答案是一句话的期望乘以;
• 由于无法交流,每个人在唱每句时唯一的策略就是随机以的概率决定唱或不唱这一句
• 又由于个人之间没有区别,所以不同人的概率一定是相等的,记为;
• 因此,唱失败的概率是^ + (1 − )^,求其最小值得 = 1/2,
答案:
m × ( 1 − 1 2 n − 1 ) {m×(1-\frac{1}{2^{n-1}})} m×(1−2n−11)
#include
using namespace std;
typedef long long ll;
const int mod = 1e9 + 7;
ll qmi(ll a, ll k) {
ll res = 1ll;
while(k) {
if(k & 1) res = res * a % mod;
a = a * a % mod;
k >>= 1;
}
return res % mod;
}
int main() {
ll t; cin >> t;
while(t--) {
ll n, m; cin >> n >> m;
int x = qmi(2, n - 1);
cout << m * (1ll + mod - 1ll * qmi(x, mod - 2)) % mod << endl;
}
return 0;
}
a个安静小朋友,b个不安分小朋友,选出n个围成圈。规定两个不安分的不能手拉手。每个小朋友有幸福值,求出最大值。
思路:
1. 先将两种小朋友的幸福度分别按从大到小排序,记为A和B数组
2. 那么最优的方案一定是从A和B中各选一个前缀
3. 贪
#include
using namespace std;
const int N = 1e5 + 10;
int a[N],b[N];
bool cmp(int a,int b) {
return a > b;
}
signed main()
{
int t; cin >> t;
while(t--)
{
long long A , B , n;
cin >> A >> B >> n;
for(int i = 1; i <= A ; i ++) cin>>a[i];
for(int i = 1; i <= B ; i ++) cin>>b[i];
sort(a + 1, a + 1 + A , cmp);
sort(b + 1, b + 1 + B , cmp);
for(int i = 1 ; i <= A ; i ++) a[i] = a[i - 1] + a[i];
for(int i = 1 ; i <= B ; i ++) b[i] = b[i - 1] + b[i];
int ans = -1;
for(int i = 1; i <= A ; i ++)
if(n <= 2 * i && n - i <= B && i <= n)
ans = max(ans , a[i] + b[n - i]);
cout << ans << endl;
}
}