AtCoder Beginner Contest 376
AtCoder Beginner Contest 376
A - Candy Button
有一个人按若干次按钮,如果距离上次得分的时间超过\(C\),那么就会获得一颗糖。
给出这个人按按钮的时刻,回答最终会获得有多少糖。
模拟题
#include<iostream>
#include<cstdio>
using namespace std;
int n,c,a,ans;
int main()
{
cin>>n>>c;
for(int i=1,lt=-c;i<=n;++i)
{
cin>>a;
if(a-lt>=c)++ans,lt=a;
}
cout<<ans<<endl;
return 0;
}
B - Hands on Ring (Easy)
一个环被分成\(n\)个区域,一开始左手在区域\(1\),右手在区域\(2\),每次可以把一只手移动到一个相邻区域,但是任何时候两只手都不能在同一个区域。
现在给出Q个操作,每个操作给定手和一个位置,表示在不动另一只手的情况下将给定手移动到给定位置。
回答最小的总操作次数。
模拟题,判断是顺时针移动还是逆时针移动就好了。
#include<iostream>
#include<cstdio>
using namespace std;
int n,Q;
int nL,nR;
int p,ans;
char c;
int check(int a,int b,int c)
{
//顺时针
if(a<=c&&c<=b)return c-a;
if(a>b&&a<=c+n&&c+n<=b+n)return c+n-a;
if(a<=c&&c<=b+n&&a>=b)return c-a;
//逆时针
if(a>=c)return a-c;
else return a+n-c;
}
int main()
{
cin>>n>>Q;
nL=1,nR=2;
while(Q--)
{
cin>>c>>p;
if(c=='L')ans+=check(nL,nR,p),nL=p;
else ans+=check(nR,nL,p),nR=p;
}
cout<<ans<<endl;
return 0;
}
C - Prepare Another Box
给出N个物品,第\(i\)个物品的体积是\(a_i\)。现在有\(N-1\)个背包,第\(i\)个背包的容量是\(b_i\),现在希望再加一个包,使得每个玩具都能放到一个容量大于其体积的背包中,每个背包只能放一个物品。
问加的这个包的容量的最小值是多少。
假设已经有\(n\)个包,判断很方便,两个数组分别排序之后检查对应位置大小关系即可。
再加一个二分答案即可求出最小值。
#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
int read(){int x;scanf("%d",&x);return x;}
#define MAX 200200
int n,a[MAX],b[MAX],c[MAX];
bool check(int x)
{
for(int i=1;i<n;++i)c[i]=b[i];
c[n]=x;
sort(&c[1],&c[n+1]);
cout<<endl;
for(int i=1;i<=n;++i)
if(a[i]>c[i])return false;
return true;
}
int main()
{
n=read();
for(int i=1;i<=n;++i)a[i]=read();
for(int i=1;i<n;++i)b[i]=read();
sort(&a[1],&a[n+1]);
int l=1,r=a[n],ans=-1;
while(l<=r)
{
int mid=(l+r)>>1;
if(check(mid))ans=mid,r=mid-1;
else l=mid+1;
}
cout<<ans<<endl;
return 0;
}
D - Cycle
给定一个有向图,问是否存在一个包含节点\(1\)的环。
如果存在,求出最小的环的长度。
正图反图分别求一次到\(1\)的最短路。
对应点加起来就是包含这个点和\(1\)的最小环。
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<queue>
using namespace std;
#define MAX 200200
inline int read()
{
int x=0,t=1;char ch=getchar();
while((ch<'0'||ch>'9')&&ch!='-')ch=getchar();
if(ch=='-')t=-1,ch=getchar();
while(ch<='9'&&ch>='0')x=x*10+ch-48,ch=getchar();
return x*t;
}
int n,m,ans;
struct Graph
{
int h[MAX],cnt;
struct Edge{int v,next;}e[MAX<<1];
void Add(int u,int v){e[++cnt]=(Edge){v,h[u]};h[u]=cnt;}
int dis[MAX];
bool vis[MAX];
priority_queue<pair<int,int>,vector<pair<int,int> >,greater<pair<int,int> > >Q;
void Dijkstra()
{
for(int i=2;i<=n;++i)dis[i]=n+1;
Q.push(make_pair(dis[1]=0,1));
pair<int,int> u;
while(!Q.empty())
{
u=Q.top();Q.pop();
if(vis[u.second])continue;
vis[u.second]=true;
dis[u.second]=u.first;
for(int i=h[u.second];i;i=e[i].next)
{
int v=e[i].v;
if(vis[v])continue;
Q.push(make_pair(u.first+1,v));
}
}
}
}T1,T2;
int main()
{
n=read();m=read();
for(int i=1;i<=m;++i)
{
int u=read(),v=read();
T1.Add(u,v);
T2.Add(v,u);
}
T1.Dijkstra();
T2.Dijkstra();
ans=n+1;
for(int i=2;i<=n;++i)ans=min(ans,T1.dis[i]+T2.dis[i]);
if(ans>n)puts("-1");
else printf("%d\n",ans);
return 0;
}
E - Max × Sum
有两个长度为\(n\)的数组\(A\)和\(B\)。
现在需要选出一个大小为\(k\)的下标集合\(S\),最小化\((\max_{i\in S}A_i)\times (\sum_{i\in S }B_i)\)
按照A排序,这样子枚举就可以确定最大值。
要最小化目标式,左边的max固定,所以要最小化右边的求和,因此选择最小的\(B\)就行了。
拿一个堆维护一下就解决了。
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<queue>
using namespace std;
#define MAX 200200
#define ll long long
inline int read()
{
int x=0,t=1;char ch=getchar();
while((ch<'0'||ch>'9')&&ch!='-')ch=getchar();
if(ch=='-')t=-1,ch=getchar();
while(ch<='9'&&ch>='0')x=x*10+ch-48,ch=getchar();
return x*t;
}
int T,K,n;
pair<int,int> A[MAX];
priority_queue<int> Q;
ll ans,sum;
int main()
{
T=read();
while(T--)
{
n=read();K=read();
for(int i=1;i<=n;++i)A[i].first=read();
for(int i=1;i<=n;++i)A[i].second=read();
sort(&A[1],&A[n+1]);
ans=1e18;sum=0;
while(!Q.empty())Q.pop();
for(int i=1;i<=K-1;++i)Q.push(A[i].second),sum+=A[i].second;
for(int i=K;i<=n;++i)
{
sum+=A[i].second;
ans=min(ans,sum*A[i].first);
Q.push(A[i].second);
sum-=Q.top();
Q.pop();
}
cout<<ans<<endl;
}
return 0;
}
F - Hands on Ring (Hard)
一个环被分成\(n\)个区域,一开始左手在区域\(1\),右手在区域\(2\),每次可以把一只手移动到一个相邻区域,但是任何时候两只手都不能在同一个区域。
现在给出Q个操作,每个操作给定手和一个位置,表示在可以移动另一只手的情况下将给定手移动到给定位置。
回答最小的总操作次数。
考虑dp,但是发现我们需要同时存在左右手的位置。
但是注意到每个条件执行完之后我们已经知道了一个手的位置,所以只需要知道另一个手的位置就好了。
设\(f[i][j]\)表示执行完第\(i\)步之后,没确定位置的那只手的位置是\(j\)。
考虑转移,要么像\(B\)题一样不动另一只手,判断顺时针还是逆时针即可。否则要移动这只手,一定是恰好移动到目标位置的下一个位置,然后让目标手移动过来。(贪心的想想,移动更多的位置只是单纯的为了后续的操作,那么不影响我先移动完这次操作再动另一只手)
#include<iostream>
#include<cstdio>
using namespace std;
#define MAX 3030
int n,Q;
int p[MAX],ans;
char c[MAX],s[10];
int f[MAX][MAX];
int check(int a,int b,int c)
{
if(a==c)return 0;
if(b==c)return 1e7;
//顺时针
if(a<=c&&c<=b)return c-a;
if(a>b&&a<=c+n&&c+n<=b+n)return c+n-a;
if(a<=c&&c<=b+n&&a>=b)return c-a;
//逆时针
if(a>=c)return a-c;
else return a+n-c;
}
int main()
{
cin>>n>>Q;
for(int i=1;i<=Q;++i)
{
scanf("%s%d",s,&p[i]);
c[i]=s[0];
}
c[0]='L';p[0]=1;
for(int i=0;i<=Q;++i)
for(int j=1;j<=n;++j)
f[i][j]=1e7;
f[0][2]=0;
for(int i=1;i<=Q;++i)
for(int j=1;j<=n;++j)
{
int L,R;
if(c[i-1]=='L')L=p[i-1],R=j;
else L=j,R=p[i-1];
//第一种转移 非目标手不动
if(c[i]=='L')
f[i][R]=min(f[i][R],f[i-1][j]+check(L,R,p[i]));
else
f[i][L]=min(f[i][L],f[i-1][j]+check(R,L,p[i]));
//否则,非目标手移动到目标位置旁边
//顺时针的旁边
int pos=p[i]%n+1;
if(c[i]=='L')
f[i][pos]=min(f[i][pos],f[i-1][j]+check(R,L,pos)+check(L,pos,p[i]));
else
f[i][pos]=min(f[i][pos],f[i-1][j]+check(L,R,pos)+check(R,pos,p[i]));
//逆时针的旁边
pos=(p[i]==1)?n:(p[i]-1);
if(c[i]=='L')
f[i][pos]=min(f[i][pos],f[i-1][j]+check(R,L,pos)+check(L,pos,p[i]));
else
f[i][pos]=min(f[i][pos],f[i-1][j]+check(L,R,pos)+check(R,pos,p[i]));
}
ans=1e7;
for(int i=1;i<=n;++i)
ans=min(ans,f[Q][i]);
printf("%d\n",ans);
return 0;
}
G - Treasure Hunting
给定一棵\(N+1\)个节点的有根树树,\(0\)号节点是根节点。现在在\(1-N\)中有一个节点有宝藏,第\(i\)个节点有宝藏的概率是\(\frac{a_i}{\sum_{j=1}^N a_j}\)。
每次可以选一个点挖掘,一个点能够被挖掘当且仅当其所有祖先节点都已经被挖掘过。初始时根节点已经被挖掘。挖掘到宝藏时停止。
求出最小的挖掘次数的期望。
先考虑一个极端情况,如果整个树是菊花树,那么一定每次会选择概率最大的进行挖掘。
为了方便,假设我们的挖掘序列是\(\{b_1,b_2,...,b_n\}\)
那么此时的期望是\(\frac{1}{\sum_{i=1}^n a_i}\sum_{i=1}^n a_{b_i}\times i\),我们需要最小化这个结果。
这个问题是一个套路贪心。
每次考虑最大的\(a_x\),那么毫无疑问,我们一定要让\(x\)在序列中尽可能靠前的出现,那么这意味着,一旦其父亲节点被挖掘了,那么这个点一定立即跟在后面挖掘。
每次取出最大的\(a_x\)段,然后把他和其父亲节点拼在一起看成一个节点就好了。合成的新的节点的权值是该节点代表的所有点的\(a\)的平均值。
#include<iostream>
#include<cstdio>
#include<set>
using namespace std;
#define MAX 200200
#define MOD 998244353
inline int read()
{
int x=0,t=1;char ch=getchar();
while((ch<'0'||ch>'9')&&ch!='-')ch=getchar();
if(ch=='-')t=-1,ch=getchar();
while(ch<='9'&&ch>='0')x=x*10+ch-48,ch=getchar();
return x*t;
}
int fpow(int a,int b)
{
int x=1;
while(b)
{
if(b&1)x=1ll*x*a%MOD;
a=1ll*a*a%MOD;
b>>=1;
}
return x;
}
int N,fa[MAX];
int a[MAX],sum;
struct Node{int v,sz,x;}V[MAX];
bool operator<(Node a,Node b)
{
if(1ll*a.v*b.sz==1ll*b.v*a.sz)return a.x<b.x;
return 1ll*a.v*b.sz>1ll*b.v*a.sz;
}
int f[MAX];
int getf(int x){return x==f[x]?x:f[x]=getf(f[x]);}
int main()
{
int T=read();
while(T--)
{
N=read();sum=0;
for(int i=1;i<=N;++i)fa[i]=read();
for(int i=1;i<=N;++i)sum+=(a[i]=read());
for(int i=1;i<=N;++i)V[i]=(Node){a[i],1,i};
V[0]=(Node){-(int)1e8,1,0};
set<Node> S;S.clear();
for(int i=1;i<=N;++i)S.insert(V[i]),f[i]=i;
int ans=0;
for(int i=1;i<=N;++i)
{
int x=(*S.begin()).x;
S.erase(V[x]);
int y=getf(fa[x]);
S.erase(V[y]);
f[x]=y;
ans=(ans+1ll*V[y].sz*V[x].v)%MOD;
V[y].sz+=V[x].sz;
V[y].v+=V[x].v;
S.insert(V[y]);
}
int inv=fpow(sum,MOD-2);
ans=1ll*ans*inv%MOD;
printf("%d\n",ans);
}
return 0;
}