[省选集训2022] 模拟赛4

A

题目描述

给定一个长度为 n 的数列 a 和常数 c,将其划分成若干段,设 bi 表示第 i 段的最大值:

i=2m(bibi1)2+c

特别地,如果只划分出一段(m=1)则答案为 0

2n106,1ai106,0c1010

解法

dp[i] 表示 ai 为最后一段的最大值的答案,转移枚举上一段的最大值点 j

dp[i]=max{dp[j]+(aiaj)2+c}

转移的条件是 [j,i] 可以被划分成两段使得 aj,ai 分别是这两段的最大值。考虑 k[i,j],如果  ak>aiak>aj,那么肯定是划分不动的,但是考虑此时k 转移一定比 j,所以不用考虑 j 合不合法,因为如果 j 不合法那么一定没用。

那么直接斜率优化即可,时间复杂度 O(nlogn),所以你按照 ai 单增去打这题就可以过掉。

#include <cstdio>
#include <iostream>
using namespace std;
const int M = 1000005;
#define int long long
int read()
{
	int x=0,f=1;char c;
	while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
	while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
	return x*f;
}
int n,m,c,ans;
struct node
{
	int k,b;
	node(int K=0,int B=-9e18) : k(K) , b(B) {}
	int ask(int x) {return k*x+b;}
}t[M<<2];
void ins(int i,int l,int r,node nw)
{
	int mid=(l+r)>>1;
	if(nw.ask(mid)>t[i].ask(mid)) swap(t[i],nw);
	if(l==r) return ;
	if(nw.ask(l)>t[i].ask(l)) ins(i<<1,l,mid,nw);
	if(nw.ask(r)>t[i].ask(r)) ins(i<<1|1,mid+1,r,nw);
}
int ask(int i,int l,int r,int x)
{
	int res=t[i].ask(x),mid=(l+r)>>1;
	if(l==r) return res;
	if(mid>=x) return max(ask(i<<1,l,mid,x),res);
	return max(ask(i<<1|1,mid+1,r,x),res);
}
signed main()
{
	freopen("array.in","r",stdin);
	freopen("array.out","w",stdout);
	n=read();c=read();m=1e6;
	for(int i=1;i<=n;i++)
	{
		int x=read();
		int dp=max(0ll,ask(1,1,m,x)+x*x+c);
		ans=max(ans,dp);
		ins(1,1,m,node(-2*x,dp+x*x));
	}
	printf("%lld\n",ans);
}

B

题目描述

这是一道交互题,交互库有下列三个可调用的函数:

int qry1(int x);//返回 a[x]
vector<int> qry2(vector<int> &s);//返回内含 |a[s[i]]-a[s[j]]| (i>j) 的无序集合
void answer(vector<int> &ans);//给出交互答案,调用该函数即意味着结束程序

有长度为 n 的数组 a,满足 ai 互不相同,请通过交互求出这个数组。你不需要实现主函数,你只需要实现下列函数,并且要求 qry1 的调用次数不超过 m1 次,qry2 的调用次数不超过 m2 次,answer 的调用次数恰好为一次。

void find(int n,int m1,int m2);

对于最大范围的数据满足:n250,m1=2,m2=30

解法

这种确定序列的题目有常规做法,那就是先找关键点,然后再确定整个序列

有一个 observation:我们从 询问 {sx} 得到的集合 中除掉 询问 {s} 得到的集合,可以得到 s 中所有数和 x 的距离,那么我们的关键点可以设置成最大值或者最大值。

先问一次可以得到序列的极差,那么我们二分一个前缀是否包含极差,就可以得到位置 x(它是最大值或者最小值,但是暂时不清楚),这部分的花费是 1logn 的。

然后我们确定整个序列,可以二进制分组(或者直接分治,原理都是一致的),因为每个距离都可以唯一代表一个值,我们只需要确定距离对应的位置。那么可以把包含 0,1,2...logn 这些二进制位的位置都取出来,然后直接询问这个集合 {s} 和集合 {sx},把得到的距离的 pos 都加上 2i

那么最后的 pos 就是这个距离对应着的位置,小细节是这样做必须从 1 开始编号最好。并且我们可以得到另一个最值的位置,把它们两个都问一下就可以得到 x 是最大值还是最小值了。这一部分的消耗是 2logn 的,所以总共的消耗是 3logn,足以通过本题。

#include "difference.h"
#include <vector>
#include <algorithm>
#include <map>
using namespace std;
#define pb push_back
#define vi vector<int>
void find(int n,int M1,int M2)
{
	vi A,B,ans;map<int,int> mp;
	for(int i=0;i<n;i++) A.pb(i);B=qry2(A);
	int m=*max_element(B.begin(),B.end());
	int l=1,r=n-1,x=0,t=0,p[505]={};
	while(l<=r)
	{
		int mid=(l+r)>>1;A.clear();
		for(int i=0;i<=mid;i++) A.pb(i);
		B=qry2(A);t=*max_element(B.begin(),B.end());
		if(t==m) r=mid-1,x=mid;
		else l=mid+1;
	}
	for(int i=0;(1<<i)<=n;i++)
	{
		A.clear();
		for(int j=0;j<n;j++)
			if(((j+1)&(1<<i)) && j!=x) A.pb(j);
		vi C=qry2(A);map<int,int> nw;
		A.pb(x);B=qry2(A);
		for(auto x:B) nw[x]++;
		for(auto x:C) nw[x]--;
		for(auto x:nw) if(x.second)
			mp[x.first]+=(1<<i);
	}
	auto it=mp.end();it--;int u=it->second;
	p[u]=qry1(u-1);p[x+1]=qry1(x);
	int fl=(p[u]>=p[x+1])?1:-1;
	for(auto t:mp) p[t.second]=t.first*fl+p[x+1];
	for(int i=1;i<=n;i++) ans.pb(p[i]);
	answer(ans);
}
posted @   C202044zxy  阅读(242)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 地球OL攻略 —— 某应届生求职总结
· 提示词工程——AI应用必不可少的技术
· Open-Sora 2.0 重磅开源!
· 周边上新:园子的第一款马克杯温暖上架
点击右上角即可分享
微信分享提示