【题解】牛客练习赛59
A:小乔和小灰灰
令表示字符串
遍历到位置
,与字符串
匹配的最大长度。
令表示字符串
遍历到位置
,与字符串
匹配的最大长度。
则转移为,
。
最后判断最大长度是否与两个字符串的长度相等即可。
#include<bits/stdc++.h>
using namespace std;
const int N=1005;
int n;
char s[N];
string a="XiaoQiao";
string b="XiaoHuiHui";
int x,y;
int main()
{
scanf("%s",s+1);
n=strlen(s+1);
assert(n>=1&&n<=1000);
for(int i=1;i<=n;i++)
assert(s[i]>='a'&&s[i]<='z'||s[i]>='A'&&s[i]<='Z');
for(int i=1;i<=n;i++)
{
if(s[i]==a[x]) x++;
if(s[i]==b[y]) y++;
}
if(x==a.size()&&y==b.size()) printf("Happy\n");
else printf("emm\n");
}
B:牛能和小镇
题目显然是要我们求一颗最小生成树。我们把距离公式交换一下顺序:
令,那么两点之间的距离为
。
我们把所有点按排一下序,相邻两点之间连一条边即可得到最小生成树,时间复杂度
。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=1e5+5;
int n;
ll a[N];
int main()
{
scanf("%d",&n);
assert(n>=1&&n<=100000);
for(int i=1;i<=n;i++)
{
ll x,y;scanf("%lld%lld",&x,&y);
assert(x>=1&&x<=100000);
assert(y>=1&&y<=100000);
a[i]=x*x*y+y*y*(y-2*x);
}
sort(a+1,a+1+n);
ll ans=0;
for(int i=1;i<n;i++) ans+=a[i+1]-a[i];
printf("%lld\n",ans);
}
C:装备合成
假设第一种装备合成了件,那么第二种装备可以合成
件。朴素的做法是枚举
,然后取最优答案,但是会
。
于是考虑升级以上做法:
1.做一个假设,假设上述做法存在单调极值性,那么我们可以通过简单的三分做出来。
2.随机一些数据打表,打表后发现果然存在单调极值性,那么我们可以通过简单的三分做出来。
综上所述,我们可以对进行三分,单组数据的时间复杂度
,总的时间复杂度
#include<bits/stdc++.h>
using namespace std;
int main()
{
int t;scanf("%d",&t);
assert(t>=1&&t<=10000);
while(t--)
{
int x,y;scanf("%d%d",&x,&y);
assert(x>=1&&x<=1000000000);
assert(y>=1&&y<=1000000000);
int l=0,r=min(x/2,y/3);
while(r-l>10)
{
int m1=l+(r-l)/3,m2=r-(r-l)/3;
if(m1+min((x-2*m1)/4,y-3*m1)>m2+min((x-2*m2)/4,y-3*m2))
r=m2;
else l=m1;
}
int ans=0;
for(int i=l;i<=r;i++)
ans=max(ans,i+min((x-2*i)/4,y-3*i));
printf("%d\n",ans);
}
}
D:取石子游戏
首先,当是一定是必败态。
那么可以转移到的状态是
和
,即
为必胜态。
只能转移到的状态为
,即
为必败态。
能转移到的状态为
,为必胜态。
......
根据上述依次类推,可以发现必胜态和必败态是一段一段区间这样出现的,并且长度会不断增长,增长的长度与的幂次有关,那么我们记录每一段的头和尾打表,对于每个查询,二分一下
在哪一段区间,根据状态输出答案即可。时间复杂度
。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=105;
int tot;
ll s[N],e[N];
int main()
{
s[++tot]=1;e[tot]=1;
s[++tot]=2;e[tot]=3;
while(true)
{
tot++;
s[tot]=e[tot-1]+1;
if(tot&1)
e[tot]=e[tot-1]*2;
else
e[tot]=e[tot-1]*2+1;
if(e[tot]>1e18) break;
}
int t;scanf("%d",&t);
assert(t>=1&&t<=100000);
while(t--)
{
ll n;scanf("%lld",&n);
assert(n>=1&&n<=(ll)1000000000000000000);
int pos=upper_bound(s+1,s+1+tot,n)-s-1;
if(pos&1) printf("XiaoQiao\n");
else printf("XiaoHuiHui\n");
}
}
E:石子搬运
首先,让我们看看问题不修改的话我们怎么做。
设表示第
堆石子搬运
次需要的最小代价。对于单堆石子,最优的策略是把
个石子均分在
次搬运上。可以证明这是最优的策略:
假设这样产生的花费为,而每次搬运的个数都为
,我们把某次搬运的个数
,加到另一次搬运上,重新计算的花费为
,对于任意的
,一定有
,那么花费增加了。
再考虑两堆石子,我们知道
和
,另
表示两堆搬运
次全部搬完的代码,有
。
如果没有修改,我们从前往后遍历进行得出答案,这样单次处理的时间复杂度为
。如果处理
次,因为
同阶,时间复杂度为
,但复杂度太高会得到
。
注意到本题每次仅修改一个点,那么是否可以把稍做修改,使得每次修改后可以快速得出答案呢?这显然是可以的,考虑
堆石子
,我们先合并
的答案,再合并
的答案,再将
和
的答案合并,这不影响最终的结果。
这样,我们可以采用分治的思想来进行(类似于线段树),因为最多
个结点,所以初始化的时间复杂度为
。,但对于后面的每次修改,就相当于线段树单点修改,单点修改的时间复杂度为
,总的时间复杂度
,发现这样复杂度其实也挺大的,但别忘了还有一个小技巧
优化,利用
优化后时间跑不满,足以通过这道题。
#include<bits/stdc++.h>
#define inf 0x3f3f3f3f
using namespace std;
typedef long long ll;
const int N=405;
int n,m,q,sum,a[N],len[N<<2];
ll dp[N<<2][N];
void update(int k)
{
memset(dp[k],inf,sizeof(dp[k]));
int ls=k<<1,rs=k<<1|1;
for(int i=len[ls];i<=m;i++)
for(int j=len[rs];i+j<=m;j++)
{
dp[k][i+j]=min(dp[k][i+j],dp[ls][i]+dp[rs][j]);
}
}
void build(int l,int r,int k)
{
len[k]=r-l+1;
if(l==r)
{
memset(dp[k],inf,sizeof(dp[k]));
for(int i=1;i<=min(a[l],m);i++)
{
dp[k][i]=a[l]/i;
dp[k][i]=a[l]%i*(dp[k][i]+1)*(dp[k][i]+1)+(i-a[l]%i)*dp[k][i]*dp[k][i];
}
return;
}
int m=l+r>>1;
build(l,m,k<<1);build(m+1,r,k<<1|1);
update(k);
}
void fix(int l,int r,int k,int x)
{
if(l==r)
{
memset(dp[k],inf,sizeof(dp[k]));
for(int i=1;i<=min(a[l],m);i++)
{
dp[k][i]=a[l]/i;
dp[k][i]=a[l]%i*(dp[k][i]+1)*(dp[k][i]+1)+(i-a[l]%i)*dp[k][i]*dp[k][i];
}
return;
}
int m=l+r>>1;
if(x<=m) fix(l,m,k<<1,x);
else fix(m+1,r,k<<1|1,x);
update(k);
}
int main()
{
scanf("%d%d",&n,&m);
assert(n>=1&&n<=400);
assert(m>=n&&m<=400);
for(int i=1;i<=n;i++) scanf("%d",&a[i]),assert(a[i]>=1&&a[i]<=100000),sum+=a[i];
build(1,n,1);
scanf("%d",&q);
while(q--)
{
int x,v;scanf("%d%d",&x,&v);
assert(x>=1&&x<=n);assert(v>=1&&v<=100000);
sum-=a[x];
sum+=v;
a[x]=v;
fix(1,n,1,x);
printf("%lld\n",dp[1][min(sum,m)]);
}
}
F:小松鼠吃松果
对于第个松果,其落地的时间为
,那么如果小松鼠吃了第
个松果还能吃第
个松果,当且仅当松果
的落地时间大于等于松果
且他们的落地时间之差大于等于其横向距离。接下来,我们默认为
。将所有松果按落地时间
排序,我们可以得到一个简单的
,对于排序后的松果
,有
对于所有的
满足
,这样的时间复杂度为
,我们会得到
。
考虑把条件拆分一下
如果
注意到此时一定满足
如果
注意到此时一定满足
令,
。对于同时满足
和
的
,就是可以转移的,这变成了一个二维偏序关系,于是可以排序后用树状数组维护
,时间复杂度
。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=1e5+5;
struct node
{
int t,p,w;
bool operator<(const node&o)const
{
return t==o.t?p<o.p:t<o.t;
}
}a[N];
int n,m,num,pos[N],b[N],hs[N];
ll t[N];
ll query(int x)
{
ll ans=0;
while(x)
{
ans=max(ans,t[x]);
x-=x&-x;
}
return ans;
}
void add(int x,ll v)
{
while(x<=n)
t[x]=max(t[x],v),x+=x&-x;
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++) scanf("%d",&pos[i]),hs[i]=pos[i];
for(int i=1;i<=m;i++) scanf("%d",&b[i]);
for(int i=1;i<=n;i++)
scanf("%d%d%d",&a[i].t,&a[i].p,&a[i].w),a[i].t+=b[a[i].p];
for(int i=1;i<=n;i++)
{
int x=a[i].t-pos[a[i].p],y=a[i].t+pos[a[i].p];
a[i].t=x;
a[i].p=y;
hs[i]=y;
}
sort(hs+1,hs+1+n);
ll ans=0;
sort(a+1,a+1+n);
for(int i=1;i<=n;i++)
{
int pos=lower_bound(hs+1,hs+1+n,a[i].p)-hs;
ll dp=query(pos)+a[i].w;
ans=max(ans,dp);
add(pos,dp);
}
printf("%lld\n",ans);
}
