Codeforces Round 882 (Div. 2)
题号:CF1847A~F
A
题意:
给定一个数组
和一个整数 ,记 ,求将数组划分为 个部分的划分方案,使得对每个部分的 之和最小.
题解:
简单题,首先我们注意到,如果将
read(n);read(k);int ans=0;
for(int i=1;i<=n;i++)read(a[i]);
for(int i=1;i<n;i++)b[i]=abs(a[i+1]-a[i]);
sort(b+1,b+n);reverse(b+1,b+n);
for(int i=k;i<n;i++)ans+=b[i];
cout<<ans<<"\n";
B
乔纳森正在与迪奥的吸血鬼手下战斗。其中有
个吸血鬼,它们的强度分别为 。 将
表示由索引 到 的吸血鬼组成的一组。乔纳森意识到每个这样的组的强度取决于它们的最弱环节,即按位与操作。更具体地说,组 的强度等于 。这里, 表示按位与操作。 乔纳森希望能快速击败这些吸血鬼手下,因此他会将吸血鬼分成连续的组,使得每个吸血鬼正好属于一组,并且这些组的强度之和尽量小。在所有可能的分组方式中,他希望找到组数最多的方式。
给定每个吸血鬼的强度,找出在所有可能的分组方式中,拥有最小强度之和的组的最大数量。
题解:
注意到,
read(n);int ans=1;
for(int i=1;i<=n;i++)read(a[i]);
k=(1ll<<33ll)-1ll;int cnt=0;
for(int i=1;i<=n;i++){
k&=a[i];
if(k==0){
k=(1ll<<33ll)-1ll;
++cnt;
}
}
ans=max(ans,cnt);
cnt=0,k=(1ll<<33ll)-1ll;
for(int i=n;i;--i){
k&=a[i];
if(!k){
++cnt;k=(1ll<<33ll)-1ll;
}
}
ans=max(ans,cnt);
cout<<ans<<"\n";
C
DIO 意识到星尘十字军已经知道了他的位置,并且即将要来挑战他。为了挫败他们的计划,DIO 要召唤一些替身来迎战。起初,他召唤了
个替身,第 个替身的战斗力为 。依靠他的能力,他可以进行任意次以下操作:
- 设当前的替身数量为
。 - DIO 选择一个序号
。 - 接着,DIO 召唤一个新的替身,其序号为
,战斗力为 。其中,运算符 表示按位异或。 - 现在,替身总数就变成了
。 但对于 DIO 来说,不幸的是,星尘十字军通过隐者之紫的占卜能力,已经知道了他在召唤替身迎战的事情,而且他们也知道初始的
个替身的战斗力。现在,请你帮他们算一算 DIO 召唤的替身的最大可能战斗力(指单个替身的战斗力,并非所有替身战斗力之和)。
题解:
手玩一下,容易发现本质上
D
<
给出一个长度为
的字符串 ,字符串仅由 0
或1
构成。给出
个区间 ( , ),你需要将字符串 的子段 依次拼接,得到新的字符串 。 你可以对字符串
进行操作,每次操作可以交换任意两个字符的位置,注意操作不是实际改变,不会影响后续的询问。定义对于字符串 , 表示最小的操作次数,使得拼接得到的新字符串 的字典序最大。 然后有
次询问,每次询问给出一个位置 ,表示将原字符串 的 位置取反,注意是实际改变,会影响后续的询问。相应的, 字符串也会发生改变。你需要求出每次询问后, 的值。
题解:
注意到,这个字典序类似于二进制,满足前一个为1比满足后面所有为1更有效。
那么我们可以从优先级的角度考虑问题,优先级怎么求?倒着用线段树做一次区间覆盖即可(也可以正着用并查集做一次区间覆盖
做完区间覆盖后,将所有值按优先级排序(同色的按下标排序)。并且做一个映射使得可以知道原序列每个下标对应的优先级。
然后,我们再考虑维护答案。记当前序列里有
这一步可以使用线段树维护区间和。线段树维护优先级数组,当然,与原来的线段树一起就可以了。(我不嫌烦地开了个树状数组)
然后我们用
注意WA on 13的原因:当
启发:遇到明显具有优先满足性质的问题,可以考虑求出每个数的优先级,然后进行操作。
#define N 505050
int a[N],L[N],R[N];
struct node{
int l,r,lz;
}t[N<<1];
void build(int x,int l,int r){
t[x].l=l,t[x].r=r;
if(l==r)return ;
int mid=l+r>>1;
build(lc,l,mid);
build(rc,mid+1,r);
}
void pushdown(int x){
if(t[x].lz==0||t[x].l==t[x].r)return ;
t[lc].lz=t[rc].lz=t[x].lz;t[x].lz=0;
}
void change(int x,int l,int r,int k){
if(l<=t[x].l&&t[x].r<=r){
t[x].lz=k;return ;
}
if(t[x].l>r||t[x].r<l)return ;
pushdown(x);
change(lc,l,r,k);
change(rc,l,r,k);
}
int find(int x,int pos){
if(t[x].l==t[x].r)return t[x].lz;
pushdown(x);
if(t[lc].r>=pos)return find(lc,pos);
return find(rc,pos);
}
struct Node{
int id,x;
bool operator<(const Node b){
return x==b.x?id<b.id:x>b.x;
}
}b[N];int c[N],d[N],mx;
void add(int x,int k){
while(x<=n){
d[x]+=k;x+=lowbit(x);
}
}
int ask(int x){
int ans=0;
while(x){
ans+=d[x];x-=lowbit(x);
}
return ans;
}
signed main(){
ios::sync_with_stdio(false);
cin>>n;int m,q;cin>>m>>q;
for(int i=1;i<=n;i++){
char x;cin>>x;a[i]=x-'0';
}
for(int i=1;i<=m;i++)cin>>L[i]>>R[i];
build(1,1,n);
for(int i=m;i;--i)change(1,L[i],R[i],m-i+1);
for(int i=1;i<=n;i++)b[i].x=find(1,i),b[i].id=i,mx+=(b[i].x!=0);
sort(b+1,b+n+1);int ans=0,cnt=0;
for(int i=1;i<=n;i++)c[b[i].id]=i;
for(int i=1;i<=n;i++)cnt+=a[i];
for(int i=1;i<=n;i++)if(a[i])add(c[i],1);
while(q--){
int x;cin>>x;
if(!a[x]){
a[x]=1;++cnt;add(c[x],1);
}
else {
a[x]=0;--cnt;add(c[x],-1);
}
cout<<min(mx,cnt)-ask(min(mx,cnt))<<"\n";
}
}
E
交互题。
,询问次数不超过 。 序列
长为 ,其中 ,每一次询问给出3个 ,输入以 为边长的三角形的面积的平方乘16的结果,特别地,如果构不成三角形,输入0,求这个序列。
题解:
我发现,交互题的核心思想:简化确定步骤
注意,有个东西叫海伦-秦九韶公式,也即三角形面积与三边关系:记
所以
故,本质上我们是得到了三边的关系式。注意到对于
手玩一下,大胆猜想在
面对这种问题,注意到可能性很小,可以打表证明该结论。经过穷举,我们证明了以上猜想。
这是一个很有用的性质:除了0之外,一个三角形的面积单射三边长。我们考虑处理出每个面积对应的三角形三边长。
void init(){
for(int i=1;i<=4;i++){
for(int j=i;j<=4;j++){
for(int k=j;k<=4;k++){
if(i+j<=k)continue;
int p=(i+j+k)*(i+j-k)*(i+k-j)*(k+j-i);
s[p]=(node){i,j,k};
}
}
}
}
然后,我们来考虑怎么确定这些值。
注意到
怎么一次确定一个数呢?有且只有我们已知两个数
容易发现,对于一个等腰三角形而言,除非底边是两腰之和,否则必定存在该三角形。
则我们假定
那么,再考虑一下
所以本质而言,除了
我们先假定
根据鸽巢原理,在
这启发我们,当
若
void get_9(int tag){
for(int i=0;i<f.size();i++)
for(int j=i+1;j<f.size();j++)
for(int k=j+1;k<f.size();k++){
int x=ask(f[i],f[j],f[k]);
vis[i][j][k]=x;
if(x==0)continue;
if(s[x].a==s[x].c&&n>=9&&(tag==0||s[x].a!=1)){
id=s[x].a;X1=f[i],Y1=f[j],Z1=f[k];
ans[X1]=ans[Y1]=ans[Z1]=id;
return ;
}
}
dfs(0,f.size());//等会解释作用
}
//solve:
if(id>=2){
for(int i=1;i<=n;i++){
if(X1==i||Y1==i||Z1==i)continue;
int k=ask(X1,Y1,i);
for(int j=1;j<=4;j++){
if(k==(id+id+j)*(id+id-j)*j*j){
ans[i]=j;break;
}
}
}
cout<<"! ";for(int i=1;i<=n;i++)cout<<ans[i]<<" ";
cout.flush();
}
但
这样的话,我们将剩下的可以确定
询问次数?除了两次找三个相等
但是,如果找不到呢?
我们将原本确定的三个一也插入进去,以防止找不到,若找不到就执行下面的dfs直接
int a[N],vis[9][9][9],ans[N];
vector<int>f;
struct node{
int a,b,c;
}s[N];
int ask(int x,int y,int z){
if(x>y)swap(x,y);
if(x>z)swap(x,z);
if(y>z)swap(y,z);
cout<<"? "<<x<<" "<<y<<' '<<z<<"\n";cout.flush();
int res;cin>>res;return res;
}
void init(){
for(int i=1;i<=4;i++){
for(int j=i;j<=4;j++){
for(int k=j;k<=4;k++){
if(i+j<=k)continue;
int p=(i+j+k)*(i+j-k)*(i+k-j)*(k+j-i);
s[p]=(node){i,j,k};
}
}
}
}
int cnt=0;
bool check(){
// for(int i=0;i<f.size();++i)cout<<a[i]<<" ";cout<<"\n";
for(int i=0;i<f.size();i++)
for(int j=i+1;j<f.size();j++)
for(int k=j+1;k<f.size();k++){
int x=a[f[i]],y=a[f[j]],z=a[f[k]];
if(x+y<=z||x+z<=y||y+z<=x){
if(!vis[i][j][k])continue;
return 0;
}
if(vis[i][j][k]==(x+y+z)*(x+y-z)*(x-y+z)*(-x+y+z))continue;
return 0;
}
for(int i=0;i<f.size();++i)ans[f[i]]=a[f[i]];
return 1;
}
int X1,Y1,Z1,id;
void dfs(int now,int len){
if(cnt>1)return ;
if(now>=len){
cnt+=check();
return ;
}
for(int i=1;i<=4;i++){
a[f[now]]=i;dfs(now+1,len);
}
return ;
}
void get_9(int tag){
for(int i=0;i<f.size();i++)
for(int j=i+1;j<f.size();j++)
for(int k=j+1;k<f.size();k++){
int x=ask(f[i],f[j],f[k]);
vis[i][j][k]=x;
if(x==0)continue;
if(s[x].a==s[x].c&&n>=9&&(tag==0||s[x].a!=1)){
id=s[x].a;X1=f[i],Y1=f[j],Z1=f[k];
ans[X1]=ans[Y1]=ans[Z1]=id;
return ;
}
}
dfs(0,f.size());
}
void repeat(){
f.clear();
for(int i=1;i<=n;i++){
if(X1==i||Y1==i||Z1==i)continue;
if(ask(X1,Y1,i)!=0){
ans[i]=1;
}
else {
f.push_back(i);if(f.size()==9)break;
}
}
f.push_back(X1),f.push_back(Y1),f.push_back(Z1);
cnt=0;
get_9(1);
if(id==1){
if(cnt!=1)cout<<"! -1\n";
else {
cout<<"! ";for(int i=1;i<=n;i++)cout<<ans[i]<<" ";//the old time:f.size()<9,else id must be 2 or 3 or 4
}
return ;
}
for(int i=1;i<=n;i++){
if(X1==i||Y1==i||Z1==i)continue;
int k=ask(X1,Y1,i);
for(int j=1;j<=4;j++){
if(k==(id+id+j)*(id+id-j)*j*j){
ans[i]=j;break;
}
}
}
cout<<"! ";for(int i=1;i<=n;i++)cout<<ans[i]<<" ";cout.flush();
}
void solve(){
for(int i=1;i<=min(9ll,n);i++)f.push_back(i);
if(n<9){
get_9(0);
if(cnt!=1){
cout<<"! -1\n";cout.flush();
}
else{
cout<<"! ";for(int i=1;i<=n;i++)cout<<ans[i]<<" ";
cout.flush();
}
return ;
}
else {
get_9(0);
if(id>=2){
for(int i=1;i<=n;i++){
if(X1==i||Y1==i||Z1==i)continue;
int k=ask(X1,Y1,i);
for(int j=1;j<=4;j++){
if(k==(id+id+j)*(id+id-j)*j*j){
ans[i]=j;break;
}
}
}
cout<<"! ";for(int i=1;i<=n;i++)cout<<ans[i]<<" ";
cout.flush();
}
else repeat();
}
}
signed main(){
ios::sync_with_stdio(false);
cin>>n;
init();
solve();
}
不得不说,代码有点长,有一点点暴力
F
给定一个无穷长的整数数列的前
项 ,且对于任意 , . 你需要处理
次询问。每次询问给定一个整数 ,请找出最小的 满足 。如果不存在这样的 ,输出 −1
。
这问题更有意思了。w直接喜提次劣解,不懂为什么我单log却被卡得那么惨,时限3000ms,我直接2994ms
对于有规律的递推数列问题,可以考虑写个十几项找规律。
我们假定
如果用DP式来说,就是
容易看出,所求
- 每一个
都对应原序列的一段区间的或运算值 - 除
外,每一个 的对应或区间的末尾都不是1 - 若破环为链,则第
列的第 个对应区间或区间
回到本题,从序列上发现性质之后,就应该在运算上发现性质。
结合或运算的性质:
关于或运算还有与运算,都有性质:对于序列
为什么?因为做前缀或/与运算,只有在二进制上0/1不断变1/0,至多变化
推论:对于序列
由此,我们可以考虑求出这些值,然后将这
for(int i=1;i<=n;i++){
b[++tot]=(node){a[i],i};
solve(i,2,n);
}
sort(b+1,b+tot+1);//运算符重载
for(int i=1;i<=tot;i++)g[i]=min(g[i-1],b[i].id);
while(q--){
read(k);if(k>=b[1].x){
cout<<"-1\n";continue;
}
int x=lower_bound(b+1,b+tot+1,(node){k,0})-b-1;//注意这里有个坑点,比大小只能与k有关,不可以与id有关,否则会挂掉的。
cout<<g[x]<<"\n";
}
然后我们考虑对于每行怎么求出这些值。
其实是简单的,我们可以对于 每一个值都单独倍增/二分一遍就行。但要特判区间右端点不为
这里我在观看题解后采用的一种更为简单的做法:利用线段树的分治思想,(亦或者可以叫整体二分的某个变种),处理一整段区间,对于整个区间值都相同且已经统计过就直接跳过。
void solve(int now,int l,int r){
int mid=l+r>>1,x=get(now,now+mid-1);
if(l==r){
if(b[tot].x!=x&&now+l-1!=n+1){
b[++tot]=(node){x,(l-1-(now+l-1>n+1))*n+now};
}
return ;
}
if(b[tot].x==get(now,now+l-1)&&b[tot].x==get(now,now+r-1))return ;
solve(now,l,mid);solve(now,mid+1,r);
}
简要分析一下复杂度:递归统计每个答案到底层都要
这样就做完了,复杂度
这种若干次二分化递归解决,是一个不错的办法。
Trick 总结
- D:如果问题中满足某些限制的数明显具有优先级,那么从优先级的角度考虑问题,求出每个数的优先级后再做处理
- D:注意答案的上下界
- E:存在性问题,鸽巢原理
- E:简化确定步骤
- E:考虑单射的结论
- E:大胆猜测,打表求证
- F:对于有规律的递推数列问题,可以考虑写个十几项找规律
- F:找矩阵的规律,关注行列对角线等,以及运算方式
- F:多次倍增跳跃求变化用区间递归实现
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!