前言
上午考完编译原理,12:52
左右回到实验室,然后学弟说打算13:00
开一场vp,之后我们愉快的在13:05
开始了这场vp,开场看A,题意把我搞晕了好久,后来冷静了一下15min1A
,之后看B,一眼二分,check写错wa了一次,26min2A
,之后看C,发现就是个暴力,42min1A
,来到D,发现做过,开始写,奈何建边的时候边权写错,浪费了20分钟找这个bug实在不应该,之后数组开小RE一发,也实在是不应该,最后82min2A
最后看E,一眼字典树,但是感觉不好实现,就慢慢写,最后一分钟写完,数组开小,再交,119min2A
这场比赛完全暴露了编译原理考完变成智障的事实,两个数组开小,还有A题慌张,都是不应该的,下次注意吧。
A. The Fair Nut and Elevator
题意
给你n个楼层的用户个数,每一个用户每天上下楼两次,电梯运转是这样的,有一个基础位置x,用户想从a->b的话,电梯的运转是这样的,x->a->b->x,问x选在哪个楼层能使电梯的行驶距离最短。
做法
因为n<=100,暴力枚举就可以。
代码
#include<stdio.h>
#include<iostream>
using namespace std;
const int maxn = 1e5+5;
int a[maxn];
int main()
{
int n;
scanf("%d",&n);
for(int i=1;i<=n;i++) scanf("%d",&a[i]);
int ans=INF;
for(int i=1;i<=n;i++)
{
int sum=0;
for(int j=1;j<=n;j++)
{
int tmp=0;
tmp=tmp+i+j-2;
tmp+=abs(i-j);
sum+=a[j]*tmp;
}
ans=min(ans,sum*2);
}
printf("%d\n",ans);
return 0;
}
B. Kvass and the Fair Nut
题意
题意就是给你n个杯子,每个杯子中有 v i v_i vi升水,从中倒出s剩水,尽量让剩下杯子中的水的最小值最大
做法
最小值最大,这明显的二分,所以写个二分+check就可以了。
要注意check的时候如果有杯子容量本来就小于当前mid,直接return false
代码
#include<stdio.h>
#include<iostream>
#include<algorithm>
using namespace std;
typedef long long ll;
const int maxn = 1e5+5;
ll v[maxn];
int n;
ll s;
bool check(ll mid)
{
ll sum=0;
for(int i=1;i<=n;i++)
{
if(v[i]>=mid) sum+=v[i]-mid;
else return false;
}
if(sum>=s) return true;
else return false;
}
int main()
{
scanf("%d%lld",&n,&s);
for(int i=1;i<=n;i++) scanf("%lld",&v[i]);
ll l=0,r=1000000000,mid;
while(l<=r)
{
mid=(l+r)/2;
if(check(mid)) l=mid+1;
else r=mid-1;
}
printf("%lld\n",r);
return 0;
}
C. The Fair Nut and String
题意
给你一个字符串,现在统计子序列的个数,满足
1.子序列所有元素都是’a’
2.子序列中任意两个连续的a,在原序列中,这两个a之间必须有一个b
做法
我们发现字符串中只有ab是有意义的字符,所以字符串就可以看成
一段a,b,一段a,一段b,一段a,b,一段a,b…
对于某一段a,他可以和之前每一段a组合,所以我们正向算
用pre表示之前有多少合法的子序列,num表示当前段有多少个a
那么pre*num就是以这段为结尾的方案数,之后更新pre,
这样最后统计以每一段结尾的方案数就是答案。
代码
#include<stdio.h>
#include<iostream>
#include<algorithm>
#include<set>
#include<vector>
#include<string.h>
using namespace std;
typedef long long ll;
const int maxn = 1e5+5;
const int Mod=1000000007;
char str[maxn];
vector<ll> v;
int main()
{
scanf("%s",str);
int len=strlen(str);
for(int i=len-1;i>=0;i--)
{
if(str[i]=='a')
{
ll sum=0;
while(i>=0&&str[i]!='b')
{
if(str[i--]=='a') sum++;
}
v.push_back(sum);
}
}
ll ans=0,pre=0;
for(int i=0;i<v.size();i++)
{
ans=(ans+v[i]+v[i]*pre%Mod)%Mod;
pre=ans;
}
printf("%lld\n",ans);
return 0;
}
D. The Fair Nut and the Best Path
题意
给你一棵树,在树中找出一条路径(也可以只有一个点),
这条路径(点权和-边权和)最大。
做法
我们设dp[i]为以i为出发点向子树方向的最优路径,
那么我们可以很轻松的用儿子的dp数组更新父亲的dp数组
也就是取一个最大的dp[i]-w[v],w[v]表示父亲与这个儿子之间的路径权值
我们有了dp数组,我们发现,对于某个点,如果答案在这个点的子树内而且经过这个点,答案要么是从这个点出发的一条向子树的路径,要么是从某个子树到这个点再到另一颗子树。对于第一种情况,答案很显然就是dp[i]中的最大值,第二种情况,就对每个点在他的儿子中选两个最优的连接起来,同样取所有点的最大值就可以。
比如上图,经过rt的而且在他的子树内的最优路径,一定是在所有的
D
P
[
i
]
−
w
i
DP[i]-w_i
DP[i]−wi中选出两个最大而且大于0的值,连接起来。就这样不断从叶子向上更新就可以得到答案。
代码
#include<stdio.h>
#include<iostream>
#include<algorithm>
#include<vector>
#include<string.h>
using namespace std;
typedef long long ll;
const int maxn = 5e5+5;
vector<int> G[maxn];
vector<ll> val[maxn];
ll w[maxn];
ll dp[maxn];
int n,u,v;
ll we,ans;
vector<ll> tmp;
bool cmp(ll a,ll b)
{
return a>b;
}
void dfs(int rt,int fa)
{
dp[rt]=w[rt];
for(int i=0;i<G[rt].size();i++)
{
int to=G[rt][i];
ll va=val[rt][i];
if(to==fa) continue ;
dfs(to,rt);
dp[rt]=max(dp[rt],w[rt]+dp[to]-va);
}
ans=max(ans,dp[rt]);
tmp.clear();
for(int i=0;i<G[rt].size();i++)
{
int to=G[rt][i];
ll va=val[rt][i];
if(to==fa) continue ;
tmp.push_back(dp[to]-va);
}
sort(tmp.begin(),tmp.end(),cmp);
ll tt=w[rt];
if(tmp.size()>=2)
{
if(tmp[0]>=0) tt+=tmp[0];
if(tmp[1]>=0) tt+=tmp[1];
}
ans=max(ans,tt);
}
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++) scanf("%lld",&w[i]);
for(int i=1;i<=n-1;i++)
{
scanf("%d%d%lld",&u,&v,&we);
G[u].push_back(v);
G[v].push_back(u);
val[u].push_back(we);
val[v].push_back(we);
}
dfs(1,-1);
printf("%lld\n",ans);
return 0;
}
E. The Fair Nut and Strings
题意
给你两个只含ab的长度为n的字符串,让你在字典序在这两个之间的字符串中找出k个字符串,使这k个字符串有最多的不同前缀,输出不同的前缀个数。
做法
把ab考虑为01,而且还要考虑前缀,很显然可以想到01字典树
我们把字典树画出来一看,
如果我们要查011和110之间的字符串,我们发现就是这样一个区域
很明显我们只要统计每个长度的前缀有多少种就可以,
也就是从第一层开始每一层有多少个节点,选的时候尽量选层数靠后的就可以
最开始的写法不太好写,后来优化了一下就变成了下面这么短
代码
#include<stdio.h>
#include<iostream>
using namespace std;
typedef long long ll;
const int maxn = 5e5+5;
char str1[maxn],str2[maxn];
int main()
{
int n,k;
scanf("%d%lld",&n,&k);
scanf("%s",str1);
scanf("%s",str2);
ll ans=0,pre=0;
for(int i=0;i<n;i++)
{
pre=min(pre*2+(int)(str2[i]-str1[i]),1LL<<40);
ans+=min(pre+1,(ll)k);
}
printf("%lld\n",ans);
return 0;
}