Div3相对以前变简单了,罚时手速场。
A题
暴力
题意:
1000组数据
给定一个只包含正整数的长度为n的数组(n最大50),每个数字均在1到50之间。
你可以在任意位置插入任意的整数(这里其实有bug,你如果插入一个负数那怎么都满足了,题目要求的应该是正整数)。
问最少需要插入多少个数字,使得该数组每一对相邻的两个数字,都满足大的那个不超过小的那个的2倍。
思路:
数值都非常小,直接暴力检测每一对相邻的数字,贪心对小的那个数不断乘2翻倍直到大于等于大的那个数即可。
#include
#define ll long long
#define INF 0x7f7f7f7f
#define llINF 9223372036854775807
#define IOS ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
using namespace std;
const int maxn=1e6+7;
const double eps=1e-6;
const int mod=1e9+7;
int main()
{
IOS
int t;cin>>t;
while(t--)
{
int n;cin>>n;
vector<int>num(n);
for(int i=0;i<n;i++) cin>>num[i];
int ans=0;
for(int i=1;i<n;i++)
{
int x=min(num[i],num[i-1]);
int y=max(num[i],num[i-1]);
while(x*2<y)
{
ans++;
x*=2;
}
}
cout<<ans<<endl;
}
}
B题
暴力
题意:
给定一个长度为n的只包含整数的数列,其中n必定能被3整除。
数列中对3取余得到0的数字的个数记为cas0,
数列中对3取余得到1的数字的个数记为cas1,
数列中对3取余得到2的数字的个数记为cas2,
每次操作,你可以将数列中的某个数字的值+1。
问最少经过多少次操作,可以使得cas0=cas1=cas2。
思路:
我们最后的目标是使得cas0=cas1=cas2=n/3。
那么实际上我们就是要把多了的通过+1操作补到少了的cas上去。
如果cas1大于n/3的话,我们可以把多了的部分都+1,让他们变成cas2的值。
cas0,cas1,cas2的具体情况当然是有很多种,但是不论是哪种,都存在[0->1->2]或者[1->2->0]或者[2->0->1]这样的转移顺序使得最后三个cas都为n/3。
直接暴力for两遍0-2的转移即可。
#include
#define ll long long
#define INF 0x7f7f7f7f
#define llINF 9223372036854775807
#define IOS ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
using namespace std;
const int maxn=1e6+7;
const double eps=1e-6;
const int mod=1e9+7;
int main()
{
IOS
int t;cin>>t;
while(t--)
{
int n;cin>>n;
vector<int>num(n);
int cas[3]={
0};
for(int i=0;i<n;i++)
{
cin>>num[i];
if(num[i]%3==0) cas[0]++;
else if(num[i]%3==1) cas[1]++;
else cas[2]++;
}
int ans=0;
for(int i=0;i<2;i++)
for(int j=0;j<3;j++)
if(cas[j]>n/3) {
ans+=cas[j]-n/3;cas[(j+1)%3]+=cas[j]-n/3;cas[j]=n/3;}
cout<<ans<<endl;
}
}
C题
预处理,map
题意:
t组数据,最大100组。
每组数据给定一个1e12以内的正整数x,你需要求出满足a3+b3=x的(a,b)有多少对。
思路:
注意到x最大为1e12,那么a和b的取值最大只有1e4。
我们可以预处理出1到1e4这1e4个整数的三次方为多少,用map记录其是否出现。
每组数据,我们暴力枚举这1e4个三次方,用map检测x减去当前的值,剩下的部分是否也为一个立方数即可。
复杂度为100 × \times × 1e4 × \times × log(1e4)。
#include
#define ll long long
#define INF 0x7f7f7f7f
#define llINF 9223372036854775807
#define IOS ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
using namespace std;
const int maxn=1e6+7;
const double eps=1e-6;
const int mod=1e9+7;
map<ll,int>M;
vector<ll>num;
int main()
{
IOS
int t;cin>>t;
for(int i=1;i<=10000;i++)
{
ll x=i;
x=x*x*x;
M[x]=1;
num.push_back(x);
}
while(t--)
{
bool flag=0;
ll x;cin>>x;
for(int i=0;i<num.size();i++)
{
if(M.find(x-num[i])!=M.end()) {
flag=1;break;}
}
if(flag) cout<<"YES"<<endl;
else cout<<"NO"<<endl;
}
}
D题
暴力模拟,dfs
题意:
给定一个长度为n的排列。
你要按照如下的规则将这个排列构造成一棵二叉树,依次输出每个位置在这棵树中的深度是多少。
先把整个排列中最大的数,作为树根。
在原排列中,在当前这个数左侧的所有数都在当前根的左子树中,并且以其中最大的值为子树的根。
在原排列中,在当前这个数右侧的所有数都在当前根的右子树中,并且以其中最大的值为子树的根。
之后左右子树同上进行构造。
思路:
直接dfs模拟这个过程就可以了,分析复杂度会发现最糟糕也就是n2,再乘以数据组数100组也就1e6的级别。
#include
#define ll long long
#define INF 0x7f7f7f7f
#define llINF 9223372036854775807
#define IOS ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
using namespace std;
const int maxn=1e6+7;
const double eps=1e-6;
const int mod=1e9+7;
int num[107];
int deep[107];
void dfs(int l,int r,int now,int d)
{
if(l==r) return;
if(l<now)
{
int tar=now-1;
for(int i=l;i<now-1;i++) if(num[i]>num[tar]) tar=i;
deep[tar]=d+1;
dfs(l,now-1,tar,d+1);
}
if(r>now)
{
int tar=now+1;
for(int i=now+2;i<=r;i++) if(num[i]>num[tar]) tar=i;
deep[tar]=d+1;
dfs(now+1,r,tar,d+1);
}
}
int main()
{
IOS
int t;cin>>t;
while(t--)
{
int n;cin>>n;
for(int i=1;i<=n;i++) cin>>num[i];
memset(deep,0,sizeof(deep));
int tar=1;
for(int i=2;i<=n;i++) if(num[i]>num[tar]) tar=i;
deep[tar]=0;
dfs(1,n,tar,0);
for(int i=1;i<=n;i++) cout<<deep[i]<<' ';
cout<<endl;
}
}
E题
前缀和,贪心
真是绝了,上场读错了的题意这次居然真的出了这样的题
题意:
给定n个玩家,每个玩家一开始都有若干的代币。
会进行n-1次比拼,每次比拼随机选出两个代币数量不为0的玩家。
如果两个人代币数量不相同,代币多的那个人,拿走两个人的全部代币。
如果两个人代币数量相同,在这两个人中随机选一个人,拿走两个人的所有代币。
最后只剩下一个人。
问有哪些人是有可能是最后剩下的那个人。
思路:
既然说是有可能了,那么我们可以理解为我们可以贪心地去操作比赛的结果,平局的时候我们都令自己选的哪个人获胜。
对于每个人来说,肯定都是先贪心去找其他人当中代币最少的那个去比,如果出现剩下的人中,代币最少的那个也比自己的代币多,那就没办法胜利了。
实际上我们注意到,胜利的人会拿走所有的代币。我们先对着n个人按照代币数量从小到大排序为num[n]数组。随便取一个人,如果他打赢了前i个人的话,那么它当前的代币数量就是num[i]的前缀和。
如果num[i]
我们从后往前去找,找到前缀和不足以赢下一个人的位置,就得到了想赢所有人需要的最少代币。输出初始代币数量不少于这个值的人即可。
#include
#define ll long long
#define INF 0x7f7f7f7f
#define llINF 9223372036854775807
#define IOS ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
using namespace std;
const int maxn=2e5+7;
const double eps=1e-6;
const int mod=1e9+7;
ll num[maxn],chuli[maxn],sum[maxn];
int main()
{
IOS
int t;cin>>t;
while(t--)
{
int n;cin>>n;
for(int i=1;i<=n;i++)
{
cin>>num[i];
chuli[i]=num[i];
}
sort(chuli+1,chuli+n+1);
for(int i=1;i<=n;i++) sum[i]=sum[i-1]+chuli[i];
ll bas=chuli[n];
for(int i=n-1;i>=1;i--)
{
if(sum[i]<chuli[i+1]) break;
bas=chuli[i];
}
vector<int>out;
for(int i=1;i<=n;i++)
if(num[i]>=bas) out.push_back(i);
cout<<out.size()<<endl;
for(int i=0;i<out.size();i++) cout<<out[i]<<' ';
cout<<endl;
}
}
F题
前缀和,暴力
读错题意成既可以删除也可以增加可还行,不过还好只需要删改两三行就可以了
题意:
给定一个长度为n只包含正整数的数组。(n最大2e5)
现在你需要删除尽可能少的数,使得数组中的每个数字出现的次数相同。
思路:
直接暴力枚举最后结果的数组中,每个数字出现了多少次,次数肯定在1到2e5之间。
而每次枚举的计算过程,可以利用前缀和来O(1)实现,具体看代码和注释。
#include
#define ll long long
#define INF 0x7f7f7f7f
#define llINF 9223372036854775807
#define IOS ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
using namespace std;
const int maxn=2e5+7;
const double eps=1e-6;
const int mod=1e9+7;
map<int,int>M;
ll cishu[maxn];
ll sumcishu[maxn];
ll num[maxn];
ll sumnum[maxn];
int main()
{
IOS
int t;cin>>t;
while(t--)
{
int n;cin>>n;
ll ans=llINF;
for(int i=0;i<n;i++)
{
int x;cin>>x;
if(M.find(x)==M.end()) M[x]=1;
else M[x]++;
}
for(auto &x:M)
{
cishu[x.second]++;
num[x.second]+=x.second;
}
for(int i=1;i<=n;i++)
{
sumcishu[i]=sumcishu[i-1]+cishu[i];
sumnum[i]=sumnum[i-1]+num[i];
}
for(int i=1;i<=n;i++)
{
ll temp=0;
temp+=n-sumnum[i]-(sumcishu[n]-sumcishu[i])*i;
temp+=sumnum[i-1];
ans=min(ans,temp);
}
cout<<ans<<endl;
for(int i=1;i<=n;i++) cishu[i]=sumcishu[i]=num[i]=sumnum[i]=0;
M.clear();
}
}
G题
二分,前缀和,dp
二分函数的mid写成了l是我没想到的,疯狂白给5发,二分居然写错了,青结
题意:
给定n个数字,每次操作都从第一个数字开始,每过一秒,依次加上下一位置的数字,不断循环。
给定m个数字x,问经过多少秒之后,累加的和不小于x。如果永远不可能达到x,输出-1。
n和m最大均为2e5。
思路:
首先用sum[i]记录前i个数字累加起来是多少,mi记录sum数组的最大值。
如果给定的x小于等于mi的话,那么在第一轮循环遍n个数字前便已经结束。我们可以对sum[i]做一个dp,用dp[i]记录sum[1]到sum[i]中最大的值为多少。由此利用二分可在logn的时间找到累加大于等于x的第一个位置。
如果给定的x大于mi的话,就要看后面多次循环后能否达到了。x大于mi,那么x的值至少要被减去x-mi才行。
如果sum[n]<=0的话,代表每次循环结束x的值x与mi的差值并不会减少,因此此时就是无限循环,输出-1即可。
当sum[n]>0时,x-mi至少需要(x-mi)/sum[n]向上取整的循环后才会被减去,再下一轮循环就能达到值x了,同样利用二分来logn时间寻找位置。
#include
#define ll long long
#define INF 0x7f7f7f7f
#define llINF 9223372036854775807
#define IOS ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
using namespace std;
const int maxn=2e5+7;
const double eps=1e-6;
const int mod=1e9+7;
ll num[maxn];
ll sum[maxn];
ll dp[maxn];
int search(int n,ll x)
{
int l=1,r=n;
while(l<r)
{
int mid=(l+r)>>1;
if(dp[mid]<x) l=mid+1;
else r=mid;
}
return l;
}
int main()
{
IOS
int t;cin>>t;
while(t--)
{
int n,m;cin>>n>>m;
ll mi=-1e10;
for(int i=1;i<=n;i++)
{
cin>>num[i];
sum[i]=sum[i-1]+num[i];
dp[i]=max(dp[i-1],sum[i]);
mi=max(mi,sum[i]);
}
while(m--)
{
ll x;cin>>x;
if(x>mi)
{
if(sum[n]<=0) cout<<-1<<' ';
else
{
ll cas=(x-mi)/sum[n];
if((x-mi)%sum[n]) cas++;
x-=cas*sum[n];
cout<<cas*n-1+search(n,x)<<' ';
}
}
else cout<<search(n,x)-1<<' ';
}
cout<<endl;
for(int i=1;i<=n;i++) num[i]=sum[i]=dp[i]=0;
}
}