2019牛客暑期多校赛(第四场)
A题 https://ac.nowcoder.com/acm/contest/884/A
题意:给定n个点和n-1条边构成一颗树,k个人,最后一行给出k个人的位置来,问所有人到达树的一点会面所用的最短时间是什么,所需最短时间是每个人到达那个地方的最长时间。
题解:类似求树的直径,从所给的人的位置为起点,然后求到另一个给的人的位置的最大值,找到最大值的这个位置再搜一遍最大值,这个距离再除2向上取整就是答案了。
官方题解:
代码:
#include<bits/stdc++.h>
using namespace std;
typedef long long int ll;
typedef unsigned long long int ull;
const int inf = 0x3f3f3f3f;
int dir[8][2]={{1,0},{0,1},{1,1},{1,-1},{-1,1},{-1,-1},{0,-1},{-1,0}};
#define pi acos(-1)
#define ls rt<<1
#define rs rt<<1|1
#define me0(s) memset(s,0,sizeof(s))
#define me1(s) memset(s,1,sizeof(s))
#define mef(s) memset(s,-1,sizeof(s))
#define meinf(s) memset(s,inf,sizeof(s))
const int N=200005;
inline int read() {
char c=getchar(); int x=0, f=1;
while(c<'0'|c>'9') {if(c=='-') f=-1;c=getchar();}
while(c>='0'&&c<='9') x=x*10+c-'0',c=getchar();
return x*f;
}
ll q_pow(ll a,ll b,ll mod){
ll anss=1;
while(b){
if(b&1) anss=anss*a%mod;
a=a*a%mod;
b>>=1;
}
return anss;
}
ll q_mul(ll a,ll b,ll mod){
ll anss=0;
while(b){
if(b&1) anss=(anss+a)%mod;
a=(a+a)%mod;
b>>=1;
}
return anss;
}
struct node{
int v,next;
}edge[N];
int cnt,fir[N],vis1[N],a[N];
void init(){
cnt=0;
mef(fir);
}
void add(int u,int v){
edge[cnt].v=v;
edge[cnt].next=fir[u];
fir[u]=cnt++;
}
int bfs(int x,int y){
int vis[N],ans1=x,ans2=0;
me0(vis);
queue<pair<int,int> > q;
q.push(make_pair(x,0));
vis[x]=1;
while(!q.empty()){
int lx=q.front().first,ly=q.front().second;
if(vis1[lx]){
ans1=lx;
ans2=ly;
}
q.pop();
for(int i=fir[lx];i!=-1;i=edge[i].next){
int lv=edge[i].v;
if(!vis[lv]){
q.push(make_pair(lv,ly+1));
vis[lv]=1;
}
}
}
if(!y) return ans1;
else return ans2;
}
int main(int argc, char * argv[]){
ios::sync_with_stdio(false);
int n,k;
cin>>n>>k;
init();
for(int i=1;i<=n-1;i++){
int a,b;
cin>>a>>b;
add(a,b);
add(b,a);
}
me0(vis1);
for(int i=1;i<=k;i++){
cin>>a[i];
vis1[a[i]]=1;
}
int re=bfs(a[1],0);
re=bfs(re,1);
cout<<re/2+re%2<<endl;
return 0;
}
B题 https://ac.nowcoder.com/acm/contest/884/B
好像说是线段树加线性基的交,还不会,先码着,学了来补
C题 https://ac.nowcoder.com/acm/contest/884/C
题意:给定两个长度为n的数组a和b,求a数组任意一区间的最小值乘b数组相同区间和的最大值是多少。其中a,b包含负数(重点)。
题解:和南昌网络邀请赛题目一样,只不过是用前缀和存b数组就是了。
一开始时限1s会卡线段树,后面抬一手时限放了3s线段树也能过了。
官方题解:
代码:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
int dir[8][2]={{1,0},{0,1},{1,1},{1,-1},{-1,1},{-1,-1},{0,-1},{-1,0}};
#define pi acos(-1)
#define ls rt<<1
#define rs rt<<1|1
#define inf 1e18
#define me0(s) memset(s,0,sizeof(s))
#define me1(s) memset(s,1,sizeof(s))
#define mef(s) memset(s,-1,sizeof(s))
#define meinf(s) memset(s,inf,sizeof(s))
const int N=3e6+5;
ll a[N],b[N],sum[N],Min[N<<2],Max[N<<2];
stack <ll> s;
void pushup(int rt){
Max[rt]=max(Max[ls],Max[rs]);
Min[rt]=min(Min[ls],Min[rs]);
}
void build(int l,int r,int rt){
if(l==r){
Max[rt]=Min[rt]=sum[l];
return ;
}
int m=(l+r)>>1;
build(l,m,ls);
build(m+1,r,rs);
pushup(rt);
}
ll query_max(int L,int R,int l,int r,int rt){
if(l<=L&&r>=R){
return Max[rt];
}
ll ans=-inf;
int m=(L+R)>>1;
if(l<=m) ans=max(ans,query_max(L,m,l,r,ls));
if(r>m) ans=max(ans,query_max(m+1,R,l,r,rs));
return ans;
}
ll query_min(int L,int R,int l,int r,int rt){
if(l<=L&&r>=R){
return Min[rt];
}
ll ans=inf;
int m=(L+R)>>1;
if(l<=m) ans=min(ans,query_min(L,m,l,r,ls));
if(r>m) ans=min(ans,query_min(m+1,R,l,r,rs));
return ans;
}
int n;
int l[N],r[N];
int main(int argc, char * argv[])
{
ios::sync_with_stdio(false);
cin>>n;
for(int i=1;i<=n;i++){
cin>>a[i];
}
for(int i=1;i<=n;i++){
cin>>b[i];
sum[i]=sum[i-1]+b[i];
}
build(1,n,1); //线段树存的是区间和的值
for(int i=1;i<=n;i++){
while(s.size()&&a[s.top()]>=a[i]) s.pop();
if(s.size()) l[i]=s.top()+1;
else l[i]=1;
s.push(i);
}
while(s.size()) s.pop();
for(int i=n;i>=1;i--){
while(s.size()&&a[s.top()]>=a[i]) s.pop();
if(s.size()) r[i]=s.top()-1;
else r[i]=n;
s.push(i);
} //左右第一个比他大的地方
ll ans=-inf;
for(int i=1;i<=n;i++){
if(a[i]>=0) ans=max(ans,(sum[r[i]]-sum[l[i]-1])*a[i]);
else{
ll maxn=query_max(1,n,max(l[i]-1,1),max(i-1,1),1);
if(l[i]==1&&maxn<0) maxn=0;
ll minn=query_min(1,n,i,r[i],1);
ans=max(ans,(minn-maxn)*a[i]);
}
}
cout<<ans<<endl;
return 0;
}
D题 https://ac.nowcoder.com/acm/contest/884/D
题意:给定一个数a,要求构造最少个数的3的倍数使得这些数的按位或的和为a。
官方题解:
代码:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
int dir[8][2]={{1,0},{0,1},{1,1},{1,-1},{-1,1},{-1,-1},{0,-1},{-1,0}};
#define pi acos(-1)
#define ls rt<<1
#define rs rt<<1|1
#define me0(s) memset(s,0,sizeof(s))
#define me1(s) memset(s,1,sizeof(s))
#define mef(s) memset(s,-1,sizeof(s))
#define meinf(s) memset(s,inf,sizeof(s))
#define inf 1e18
const int N=105;
ll f1[N],f2[N];
int a[N];
int main(int argc, char * argv[])
{
ios::sync_with_stdio(false);
int t;
cin>>t;
while(t--){
ll n;
cin>>n;
if(n%3==0) cout<<1<<" "<<n<<endl;
int len=0;
ll m=n;
while(m){
a[++len]=m%2;
m/=2;
}
ll x=1;
int ans1=0;
int ans2=0;
for(int i=1;i<=len;i++){
if(a[i]){
if(x%3==1){
f1[++ans1]=x;
}
else if(x%3==2){
f2[++ans2]=x;
}
}
x=x*2;
}
if(n%3==1){
if(ans1>=2){
cout<<2<<" ";
cout<<n-f1[1]<<" "<<n-f1[2]<<endl;
}
else if(ans1==1){
cout<<2<<" ";
cout<<n-f1[1]<<" "<<f1[1]+f2[1]<<endl;
}
else if(ans2>=3){
cout<<2<<" ";
cout<<f2[1]+f2[2]+f2[3]<<" "<<n-f2[1]-f2[2]<<endl;
}
}
else if(n%3==2){
if(ans2>=2){
cout<<2<<" ";
cout<<n-f2[1]<<" "<<n-f2[2]<<endl;
}
else if(ans2==1){
cout<<2<<" ";
cout<<n-f2[1]<<" "<<f2[1]+f1[1]<<endl;
}
else if(ans1>=3){
cout<<2<<" ";
cout<<f1[1]+f1[2]+f1[3]<<" "<<n-f1[1]-f1[2]<<endl;
}
}
}
return 0;
}
J题 https://ac.nowcoder.com/acm/contest/884/J
题意:n个点m条边,从点s到t的路径中,选择最短路,其中你可以将任意k条边的花费变为0,问最小花费是多少
题解:最短路,队友拍的,不懂。
官方题解:
代码:
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
#include<queue>
#include<cmath>
using namespace std;
#define LL long long
const int MOD=1e9+7;
const int inf=0x3f3f3f3f;
const LL inff=0x3f3f3f3f3f3f3f3f;
const LL MAX_N=10005;
const LL MAX_M=100005;
#define MEF(x) memset(x,-1,sizeof(x))
#define ME0(x) memset(x,0,sizeof(x))
#define MEI(x) memset(x,inf,sizeof(x))
struct Node
{
LL i,k,dis;
bool operator <(const Node &a)const
{
return dis>a.dis;
}
};
struct Edge
{
LL v,w,next;
}edge[MAX_M];
LL dis[MAX_N][15];
LL first[MAX_N],cnt,vis[MAX_N][15];
void add(LL u,LL v,LL w)
{
edge[cnt].v=v;
edge[cnt].w=w;
edge[cnt].next=first[u];
first[u]=cnt++;
}
void dijstra(LL s,LL t,LL k)
{
priority_queue<Node> pq;
MEI(dis);
ME0(vis);
Node l;
l.i=s;
l.k=0;
l.dis=0;
dis[s][0]=0;
pq.push(l);
while(!pq.empty())
{
l=pq.top();
pq.pop();
LL li=l.i,lk=l.k;
if(vis[li][lk])
{
continue;
}
vis[li][lk]=1;
for(LL i=first[li];i!=-1;i=edge[i].next)
{
LL lv=edge[i].v;
if(!vis[lv][lk]&&dis[lv][lk]>dis[li][lk]+edge[i].w)
{
dis[lv][lk]=dis[li][lk]+edge[i].w;
l.i=lv;
l.k=lk;
l.dis=dis[lv][lk];
pq.push(l);
}
if(lk+1<=k&&!vis[lv][lk+1]&&dis[lv][lk+1]>dis[li][lk])
{
dis[lv][lk+1]=dis[li][lk];
l.i=lv;
l.k=lk+1;
l.dis=dis[lv][lk+1];
pq.push(l);
}
}
}
LL ans=dis[t][k];
for(int i=0;i<=k;i++)
{
ans=min(dis[t][i],ans);
}
printf("%lld\n",ans);
}
int main()
{
LL n,m,k,st,ed,a,b,c;
scanf("%lld%lld%lld%lld%lld",&n,&m,&st,&ed,&k);
cnt=0;
MEF(first);
for(LL m1=1;m1<=m;m1++)
{
scanf("%lld%lld%lld",&a,&b,&c);
add(a,b,c);
add(b,a,c);
}
dijstra(st,ed,k);
return 0;
}
K题: https://ac.nowcoder.com/acm/contest/884/K
题意:给定一串字符,求字符子串能构成300倍数出现的次数,0,00,000以及前导零都算。
题解:比赛时候刷刷刷的过人,我们辛辛苦苦暴力还超时,自闭。(万物皆可dp)
官方题解:
代码:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
int dir[8][2]={{1,0},{0,1},{1,1},{1,-1},{-1,1},{-1,-1},{0,-1},{-1,0}};
#define pi acos(-1)
#define ls rt<<1
#define rs rt<<1|1
#define me0(s) memset(s,0,sizeof(s))
#define me1(s) memset(s,1,sizeof(s))
#define mef(s) memset(s,-1,sizeof(s))
#define meinf(s) memset(s,inf,sizeof(s))
#define inf 1e18
const int N=1e6+6;
inline int read() {
char c=getchar(); int x=0, f=1;
while(c<'0'|c>'9') {if(c=='-') f=-1;c=getchar();}
while(c>='0'&&c<='9') x=x*10+c-'0',c=getchar();
return x*f;
}
ll exgcd(ll a,ll b){
if(b==0) return a;
exgcd(b,a%b);
}
ll q_pow(ll a,ll b,ll mod){
ll anss=1;
while(b){
if(b&1) anss=anss*a%mod;
a=a*a%mod;
b>>=1;
}
return anss;
}
ll q_mul(ll a,ll b,ll mod){
ll anss=0;
while(b){
if(b&1) anss=(anss+a)%mod;
a=(a+a)%mod;
b>>=1;
}
return anss;
}
int a[N];
int main(int argc, char * argv[])
{
ios::sync_with_stdio(false);
string s;
cin>>s;
int sum=0;
ll ans=0;
a[0]++;
for(int i=0;i<s.length();i++){
sum+=s[i]-'0';
sum%=3;
if(s[i+1]=='0'&&s[i+2]=='0') ans+=a[sum];
a[sum]++;
}
for(int i=0;i<s.length();i++){
if(s[i]=='0') ans++;
if(s[i]=='0'&&s[i+1]=='0') ans++;
}
cout<<ans<<endl;
return 0;
}