文章目录
A 九小时九个人九扇门
题意:给定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 <bits/stdc++.h>
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;
}
B 炸鸡块君与FIFA22
#include<bits/stdc++.h>
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;
}
C Baby’s first attempt on CPU
pass
D 牛牛做数论
问题一:由于𝜙 𝑥 = 𝑥 ∗ ς(1 − 1/𝑝𝑖),之中𝑝𝑖取遍𝑥的所有种类的质因 子(注意是种类而不是个数,如素因子2出现两次在公式里也只乘上一次),则𝐻 𝑥 = ς(1 − 1/𝑝𝑖),显然乘的项数越多、每一项越小𝐻(𝑥)就越小,而按照2,3,5,7,11 … …的顺序取𝑝𝑖就可以达到这一目标。
问题二:对于素数𝑝有𝜙 (𝑝) = 𝑝 − 1,因此𝐻 (𝑝) = (p-1)/p,直觉可以感到
这是挺大一数(毕竟𝐻(𝑥)最大不超过1),所以可以猜出取最大的素数𝑝即可
#include <bits/stdc++.h>
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;
}
E 炸鸡块君的高中回忆
签到
F 中位数切分
给定一个长为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;
}
G ACM is all you need
不会
H 牛牛看云
∑ 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
- 自身 + 握手原则(n + n * (n - 1) / 2)
-
i != j
- 直接相乘(cnt[i] * cnt[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;
}
I B站与各唱各的
题意:n个人,m句话,每人都可以选择唱与不唱。某句话都没人唱,或都唱了算这句话唱失败,否则是唱成功。求成功唱出的句子数尽可能多的数量。
思路:
• 首先注意到句子与句子之间没有办法互相影响,因此答案是一句话的期望乘以𝑚;
• 由于无法交流,每个人在唱每句时唯一的策略就是随机以𝑝𝑖的概率决定唱或不唱这一句
• 又由于𝑛个人之间没有区别,所以不同人的概率一定是相等的,记为𝑝;
• 因此,唱失败的概率是𝑝^𝑛 + (1 − 𝑝)^𝑛,求其最小值得𝑝 = 1/2,
答案:
m
×
(
1
−
1
2
n
−
1
)
{m×(1-\frac{1}{2^{n-1}})}
m×(1−2n−11)
#include <bits/stdc++.h>
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;
}
J 小朋友做游戏
a个安静小朋友,b个不安分小朋友,选出n个围成圈。规定两个不安分的不能手拉手。每个小朋友有幸福值,求出最大值。
思路:
1. 先将两种小朋友的幸福度分别按从大到小排序,记为A和B数组
2. 那么最优的方案一定是从A和B中各选一个前缀
3. 贪
#include<bits/stdc++.h>
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;
}
}