A. Got Any Grapes?
题意:有3种葡萄,第一种有a个,第二种有b个,第三种有c个,现在有三个人,第一个人要吃x个,但是只能吃第一种,第二个人要吃y个,但是只能吃第一种和第二种,第三个人要吃z个,他什么都能吃,问能满足所有人的需要吗?
知识点:贪心
思路:我们先让第一个人吃,再让第二个人吃,最后全给第三人,如果过程中都满足了,才能满足所有人的需要。
代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int mod=1e9+7;
const int N=1e6+5;
void solve(){
vector<int>a(3),b(3);
for(int i=0;i<3;++i)cin>>a[i];//每个人的需要
for(int i=0;i<3;++i)cin>>b[i];//每个葡萄的个数
if(b[0]<a[0])cout<<"NO\n";//如果第一个人都不满足,那一定不满足
else {
b[0]-=a[0];//第一个人吃完
if(b[0]+b[1]<a[1])cout<<"NO\n";//第二个人不满足
else {
if(b[0]>=a[1]){//如果第二个人只吃第一种葡萄就满足了
b[0]-=a[1];
if(b[0]+b[1]+b[2]<a[2])cout<<"NO\n";//第三人不满足
else cout<<"YES\n";
}
else {//第二个人要吃第一种和第二种
a[1]-=b[0];
b[1]-=a[1];
if(b[1]+b[2]<a[2])cout<<"NO\n";//第三人不满足
else cout<<"YES\n";
}
}
}
}
int main(){
ios::sync_with_stdio(false);cin.tie(nullptr);cout.tie(nullptr);
int _=1;//cin>>_;
while(_--){
solve();
}
return 0 ;
}
B. Yet Another Array Partitioning Task
题意:有一个长度为n的数组a,现在要把这个数组划分为k段,然后问你怎么划分,能使划分后,每段的长度大于等于m,并且每段的最大m个数的和加起来最大?
知识点:思维,排序
思路:最后最大的和一定是数组a中最大的m*k个数的和,所以我们先排个序,确定最大的m*k个数在哪里,然后从i=1的位置依次往右移动,每次遇到一个最大的m*k的数中的一个就++,如果个数为m了,就划分当前位置,然后从后面重新开始,最后判一下最后一段就可以了。
代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int mod=1e9+7;
const int N=2e5+5;
bool vis[N];
pair<int,int>a[N];//排序要下标
int b[N];
void solve(){
int n,m,k;cin>>n>>m>>k;
for(int i=1;i<=n;++i){
cin>>b[i];
a[i].first=b[i];
a[i].second=i;
}
sort(a+1,a+n+1);
ll w=0;
for(int i=n-m*k+1;i<=n;++i){
vis[a[i].second]=true;//最大的m*k个数的下标标记
w+=a[i].first;//答案求和
}
cout<<w<<'\n';
int res=0,cnt=0;
for(int i=1;i<=n;++i){
if(vis[i]){
res++;//最大m*k的数的个数++
if(res==m){
cnt++;//划分组数++
if(cnt==k)continue;//如果已经有k组了,就可以退了
cout<<i<<' ';
res=0;//清空
}
}
}
}
int main(){
ios::sync_with_stdio(false);cin.tie(nullptr);cout.tie(nullptr);
int _=1;//cin>>_;
while(_--){
solve();
}
return 0 ;
}
C. Trailing Loves (or L'oeufs?)
题意:给你一个数n,问你在b进制下末尾有多少个0?
知识点:数论,数学
思路:b进制下有末尾有多少个0,就是问整除情况下,最大的w是多少,因为在b进制下,我们可以把一个数看成 ,是每一位前的系数,末尾是0,说明是0,直到一个不是0,说明找完了,也找到了一个对应的,就是末尾0的个数。
对b质因数分解,假设分解后,是第i个质数,是第i个质数的个数,
然后在中找这些质数的个数,具体是这样,是1到n中关于b的第j个质数的个数。(比如找2的个数,先看所以2的倍数,再看4的倍数,再看8的倍数.......)
代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int mod=1e9+7;
const int N=2e5+5;
void solve(){
ll n,b;cin>>n>>b;
vector<pair<ll,int> >p;
for(ll i=2;i*i<=b;++i){//对b质因子分解
if(b%i==0){
int cnt=0;
while(b%i==0)b/=i,cnt++;
p.push_back({i,cnt});//记录质因子和个数
}
}
if(b>1)p.push_back({b,1});
ll ans=1e18;
for(auto [pi,w]:p){
ll res=0;
for(ll j=pi;j<=n;j*=pi){//pi pi^2 pi^3....都看
res+=(n/j);
if(j>=n/pi+1)break;//这里一定要加,不然会有j*pi炸long long的情况
}
ans=min(ans,res/w);//找答案
}
cout<<ans<<'\n';
}
int main(){
ios::sync_with_stdio(false);cin.tie(nullptr);cout.tie(nullptr);
int _=1;//cin>>_;
while(_--){
solve();
}
return 0 ;
}
D. Flood Fill
题意:有一个长度为n的数组,定义对于任意一对,如果对于任意,满足,说明这一段是一个联通块。现在要我们任选一个点开始,每次可以让包含我们开始选的点的情况下,把这个联通块变成任意值,问最小的操作数,让整个数组属于一个联通块。
知识点:DP
思路:很容易想到枚举每一个点作为开始,看当前点开始后的最小操作数,让整个数组属于一个联通块,然后发现,我们一定能在n-1次操作下满足条件,每次啥都不管就硬变。如果要让操作数减少一定会是我们当前左右俩是相同的值,比如这样
黄色框住的是开始位置,现在我们可以变成1,这样俩边的1就是包含主了,操作数就少了1
怎么能让这样的情况最多呢?
比如这种,我们在红色位置,我们把后面的字符反转一下,拼接到前面的下面
这时候,我们选 7 4 3 和 7 5 3 都能让这样的情况最多,这就变成了有两个序列(我们设上面是a序列,下面是b序列),我们要找这两个序列的最长相等子序列,这是一个经典的DP问题,
DP方程是
但是复杂度是的,我们还要枚举起始位置,这样复杂度就是了,还是不可以接受,我们再回看问题,发现我们每次a序列只加一个字符,b序列只减少一个字符,而我们DP式子转移的情况在上一次DP里一定能找到,所以不需要n次DP,只要一次就够了。
代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int mod=1e9+7;
const int N=1e6+5;
int dp[5005][5005];
void solve(){
int n;cin>>n;
vector<int>a,b;
a.push_back(0);//只是为了下标从1开始
for(int i=1;i<=n;++i){
int x;cin>>x;
if(x==a.back())continue;//合并一下联通块
a.push_back(x);
}
b.push_back(0);int l=a.size();
for(int i=l-1;i>=1;--i)b.push_back(a[i]);//b是a的反
int ans=l-2;
for(int i=1;i<l-1;++i){//i对应了a现在加了几个字符了
for(int j=1;j<l-i;++j){//l-i-1是b的字符个数
if(a[i]==b[j])dp[i][j]=dp[i-1][j-1]+1;//DP的转移
else dp[i][j]=max(dp[i-1][j],dp[i][j-1]);
}
ans=min(ans,l-2-dp[i][l-i-1]);//统计答案
}
cout<<ans<<'\n';
}
int main(){
ios::sync_with_stdio(false);cin.tie(nullptr);cout.tie(nullptr);
int _=1;//cin>>_;
while(_--){
solve();
}
return 0 ;
}
E. Arithmetic Progression
题意:现在有一个长度为n的数组a,你有60次查询,每次查询,可以选择以下两种操作
(1)问a数组中是否有严格大于x的数,有返回1,没有返回0
(2)问a数组在x位置的数是多少
要在60次查询内找到m和d。
m和d的定义是,把a数组(假设下标从1开始)按从小到大排序,m=a[1],d=a[2]-a[1]。
知识点:二分,随机化?(不是
思路:对于操作(1),我们可以二分找到最大的数组中的元素,设为R,这个操作最多30次,
然后剩下操作,我们随机的在n中选30个位置,他们之中俩俩的差的绝对值一定满足是d的倍数
所以我们把这些数取个gcd,就大概率是d的值了,然后输出R-(n-1)*d 和d(这个证明我还要再想想,具体是多大的概率不是很清楚,不过跑过100多个点,应该挺高的)
代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int mod=1e9+7;
const int N=1e6+5;
bool check(int x){
cout<<"> "<<x<<endl;
cout.flush();
int ans;
cin>>ans;
return ans;
}
bool vis[N];
void solve(){
int n;cin>>n;
int l=0,r=1e9,mid,R=1e9,cnt=0;
while(l<=r){//二分找到最大的数组中元素
mid=l+r>>1;
cnt++;
if(check(mid))l=mid+1;
else r=mid-1,R=mid;
}
vector<int>a;
//随机化
unsigned seed = std::chrono::system_clock::now().time_since_epoch().count();
mt19937 rand_num(seed);
uniform_int_distribution<long long> q(1,n);
for(int i=1;i<=1000000000;++i){
long long u=q(rand_num);
if(vis[u])continue;
vis[u]=true;
cout<<"? "<<u<<endl;
cout.flush();
int x;cin>>x;
a.push_back(x);
if(a.size()==n||a.size()+cnt==60)break;
}
a.push_back(R);
int now=0,w=a.size();
for(int i=0;i<w;++i){
for(int j=i+1;j<w;++j){
int w=abs(a[i]-a[j]);//两两差的绝对值
now=__gcd(now,w);//取个gcd
}
}
cout<<"! "<<R-(n-1)*now<<' '<<now<<endl;//输出答案
}
int main(){
//ios::sync_with_stdio(false);cin.tie(nullptr);cout.tie(nullptr);
int _=1;//cin>>_;
while(_--){
solve();
}
return 0 ;
}
F. Please, another Queries on Array?
题意:有一个长度为n的数组a,现在有q次操作,
操作1:把区间的每一个都乘x
操作2:询问区间,的值,答案对1e9+7取模
知识点:数论,数据结构,bitset
思路:学过一点数论的话,应该知道是x分解的质因子
发现,我们直接开个bitset每一位代表当前位置这个质数是否在乘积中出现过,然后
区间修改,区间查询,线段树板子题,开bitset是因为空间小,时间也好,不开MLE,然后套套就写完了,线段树主要维护区间乘积和区间中出现的质因子是谁。
代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int mod=1e9+7;
const int N=4e5+5;
#define ls (i<<1)
#define rs ((i<<1)|1)
ll a[N];
bitset<305>w[305];//记录每个数字每个位置的质数是否出现过
struct tre{
bitset<305>p,lazyp;
ll res,mul,l,r;
}tree[N<<2];
ll ksm(ll a,ll b){
ll ans=1;
while(b){
if(b&1)ans=ans*a%mod;
a=a*a%mod;
b>>=1;
}
return ans;
}
//下方类
void work_down(tre &nowtre,ll lazy){
nowtre.res=nowtre.res*ksm(lazy,nowtre.r-nowtre.l+1)%mod;
nowtre.mul=nowtre.mul*lazy%mod;
}
//lazy标记
void work_lazy(ll i){
work_down(tree[ls],tree[i].mul);
tree[ls].p|=tree[i].lazyp;
tree[ls].lazyp|=tree[i].lazyp;
work_down(tree[rs],tree[i].mul);
tree[rs].p|=tree[i].lazyp;
tree[rs].lazyp|=tree[i].lazyp;
tree[i].mul=1;tree[i].lazyp=0;
}
//向上合并
void push_up(tre &nowtre,tre ltre,tre rtre){
nowtre.p=ltre.p|rtre.p;
nowtre.res=ltre.res*rtre.res%mod;
}
//解决俩个区间不能直接合并的情况
tre query(ll l,ll r,ll i,ll L,ll R){
if(L<=l&&r<=R) return tree[i];
work_lazy(i);
ll mid=l+r>>1;
if(mid>=R)return query(l,mid,ls,L,R);
else if(mid<L)return query(mid+1,r,rs,L,R);
else {
tre ltre=query(l,mid,ls,L,R),rtre=query(mid+1,r,rs,L,R),nowtre;
push_up(nowtre,ltre,rtre);
return nowtre;
}
}
//区间修改,带lazy
void change(ll l,ll r,ll i,ll L,ll R,ll d){
if(l>R||r<L)return ;
if(L<=l&&r<=R){
tree[i].p|=w[d];
tree[i].lazyp|=w[d];
tree[i].res=tree[i].res*ksm(d,r-l+1)%mod;
tree[i].mul=tree[i].mul*d%mod;
return ;
}
work_lazy(i);
ll mid=l+r>>1;
change(l,mid,ls,L,R,d);
change(mid+1,r,rs,L,R,d);
push_up(tree[i],tree[ls],tree[rs]);
}
//建树
void build(ll l,ll r,ll i){
tree[i].l=l;tree[i].r=r;
tree[i].mul=1;
if(l==r){
tree[i].p=w[a[l]];
tree[i].res=a[l];
return ;
}
ll mid=l+r>>1;
build(l,mid,ls);
build(mid+1,r,rs);
push_up(tree[i],tree[ls],tree[rs]);
}
ll inv[305];
void solve(){
inv[1]=1;
for(int i=2;i<=300;++i){
inv[i]=(mod-mod/i)*inv[mod%i]%mod;//求个逆元,求欧拉函数用
int tmp=i;
for(int j=2;j*j<=tmp;++j){
if(tmp%j==0){
w[i][j]=1;
while(tmp%j==0)tmp/=j;
}
}
if(tmp>1)w[i][tmp]=1;
}
int n,q;cin>>n>>q;
for(int i=1;i<=n;++i)cin>>a[i];
build(1,n,1);
while(q--){
int l,r,x;
string s;cin>>s;
if(s[0]=='M'){
cin>>l>>r>>x;
change(1,n,1,l,r,x);
}
else {
cin>>l>>r;
tre ans=query(1,n,1,l,r);
ll res=ans.res;//欧拉函数的求法
for(int i=2;i<=300;++i){
if(ans.p[i])res=res*(i-1)%mod*inv[i]%mod;//定义
}
cout<<res<<'\n';
}
}
}
int main(){
ios::sync_with_stdio(false);cin.tie(nullptr);cout.tie(nullptr);
int _=1;//cin>>_;
while(_--){
solve();
}
return 0 ;
}
线段树合并一个是按位|,因为只要孩子的俩区间只要出现过,父亲就是有,区间乘积就更简单了,一个快速幂看区间乘了几次就行了。