AtCoder Beginner Contest 415
AtCoder Beginner Contest 415
A - Unsupported Type
给定一个整数序列\(A\),包含\(N\)个元素。
给定一个整数\(X\),回答\(X\)是否包含在\(A\)中。
直接扫一遍序列检查是否包含X即可。
#include<iostream>
#include<cstdio>
using namespace std;
int n,x,a[105];
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;++i)
scanf("%d",&a[i]);
scanf("%d",&x);
for(int i=1;i<=n;++i)
if(a[i]==x)
{
puts("Yes");
return 0;
}
puts("No");
return 0;
}
B - Pick Two
有一排仓库,里面存放着偶数个包裹。
给出一个字符串\(S\)表示每个仓库里面的存放情况,如果第\(i\)位是
#表示\(i\)号仓库里有一个包裹,如果是.则表示\(i\)号仓库里没有包裹。仓库里有一个机器人,会一直重复如下的工作,直到仓库里没有包裹:
- 每次从两个编号最小的且有包裹的仓库中将包裹取出仓库
输出每次被取走包裹的仓库编号
扫一遍字符串,用一个标记表示当前是 第奇数个包裹还是第偶数个包括,并记录上一个包裹的编号。
如果是第偶数个包裹的话,将其直接输出即可。
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
string S;
int lst,fl;
int main()
{
cin>>S;
for(int i=0,l=S.length();i<l;++i)
if(S[i]=='#')
{
if(fl)
{
printf("%d,%d\n",lst+1,i+1);
fl=false;
}
else
fl=true,lst=i;
}
return 0;
}
C - Mixture
有\(N\)种物质,你希望将他们都混合在一起。
给定一个长度为\(2^N-1\)的01串,对于第\(i\)位:
- 如果是\(0\),则表示\(i\)的二进制表示下为\(1\)的这些元素(自低位向高位编号),他们混合起来是安全的
- 如果是\(1\),则表示\(i\)的二进制表示下为\(1\)的这些元素(自低位向高位编号),他们混合起来是危险的
混合操作如下:
- 一开始有一个空瓶子
- 选择一种目前没有加入到瓶子中的物质,并把他加入到瓶子中。必须保证加入完后是安全的。
回答是否存在一种安全的操作,能让所有的物质混合在一起。
本质上是个状压dp。设\(f[i]\)表示是否存在一种方法,能够将\(i\)集合中的物质给混合在一起。如果\(S[i]=1\),那么显然\(f[i]=false\)。
否则的话,枚举\(i\)的一个元素\(j\),即\(i\&(1<<j)\)不为\(0\),检查\(f[i\oplus (1<<j)]\)是否为\(true\),如果是那么\(f[i]=true\)。
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
char S[(1<<18)+10];
bool f[(1<<18)+10];
int n;
int main()
{
int T;
scanf("%d",&T);
while(T--)
{
scanf("%d",&n);
scanf("%s",S+1);
S[0]='0';
f[0]=true;
int len=strlen(S+1);
for(int i=1;i<=len;++i)
{
f[i]=false;
if(S[i]=='1')continue;
for(int j=0;j<n;++j)
if(i&(1<<j))
if(f[i^(1<<j)])
f[i]=true;
}
puts(f[len]?"Yes":"No");
}
return 0;
}
D - Get Many Stickers
一家商店提供空可乐瓶对换可乐服务。
一开始有\(n\)瓶可乐,接下来可以执行以下操作若干次:
- 喝掉一瓶可乐,可乐数量减一,空瓶数量加一。
- 选择一个\(1-M\)之间的正整数\(i\)。给商店\(A_i\)个空瓶子,获得\(B_i\)瓶可乐,并获得一张纪念票。
回答最终能获得多少张纪念票。
首先拜托一个绕弯弯,只有空瓶子有用,所以操作一直接忽略掉即可,整个过程看成空瓶子换空瓶子+纪念票即可。
显而易见,我们希望每次操作完之后,剩余的瓶子数量尽可能的多,因此我们每次都会选择操作完之后,既能获得纪念票,又要保留最大的空瓶子数量的操作方案。
因此将所有的操作方案按照\(A_i-B_i\)排序,每次一直换到不能使用这种操作方案为止。
考虑到数据范围,不能模拟,需要计算一下操作方案的具体次数。
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<queue>
using namespace std;
#define ll long long
inline ll read()
{
ll x=0;bool t=false;char ch=getchar();
while((ch<'0'||ch>'9')&&ch!='-')ch=getchar();
if(ch=='-')t=true,ch=getchar();
while(ch<='9'&&ch>='0')x=x*10+ch-48,ch=getchar();
return t?-x:x;
}
struct Exchange{ll A,B;};
bool operator<(Exchange a,Exchange b)
{
ll ca=a.A-a.B;
ll cb=b.A-b.B;
if(ca!=cb)
return ca>cb;
return a.A>a.B;
}
priority_queue<Exchange> Q;
ll ans,n,m;
int main()
{
n=read();m=read();
for(int i=1;i<=m;++i)
{
ll a=read(),b=read();
Q.push((Exchange){a,b});
}
while(!Q.empty())
{
Exchange a=Q.top();
if(n<a.A)
{
Q.pop();
continue;
}
ll times=1+(n-a.A)/(a.A-a.B);
ans+=times;
n-=times*(a.A-a.B);
}
cout<<ans<<endl;
return 0;
}
E - Hungry Takahashi
有一个H行W列的网格图。有些格子内放着金币,格子\((i,j)\)中放了\(A_{i,j}\)个金币
一开始位于\((1,1)\)并拥有\(x\)个金币。接下来的\(H+W-1\)天,每天会发生一些事,假设当前是第\(k\)天:
- 收集当前所在格子上的金币
- 花费\(P_k\)个金币购买食物,如果金币不足就会饿倒
- 如果\(k<H+W-1\),可以选择向右或者向下走一步,但是不能走出网格图,如果到了最后一天,就不再移动。
如果希望不被饿倒的话,那么初始金币\(x\)最少是多少。
不难发现我们要求的就是中间金币最小值(可以是负数)的最大值,这个很难用\(dp\)直接处理。
但是如果我们已知\(x\),可以很容易的用一个\(dp\)检查是否可以走到终点。
而这题很明显具有单调性,如果初始\(x\)个金币可以,那么\(x+1\)个金币也必定可以。
所以二分\(x\),用简单\(dp\)检查是否能够走到终点。
#include<iostream>
#include<cstdio>
#include<vector>
using namespace std;
inline int read()
{
int x=0;bool t=false;char ch=getchar();
while((ch<'0'||ch>'9')&&ch!='-')ch=getchar();
if(ch=='-')t=true,ch=getchar();
while(ch<='9'&&ch>='0')x=x*10+ch-48,ch=getchar();
return t?-x:x;
}
const int MAX=200200;
int H,W;
vector<int> A[MAX];
vector<long long> f[MAX];
int P[MAX];
bool check(long long x)
{
for(int i=1;i<=H;++i)
for(int j=1;j<=W;++j)
f[i][j]=-1e18;
f[1][1]=x;
for(int i=1;i<=H;++i)
for(int j=1;j<=W;++j)
{
int d=i+j-1;
f[i][j]+=A[i][j];
f[i][j]-=P[d];
if(f[i][j]>=0)
{
if(i+1<=H)
f[i+1][j]=max(f[i+1][j],f[i][j]);
if(j+1<=W)
f[i][j+1]=max(f[i][j+1],f[i][j]);
}
}
return f[H][W]>=0;
}
int main()
{
H=read();W=read();
for(int i=1;i<=H;++i)
{
A[i].resize(W+1);
f[i].resize(W+1);
for(int j=1;j<=W;++j)
A[i][j]=read();
}
for(int i=1;i<H+W;++i)P[i]=read();
long long L=0,R=1e16,ret;
while(L<=R)
{
long long mid=(L+R)>>1;
if(check(mid))
ret=mid,R=mid-1;
else
L=mid+1;
}
cout<<ret<<endl;
return 0;
}
F - Max Combo
有一个长度为\(N\)的字符串\(S\),由小写字母组成。
执行\(Q\)次操作:
- Type1:将\(S\)的第\(i\)个字符修改成\(x\)
- Type2:假设\(t\)是\(S[L,R]\)构成的子串,求\(f(t)\)。
其中\(f(t)\)是\(t\)中的最长的连续相同字符的长度。
很明显的线段树处理。
每个节点维护如下内容:左端连续最大相同字符和长度,右端连续最大相同字符和长度,维护的区间的答案。
每次合并的时候,两个子树答案从:左子树的右侧和右子树的左侧是否能够拼接,以及两个区间的答案中取最大值,并相应的维护上述内容即可。
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<algorithm>
using namespace std;
inline int read()
{
int x=0;bool t=false;char ch=getchar();
while((ch<'0'||ch>'9')&&ch!='-')ch=getchar();
if(ch=='-')t=true,ch=getchar();
while(ch<='9'&&ch>='0')x=x*10+ch-48,ch=getchar();
return t?-x:x;
}
const int MAX=500500;
char S[MAX];
int n,Q;
struct Node
{
char char_l,char_r;
int len_l,len_r;
int maxlen;
int len;
}t[MAX<<2];
Node merge(Node l,Node r)
{
Node ret;
ret.len=l.len+r.len;
ret.char_l=l.char_l;
ret.char_r=r.char_r;
// len_l part
ret.len_l=l.len_l;
if(l.len==l.len_l&&r.char_l==l.char_l)
ret.len_l=l.len+r.len_l;
// len_r part
ret.len_r=r.len_r;
if(r.len==r.len_r&&l.char_r==r.char_r)
ret.len_r=r.len+l.len_r;
// maxlen part
ret.maxlen=max(l.maxlen,r.maxlen);
if(l.char_r==r.char_l)
ret.maxlen=max(ret.maxlen,l.len_r+r.len_l);
return ret;
}
#define ls (x<<1)
#define rs (x<<1|1)
void Build(int x,int l,int r)
{
if(l==r)
{
t[x].char_l=S[l];
t[x].char_r=S[r];
t[x].len=t[x].len_l=t[x].len_r=t[x].maxlen=1;
return;
}
int mid=(l+r)>>1;
Build(ls,l,mid);
Build(rs,mid+1,r);
t[x]=merge(t[ls],t[rs]);
}
void Modify(int x,int l,int r,int p)
{
if(l==r)
{
t[x]=(Node){S[l],S[l],1,1,1,1};
return;
}
int mid=(l+r)>>1;
if(p<=mid)Modify(ls,l,mid,p);
else Modify(rs,mid+1,r,p);
t[x]=merge(t[ls],t[rs]);
}
Node Query(int x,int l,int r,int L,int R)
{
if(L<=l&&r<=R)return t[x];
int mid=(l+r)>>1;
if(R<=mid)return Query(ls,l,mid,L,R);
if(L>mid)return Query(rs,mid+1,r,L,R);
return merge(Query(ls,l,mid,L,mid),Query(rs,mid+1,r,mid+1,R));
}
#undef ls
#undef rs
int main()
{
n=read();Q=read();
scanf("%s",S+1);
Build(1,1,n);
while(Q--)
{
int type=read();
if(type==1)
{
char c[10];
int p=read();
scanf("%s",c);
S[p]=c[0];
Modify(1,1,n,p);
}
else
{
int L=read(),R=read();
Node ret=Query(1,1,n,L,R);
printf("%d\n",ret.maxlen);
}
}
return 0;
}
G - Get Many Cola
一家商店提供空可乐瓶对换可乐服务。
一开始有\(n\)瓶可乐,接下来可以执行以下操作若干次:
- 喝掉一瓶可乐,可乐数量减一,空瓶数量加一。
- 选择一个\(1-M\)之间的正整数\(i\)。给商店\(A_i\)个空瓶子,获得\(B_i\)瓶可乐
回答最多能喝多少瓶可乐。
这题和D题几乎一样,但是区别是一个是兑换的次数最多,另一个是喝的可乐最多。
那么贪心就有区别了,兑换次数多,那么我们只需要保证前后空瓶子的差距最小就可以,比如我们用100个空瓶子换96个,和用10个换6个,都是完成了一次兑换,并使得空瓶子数量减少了4。但是在考虑喝了多少瓶可乐的时候是不一样的,虽然两者兑换完之后空瓶数量都减少了4,但是前者多喝了90瓶可乐。
因此在这个问题中,我们的贪心应该是兑换比例排序,也即根据\(B_i/A_i\)排序。
但是我们可以无脑这样贪心吗,也是否定的,因为当瓶子较少的时候我们是可能用一个不那么优的瓶子衰减,换来喝更多可乐的潜在机会。举个例子,我们有9个空瓶子,有两种兑换方法,一个是9个空瓶子兑换5个,另一个是用6个换3个。根据贪心,我们应该用9个换5个,然后不能继续换了,共计喝5瓶。但是如果我们用6个换3个,则可以完成两次兑换,总共多喝了1瓶。
因此在瓶子较少的时候,我们可以使用dp,这个就是一个简单的背包问题。
那么什么时候瓶子认为较少呢?注意到\(A_i\)最大不超过300,因此在总数超过300*301时可以使用贪心,这是因为我们可以考虑空瓶子数量的衰减,显然在空瓶子数量一致的时候我们喝的越多越好。那么两个方案衰减相同的瓶子数量的最少空瓶子数量是两者的最小公倍数,不会超过两者的乘积。在瓶盖数量大于最小公倍数的时候,一定是贪心的兑换方法更优。
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<queue>
using namespace std;
#define ll long long
inline ll read()
{
ll x=0;bool t=false;char ch=getchar();
while((ch<'0'||ch>'9')&&ch!='-')ch=getchar();
if(ch=='-')t=true,ch=getchar();
while(ch<='9'&&ch>='0')x=x*10+ch-48,ch=getchar();
return t?-x:x;
}
ll ans,n,m;
const int MAX=200000;
ll f[MAX+10];
ll C[350];
int main()
{
n=read();m=read();
for(int i=1;i<=m;++i)
{
ll a=read(),b=read();
C[a]=max(C[a],b);
}
for(int i=1;i<=MAX;++i)
for(int j=1;j<=300;++j)
if(C[j]>0&&i>=j)
f[i]=max(f[i],f[i-j+C[j]]+C[j]);
ans=n;
int id=1;
for(int i=2;i<=300;++i)
if(id*C[i]>i*C[id])
id=i;
if(n>MAX)
{
ll times=1+(n-MAX)/(id-C[id]);
ans+=times*C[id];
n-=times*(id-C[id]);
}
ans+=f[n];
cout<<ans<<endl;
return 0;
}

浙公网安备 33010602011771号