前言
好久之前打的这场比赛,刚刚打算补上题解,赛中由于读错D题题意导致崩盘,本来前期非常顺利但是D卡了一个半小时才知道题意读错了,赛后补过了DEF。
lajiyuan
r
a
t
i
n
g
−
=
32
rating-=32
rating−=32 2002->1970
A. Got Any Grapes?
题意
【模拟题】略过
做法
【模拟题】略过
代码
#include<stdio.h>
int main()
{
int x,y,z,a,b,c;
scanf("%d%d%d%d%d%d",&x,&y,&z,&a,&b,&c);
if(a>=x&&a+b>=x+y&&a+b+c>=x+y+z) puts("YES");
else puts("NO");
return 0;
}
B. Yet Another Array Partitioning Task
题意
给你一个长度为n的数组,要求分成k段,每段至少m个元素,求让每段的前m大之和的和最大的切割方案。
2
≤
n
≤
2
∗
1
0
5
,
1
≤
m
,
2
≤
k
,
m
∗
k
≤
n
2 \leq n \leq 2*10^5,1 \leq m,2 \leq k,m*k \leq n
2≤n≤2∗105,1≤m,2≤k,m∗k≤n
做法
我们换一种角度去思考这个问题,最大的答案一定是原数组的最大的km个元素相加,所以我们只要让这km个数分别是每一段的前m大即可,所以我们只需要扫出m个这其中的数分为一段即可,这样每一段的前m大都是这k*m个数中的一个。
代码
#include<stdio.h>
#include<iostream>
#include<algorithm>
#include<string.h>
using namespace std;
typedef long long ll;
const int maxn = 2e5+10;
struct data
{
int pos;
int val;
}x[maxn];
int vis[maxn];
bool cmp(const data &a,const data &b)
{
if(a.val==b.val) return a.pos<b.pos;
return a.val>b.val;
}
int main()
{
int n,k,m;
scanf("%d%d%d",&n,&m,&k);
for(int i=1;i<=n;i++)
{
scanf("%d",&x[i].val);
x[i].pos=i;
}
sort(x+1,x+1+n,cmp);
ll ans=0;
for(int i=1;i<=k*m;i++)
{
vis[x[i].pos]=1;
ans+=x[i].val;
}
printf("%lld\n",ans);
int cnt=0;
int cc=0;
for(int i=1;i<=n;i++)
{
if(vis[i])
{
++cnt;
if(cnt==m)
{
printf("%d ",i);
cnt=0;
cc++;
if(cc==k-1) return 0;
}
}
}
return 0;
}
C. Trailing Loves (or L’oeufs?)
题意
求
n
!
n!
n!在b进制下末尾有多少个0
1
≤
10
≤
1
0
18
,
2
≤
b
≤
1
0
12
1 \leq 10 \leq 10^{18},2 \leq b \leq 10^{12}
1≤10≤1018,2≤b≤1012
做法
这道题我门首先考虑10进制,那就是看n!能够整除5多少次和整除2多少次。取个min即是答案,这道题做法类似,我们只需要先对b进行分解质因数,对每个质因数统计在b中出现次数cnt和在n!中出现次数m,最后对所有的m/cnt取min即可
代码
#include<stdio.h>
#include<iostream>
#include<algorithm>
using namespace std;
typedef long long ll;
const ll LL_INF = 0x3f3f3f3f3f3f3f3f;
ll solve(ll a,ll b)
{
if(a==0) return 0;
return a/b+solve(a/b,b);
}
int main()
{
ll a,b;
scanf("%lld%lld",&a,&b);
ll ans=LL_INF;
for(ll i=2;i*i<=b;i++)
{
if(b%i==0)
{
int cnt=0;
while(b%i==0)
{
cnt++;
b/=i;
}
ans=min(ans,solve(a,i)/cnt);
}
}
if(b>1) ans=min(ans,solve(a,b));
printf("%lld\n",ans);
return 0;
}
D. Flood Fill
题意
给你一个颜色序列,每个位置有一个颜色,选择一个起始位置,每次可以改变包含这个位置的颜色段,将这个颜色段修改为任意一个颜色, 问最少操作多少次。
1
≤
n
≤
5000
1 \leq n \leq 5000
1≤n≤5000
做法
由于要求每次操作都要包含起始段,所以每次操作都应该是连续的,也就是如果我们现在改变
(
l
,
r
)
,
那
么
(
l
,
r
−
1
)
,
(
l
+
1
,
r
)
,
(
l
+
1
,
r
−
1
)
(l,r),那么(l,r-1),(l+1,r),(l+1,r-1)
(l,r),那么(l,r−1),(l+1,r),(l+1,r−1)一定已经改变完成,所以直接
d
p
dp
dp即可,
d
p
[
l
]
[
r
]
dp[l][r]
dp[l][r]表示将l-r这个区间变为同一种颜色的最小代价。
当l的颜色与r的颜色相同:
d
p
[
l
]
[
r
]
=
d
p
[
l
+
1
]
[
r
−
1
]
+
1
dp[l][r]=dp[l+1][r-1]+1
dp[l][r]=dp[l+1][r−1]+1
当l的颜色与r的颜色不同:
d
p
[
l
]
[
r
]
=
m
i
n
(
d
p
[
l
+
1
]
[
r
]
,
d
p
[
l
]
[
r
−
1
]
)
+
1
dp[l][r]=min(dp[l+1][r],dp[l][r-1])+1
dp[l][r]=min(dp[l+1][r],dp[l][r−1])+1
代码
#include<stdio.h>
#include<iostream>
#include<algorithm>
#include<string.h>
using namespace std;
const int maxn = 5005;
const int INF = 0x3f3f3f3f;
int dp[maxn][maxn];
int a[maxn];
int b[maxn];
int dfs(int l,int r)
{
if(dp[l][r]!=INF) return dp[l][r];
if(l==r) dp[l][r]=0;
else if(l+1==r) dp[l][r]=1;
else if(b[l]==b[r]) dp[l][r]=dfs(l+1,r-1)+1;
else dp[l][r]=min(dfs(l,r-1),dfs(l+1,r))+1;
return dp[l][r];
}
int main()
{
int n;
scanf("%d",&n);
for(int i=1;i<=n;i++) scanf("%d",&a[i]);
memset(dp,INF,sizeof(dp));
int cnt=0;
for(int i=1;i<=n;i++) if(a[i]!=a[i-1]) b[++cnt]=a[i];
printf("%d\n",dfs(1,cnt));
return 0;
}
E. Arithmetic Progression
题意
现在有一个长度为n的打乱的等差数列,你只知道长度,并且可以提出60个问题,让你确定这个等差序列的首项和公差。问题的格式分为两种:
1:询问等差序列中是否有大于x的数
2:询问等差序列中第i个数是什么
1
≤
a
i
≤
1
0
9
1 \leq a_i \leq 10^9
1≤ai≤109
做法
看到这个60,我们肯定知道要想log的算法,由于每个数都小于 1 0 9 10^9 109,我们只需要30次就可以算出这个序列的末项是什么。之后的30次提问,我们只需要随机的获取30次下标的值,之后排序取两两相邻差的gcd即可。这个正确性的证明可以看官方题解, 错 误 率 < 1 0 − 9 错误率<10^{-9} 错误率<10−9
代码
#include<stdio.h>
#include<iostream>
#include<algorithm>
#include<time.h>
using namespace std;
bool ask(int mid)
{
printf("> %d\n",mid);
fflush(stdout);
int x;
scanf("%d",&x);
return x;
}
std::mt19937 rnd(time(0));
int gcd_(int a, int b)
{
return b ==0? a : gcd_(b, a % b);
}
int main()
{
int n;
scanf("%d",&n);
int l=0,r=1000000000,mid;
while(l<=r)
{
mid=(l+r)/2;
if(ask(mid)) l=mid+1;
else r=mid-1;
}
vector<int> v;
for(int i=1;i<=30;i++)
{
int tmp=(rnd()%n)+1;
printf("? %d\n",tmp);
fflush(stdout);
int x;
scanf("%d",&x);
v.push_back(x);
}
sort(v.begin(),v.end());
int gcd=0;
for(int i=1;i<v.size();i++)
{
gcd=gcd_(gcd,v[i]-v[i-1]);
}
printf("! %d %d\n",r+1-gcd*(n-1),gcd);
fflush(stdout);
return 0;
}
F. Please, another Queries on Array?
题意
给你一个长度为n的序列,序列中第i个数为
a
i
a_i
ai,
1
≤
a
i
≤
300
1 \leq a_i \leq 300
1≤ai≤300
之后有q次操作,每次操作有两种形式
1:区间乘x,
1
≤
x
≤
300
1 \leq x \leq 300
1≤x≤300
2: 询问区间乘积的欧拉函数。
1
≤
n
≤
4
∗
1
0
5
1 \leq n \leq 4*10^5
1≤n≤4∗105
1
≤
q
≤
2
∗
1
0
5
1 \leq q \leq 2*10^5
1≤q≤2∗105
做法
这道题得突破点在于这个300,首先我们要知道欧拉函数的一种求法是
φ
(
x
)
=
x
Π
n
i
=
1
(
1
−
1
p
i
)
\varphi \left( x \right) =x\underset{i=1}{\overset{n}{\varPi}}\left( 1-\frac{1}{p_i} \right)
φ(x)=xi=1Πn(1−pi1)
p
i
p_i
pi表示x的每一个质因子,相同的质因子只计算一次。
那么也就是我们如果知道这个区间的乘积以及这个区间包含哪些质因子即可,
区间乘积可以通过维护简单的区间乘计算出来,由于每个数小于
300
300
300,我们发现小于
300
300
300的质因子只有
62
62
62个,正好可以用一个
l
o
n
g
l
o
n
g
longlong
longlong表示,
1
1
1表示这个质因子出现过,
0
0
0表示没有出现过,线段树再维护一个区间质因子出现情况即可,下推标记时用或运算即可。
这样查询得复杂度是
l
o
g
n
logn
logn,修改的复杂度也是
l
o
g
n
logn
logn,所以总体上复杂度就是
O
(
q
l
o
g
n
)
O(qlogn)
O(qlogn)
代码
#include<stdio.h>
#include<iostream>
#include<algorithm>
using namespace std;
typedef long long ll;
const int maxn = 4e5+5;
const int Mod=1000000007;
ll a[maxn];
int prime[maxn];
int flag[maxn+5];
int tot;
ll pp[maxn];
void init()
{
for(int i=2;i<=maxn;i++)
if(!flag[i])
{
prime[++tot]=i;
for(int j=i+i;j<=maxn;j+=i)
flag[j]=1;
}
}
vector<int> v;
ll pow_(ll a,ll b)
{
ll ans=1;
while(b)
{
if(b&1) ans=ans*a%Mod;
b>>=1;
a=a*a%Mod;
}
return ans;
}
ll inv_(int x)
{
return pow_(x,Mod-2);
}
struct T
{
int l,r,mid;
ll mul,pri,val,tt;
}tree[maxn<<2];
void push_up(int rt)
{
tree[rt].val=tree[rt<<1].val*tree[rt<<1|1].val%Mod;
tree[rt].pri=tree[rt<<1].pri|tree[rt<<1|1].pri;
}
void push_down(int rt)
{
if(tree[rt].mul!=1)
{
ll tmp=tree[rt].mul;
tree[rt<<1].val=tree[rt<<1].val*pow_(tmp,tree[rt<<1].r-tree[rt<<1].l+1)%Mod;
tree[rt<<1].mul=tree[rt<<1].mul*tmp%Mod;
tree[rt<<1|1].val=tree[rt<<1|1].val*pow_(tmp,tree[rt<<1|1].r-tree[rt<<1|1].l+1)%Mod;
tree[rt<<1|1].mul=tree[rt<<1|1].mul*tmp%Mod;
tree[rt].mul=1;
}
if(tree[rt].tt!=0)
{
ll tmp=tree[rt].tt;
tree[rt<<1].pri=tree[rt<<1].pri|tmp;
tree[rt<<1|1].pri=tree[rt<<1|1].pri|tmp;
tree[rt<<1].tt|=tmp;
tree[rt<<1|1].tt|=tmp;
tree[rt].tt=0;
}
}
void build(int rt,int l,int r)
{
tree[rt].l=l;
tree[rt].r=r;
tree[rt].mul=1;
tree[rt].tt=0;
if(l==r)
{
tree[rt].val=a[l];
tree[rt].pri=pp[l];
return ;
}
int mid=tree[rt].mid=l+r>>1;
build(rt<<1,l,mid);
build(rt<<1|1,mid+1,r);
push_up(rt);
}
void update(int rt,int l,int r,int x,ll pri)
{
if(tree[rt].l>r||tree[rt].r<l) return ;
if(tree[rt].l>=l&&tree[rt].r<=r)
{
tree[rt].val=tree[rt].val*pow_(x,tree[rt].r-tree[rt].l+1)%Mod;
tree[rt].mul=tree[rt].mul*x%Mod;
tree[rt].tt|=pri;
tree[rt].pri|=pri;
return ;
}
push_down(rt);
if(l<=tree[rt].mid) update(rt<<1,l,r,x,pri);
if(r>tree[rt].mid) update(rt<<1|1,l,r,x,pri);
push_up(rt);
return ;
}
ll query(int rt,int l,int r,ll &pri)
{
if(tree[rt].l>r||tree[rt].r<l) return 1;
if(tree[rt].l>=l&&tree[rt].r<=r)
{
pri=pri|tree[rt].pri;
return tree[rt].val;
}
push_down(rt);
ll ans=1;
if(tree[rt].mid>=l) ans=ans*query(rt<<1,l,r,pri)%Mod;
if(tree[rt].mid<r) ans=ans*query(rt<<1|1,l,r,pri)%Mod;
push_up(rt);
return ans;
}
char Q[20];
int main()
{
init();
for(int i=1;i<=tot;i++) if(prime[i]<=300) v.push_back(prime[i]);
int n,q;
scanf("%d%d",&n,&q);
for(int i=1;i<=n;i++) scanf("%lld",&a[i]);
for(int i=1;i<=n;i++)
{
ll tt=0;
for(int j=0;j<v.size();j++) if(a[i]%v[j]==0) tt=tt|(1LL<<j);
pp[i]=tt;
}
build(1,1,n);
while(q--)
{
int l,r,x;
scanf("%s",Q);
if(Q[0]=='M')
{
scanf("%d%d%d",&l,&r,&x);
ll tt=0;
for(int i=0;i<v.size();i++) if(x%v[i]==0) tt=tt|(1LL<<i);
update(1,l,r,x,tt);
}
else
{
ll tt=0;
scanf("%d%d",&l,&r);
ll ans=query(1,l,r,tt);
for(int i=0;i<v.size();i++)
{
if(tt&(1LL<<i))
{
ans=ans*inv_(v[i])%Mod;
ans=ans*(v[i]-1)%Mod;
}
}
printf("%lld\n",ans);
}
}
//cout << "time: " << (long long)clock() * 1000 / CLOCKS_PER_SEC << " ms" << endl;
return 0;
}