1017div4(D~H)
Codeforces Round 1017 (Div. 4) (D~H)
ABC被我吃掉了
D. Tung Tung Sahur
一个鼓有两面left和right,每一面的响声不同,分别是L和R,且敲打一次可能发出1次或2次声响
现给出敲打的顺序和给定一串声响,问按这个顺序敲打是否能发出给定的声响(例如:LLR和LLLR合法,但LR和LLLR不合法)
可以看出一个L可以对应1-2个L声响,遇到连着的如LLL,其对应的声响为L*3到L*6,所以由此可以想到:
将顺序中连续的L、R和声响中连续的L、R对应,如果对应不上则NO;设顺序中一段L的数量为cntL,则对应的这段声响的L数量应该在cntL与cntL*2之间,否则也NO
#include<bits/stdc++.h>
using namespace std;
int T;
string str_a;
string str_A;
vector<int>a;
vector<int>A;
int main()
{
scanf("%d\n",&T);
while(T--) {
a.clear();
A.clear();
getline(cin,str_a);
getline(cin,str_A);
//cout<<str_a<<"\n"<<str_A<<endl;
if(str_a[0]!=str_A[0]) {
printf("NO\n");
continue;
}
char last=0;
for(auto var : str_a) {
if(var==last) {
a.back()++;
} else {
last=var;
a.emplace_back(1);
}
}
last=0;
for(auto var : str_A) {
if(var==last) {
A.back()++;
} else {
last=var;
A.emplace_back(1);
}
}
if(A.size()!=a.size()) {
printf("NO\n");
continue;
}
int flag=0;
for(int i=0;i<a.size();i++) {
//printf("%d %d\n",a[i],A[i]);
if(a[i]>A[i]||a[i]*2<A[i]) {
flag=1;
break;
}
}
if(flag) printf("NO\n");
else printf("YES\n");
}
return 0;
}
E. Boneca Ambalabu
给出一个数列a,求所有 1≤k≤n 中\((a_k⊕a_1)+(a_k⊕a_2)+…+(a_k⊕a_n)\)的最大值
显然不能直接枚举k再一个个求异或和,不过我们可以发现,题目是由每个 \(a_i\) 异或某一个 \(a_k\) ,\(a_k\)不固定但\(a_i\)固定,所以很容易想到对异或之和的计算进行一些处理。
我们可以发现如果 \(a_k\) 的某一位是0/1,在之后的计算中可以直接用这一位和所有 \(a_i\) 的这一位进行异或,根据所有 \(a_i\) 都是固定的这个性质,我们可以提前存好所有 \(a_i\) 在每一个二进制位数下的0/1的个数,当得知 \(a_k\) 在某一位是0/1后就能直接计算出这一位的异或之和,就能在\(O(log\ a_{max})\)的时间内算出异或之和,在这个基础上枚举k取最大值,复杂度\(O(nlog\ a_{max})\)
注意由于要求和,会爆int
#include<bits/stdc++.h>
#define int long long
using namespace std;
int T;
int n;
int a[200100];
int b[2001000];
int cnt[1000];
signed main()
{
scanf("%lld",&T);
while(T--) {
int topbit=0;
int maxn=0;
scanf("%lld",&n);
for(int i=1;i<=n;i++) {
scanf("%lld",&a[i]);
b[i]=a[i];
}
for(int i=1;i<=n;i++) {
for(int j=1;b[i];j++) {
cnt[j]+=b[i]%2;
b[i]/=2;
topbit=max(topbit,j);
}
}
/*for(int i=1;i<=n;i++) {
for(int j=1;j<=topbit;j++) {
if((a[i]>>(j-1))%2) printf("%6d(1) ",((n-cnt[j])<<(j-1)));
else printf("%6d(0)",cnt[j]<<(j-1));
}
printf("\n");
}*/
for(int i=1;i<=n;i++) {
int sum=0;
for(int j=1;j<=topbit;j++) {
if((a[i]>>(j-1))%2) sum+=((n-cnt[j])<<(j-1));
else sum+=(cnt[j]<<(j-1));
}
maxn=max(maxn,sum);
//printf("SUM=%d\n",sum);
}
printf("%lld\n",maxn);
for(int i=0;i<1000;i++) {
cnt[i]=0;
}
}
return 0;
}
/*
第i位之和为X:则a_j
1. a_j_i=1 : sum(a_j_i^a_x_i)=n-1-(X-1)=n-X;
2. .....=0 : sum(...........)=X;
注:1. 第i位可以直接用 (x>>(i-1))%2 表示
2. 即使a_i<=2^30,sum仍可能爆int
00001
00010
00100
01000
10000
*/
F. Trulimero Trulicina
题目让我们构造一个 n*m 的由数字构成的矩形,要求:
- 由1 和 k 之间的数组成
- 1 到 k 的每个整数出现的次数相同
- 相邻两个单元格中的整数不同
显然,m<k时,复读1到k的数即可
m==k时,会出现{123456, 123456, 123456}这种情况,我们每隔一行把它往后挪一格,变成{123456, 234561, 123456}即可
m>k时,看似也是复读1到k的数,但这里有个陷阱:
12341234
12341234
当m%k0时直接复读会重复,所以我们可以像mk时那样,每隔一行往后挪一格即可:
12341234
23412341
(挪的方向不甚重要)
#include<bits/stdc++.h>
using namespace std;
int T;
int n,m,k;
int a[200100];
signed main()
{
scanf("%d",&T);
while(T--) {
scanf("%d%d%d",&n,&m,&k);
if(m%k==0) {//包含了m==k
for(int i=1;i<=n;i++) {
if(i%2==0) {
for(int j=0;j<m;j++) {
printf("%d ",j%k+1);
}
printf("\n");
} else {
for(int j=1;j<=m;j++) {
printf("%d ",j%k+1);
}
printf("\n");
}
}
} else if(m>k) {
int cnt=0;
for(int i=1;i<=n;i++) {
for(int j=1;j<=m;j++) {
printf("%d ",cnt%k+1);
cnt++;
}
printf("\n");
}
} else if(m<k) {
int cnt=0;
for(int i=1;i<=n;i++) {
for(int j=1;j<=m;j++) {
printf("%d ",cnt%k+1);
cnt++;
}
printf("\n");
}
}
}
return 0;
}
/*
1. m>k
1234561
23456
2. m<k
12345
61234
56
3. m==k
123456
234561
*/
G. Chimpanzini Bananini
给出一个长度为 m 的数组 b,定义粗糙度为\(∑^m_{i=1}b_i⋅i=b_1⋅1+b_2⋅2+b_3⋅3+…+b_m⋅m\),现在我们要进行q次操作,操作有 3 种,分别是:
1. 将\(b_m\) 删除,转而插入到 \(b_1\) 的前面变成新的 \(b_1\)
2. 将数组 b 倒转,即: 将 \(b_i\)和\(b_{m-i+1}\) 交换
3. 在数组的末尾插入一个元素 k
在每进行一种操作后,输出当前的粗糙度
和重庆市赛的L在解题的形式上有几分相似(L更简单一些),都是将操作后的数组和ans分开计算,各优化各的
整体的思路是模拟,然后用一些数学规律去优化粗糙度的计算(以下直接管粗糙度叫ans了)
对于操作1:
存储:使用deque存数组b,该操作\(O(1)\)
ans:可以等效为所有的 \(b_i\) 都后移一位:\(ans+=sum(b)\),然后单独计算 \(b_m\):\(ans-=b_m * m\);其中sum(b) 表示数组b所有元素之和,能预处理
对于操作2:
存储:使用一个flag标记,flag为0表示数组是正向的,为1表示数组反向了,进行操作1和3时都要先检查flag
ans:可以观察到正向的ans+反向的ans等于\(b_1⋅(m+1)+b_2⋅(m+1)+b_3⋅(m+1)+…+b_m⋅(m+1)\),即ans=sum(b)*(m+1)-ans
对于操作3:
存储:deque真是太好用辣!
ans:ans+=k*(m+1),然后注意要更新m和sum(b)
解题的关键是操作2和操作1的ans计算的优化,比较次要的还有操作2的存储的方式,核心还是找数学规律
(以及deque确实好用)
#include<bits/stdc++.h>
#define int long long
using namespace std;
int T,opt,n;
deque<int>que;
signed main()
{
int x;
scanf("%lld",&T);
while(T--) {
int flag=0;
int ans=0;
int sum=0;
int sum_plus=0,size=0;
scanf("%lld",&n);
for(int i=1;i<=n;i++) {
scanf("%lld",&opt);
if(opt==1) {//迁移
ans+=sum;
if(flag==0) {
//printf("ans=%lld %lld %lld %lld %lld\n",ans,flag,sum,que.back(),que.front());
ans-=(size)*que.back();
que.push_front(que.back());
que.pop_back();
} else {
ans-=(size)*que.front();
que.push_back(que.front());
que.pop_front();
}
} else if(opt==2) {//reverse
flag==0 ? flag=1 : flag=0;
ans=sum_plus+sum-ans;
} else {//add
size++;
scanf("%lld",&x);
flag==0 ? que.push_back(x) : que.push_front(x);
ans+=x*size;
sum+=x;
sum_plus+=sum+x*(size-1);
}
printf("%lld\n",ans);
}
que.clear();
}
return 0;
}
/*
byd两次把flag写成opt,还把公式写错了(像这种推公式的题尽量先在纸上写清楚吧)
这再次告诉了我们头疼不一定是头的问题可能是脚的问题
以及找性质的重要性(方法论)
*/
H. La Vaca Saturno Saturnita
有点超出我的能力范围了,之前做的时候看了题解懂了,隔了两周补题时想了很久,看了之前的代码才想出来(上次div3的最后一题不看题解还能想个七七八八,这个是一点思路没有)
给出一个数组 a,进行q次操作,每次操作在数组 a 的[ l , r ]的下标内对k进行以下操作
function f(k, a, l, r):
ans := 0
for i from l to r (inclusive):
while k is divisible by a[i]:
k := k/a[i]
ans := ans + k
return ans
要求在每次操作执行结束后,输出ans
模拟的做法是\(O(n^2)\),其中 q 次操作的 n 是省不了的,所以显然得从优化每次操作的复杂度入手
每次操作实际是去除 k 的因子中值为 \(a_l\) 到 \(a_r\) 的因子,并按顺序累加这个过程中 k 的值,不难发现有以下性质:
-
如果在 l 到 r 中有某一段[ L, R ],\(a_i\) 均不是 k 的因数,那么k不会更新,我们可以直接跳过这一段并累加 k * (R - L+1)
-
如果一个因数在前面出现过,那么在后面再次出现的时候是不生效的,因为 k 已经去除了这个因数。更进一步,这个因数的倍数在后面出现时也不会生效(不过这题用不到这个性质)。结合上一点我们只需要找到 l 到 r 中 k 的每个因数第一次出现的位置,只在这些因数位置更新 k 并累加,然后快进到下一个因数节点并累加中间的 k
关于寻找 l 到 r 中 k 的每个因数第一次出现的位置,我的实现方法是先把数组 a 预处理,用二维 vector 把每个\(a_i\)与其位置装进去( i 装数值,j 装位置),由于从左往右遍历,所以 vec[i][j] 中 vec[i] 的每个元素是递增的(位置),因此我们可以先枚举 k 的每个因数(枚举 i <= sqrt(k) 然后取 i 和 k/i 两个因子),用二分找到其在 [ l , r ] 第一次出现的位置并存下来,将这些位置排序后就能执行性质二中的步骤了(排序是因为当前的位置序列是由枚举 k 的因数产生的,是按因数的大小排序的,而我们需要的是其在数组 a 中的位置排序)
此外,关于上一步的实现,一开始想过直接用multimap找第一次出现的位置,但multimap只能lower_bound因数在1-n第一次出现的位置,没法精确到[ l, r ]之内,而写补题的时候又想到了使用map<<pair<int,int>,int> 同时存下因数和位置,然后直接lower_bound,不过TLE了,估计map对pair的lower_bound是二分找到pair键的first,然后枚举second,在这题会超时,所以还是得搓二分(你就这么懒吗)
#include<bits/stdc++.h>
#define int long long
using namespace std;
int T,n,q;
int x,l,r,k;
int ans;
map<int,int>order;
vector<vector<int> >vec;
vector<int>emp;
void ser(int j) {
if(vec[j].empty())return;
int L=0,R=vec[j].size()-1,mid;
while(L<R) {
mid=(L+R)/2;
if(vec[j][mid]>=l)R=mid;
else L=mid+1;
}
if(vec[j][R]>=l&&vec[j][R]<=r) order[vec[j][R]]=j;
///else printf("order=%d %d %d %d %d\n",vec[j][R],j,l,r,vec[j].size());
}
signed main()
{
scanf("%lld",&T);
while(T--) {
scanf("%lld%lld",&n,&q);
vec.assign(1e5+1,emp);
for(int i=1;i<=n;i++) {
scanf("%lld",&x);
vec[x].emplace_back(i);
//printf("YES");
}
for(int i=1;i<=q;i++) {
ans=0;
scanf("%lld%lld%lld",&k,&l,&r);
//if(!order.empty())printf("CHECK");
for(int j=2;j*j<=k;j++) {
if(k%j==0) {
ser(j);
ser(k/j);
}
}
ser(k);
int last=l;
//printf("order=%d\n",order.size());
for(auto var : order) {
//printf("ans=%d last=%d chu=%d po=%d\n",ans,last,var.first,var.second);
ans+=(var.first-last)*k;
while(k%var.second==0) k/=var.second;
last=var.first;
//printf("ans=%d k=%d\n",ans,k);
}
ans+=(r-last+1)*k;
printf("%lld\n",ans);
order.clear();
}
vec.clear();
}
return 0;
}
/*
想好映射关系
枚举因子的时候记得自身也是自己的因子
二分条件搞错了,弄清需求
弄清在二分什么,不要搞错下标的区别了
*/

浙公网安备 33010602011771号