AtCoder Grand Contest 015
AtCoder Grand Contest 015
A - A+...+B Problem
有一个人有\(n\)个数,最小的是\(A\),最大的是\(B\),其他数位置,问一共有多少种和的可能情况。
显然能够取到的是一段连续值,那么求出最小值和最大值就行了。
#include<iostream>
using namespace std;
long long n,a,b,l,r;
int main()
{
cin>>n>>a>>b;
if(a>b){puts("0");return 0;}
l=b+(n-1)*a,r=a+(n-1)*b;
cout<<max(0ll,r-l+1)<<endl;
return 0;
}
B - Evilator
有一个电梯,有\(n\)层楼,给定了一个字符串\(S\),\(S_i\)告诉你了这个电梯在这一层只能向上走或者是向下走。
现在问你任意两层之间需要坐几次电梯,求出所有情况的和。
显然答案不是\(1\)就是\(2\),那么对于每个位置判断一下有多少个是\(1\)有多少个是\(2\)就行了。
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
int n;long long ans;
char S[100010];
int main()
{
scanf("%s",S+1);n=strlen(S+1);
for(int i=1;i<=n;++i)
if(S[i]=='U')ans+=(n-i)+2*(i-1);
else ans+=2*(n-i)+(i-1);
printf("%lld\n",ans);
return 0;
}
C - Nuske vs Phantom Thnook
有一个网格图,一些格子被涂蓝了,保证蓝色的格子构成了一棵树。
每次询问问你一个子矩形内蓝色格子构成的联通块数量。
既然是一棵树,那么联通块个数等于点数减去边数,然后就很好做了。
#include<iostream>
#include<cstdio>
using namespace std;
#define MAX 2010
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;
}
int n,m,Q;
char g[MAX][MAX];
int s[MAX][MAX],sl[MAX][MAX],su[MAX][MAX];
int Calc(int s[][MAX],int x1,int y1,int x2,int y2)
{
if(x1>x2||y1>y2)return 0;
return s[x2][y2]-s[x2][y1-1]-s[x1-1][y2]+s[x1-1][y1-1];
}
int main()
{
n=read();m=read();Q=read();
for(int i=1;i<=n;++i)scanf("%s",g[i]+1);
for(int i=1;i<=n;++i)
for(int j=1;j<=m;++j)
{
s[i][j]=(g[i][j]=='1');
sl[i][j]=(g[i][j]=='1'&&g[i][j-1]=='1');
su[i][j]=(g[i][j]=='1'&&g[i-1][j]=='1');
}
for(int i=1;i<=n;++i)
for(int j=1;j<=m;++j)
{
s[i][j]+=s[i-1][j]+s[i][j-1]-s[i-1][j-1];
sl[i][j]+=sl[i-1][j]+sl[i][j-1]-sl[i-1][j-1];
su[i][j]+=su[i-1][j]+su[i][j-1]-su[i-1][j-1];
}
while(Q--)
{
int x1=read(),y1=read(),x2=read(),y2=read();
printf("%d\n",Calc(s,x1,y1,x2,y2)-Calc(sl,x1,y1+1,x2,y2)-Calc(su,x1+1,y1,x2,y2));
}
return 0;
}
D - A or...or B Problem
由\([A,B]\)之间的数构成的非空集合的\(or\)值有多少种。
感觉很有想法,然而就是不会做,。。。
首先\(A=B\)不用管了,只考虑\(A=B\) 的情况,那么先找到二进制位下\(A,B\)不同的第一位,前面的部分可以直接丢掉,因为对于答案不产生影响。
假设最高位是\(k\),那么我们把集合分成两个部分,一半是\([A,2^k)\),另一半是\([2^k,B]\),在第一部分里面任意选数只能选出\([A,2^k)\)的值,在第二部分中选数,考虑\(B\)的次大二进制为\(l\),那么可以选的范围在\([2^k,2^k+2^l)\)之间,如果两个部分同时选的话,那么可以得到的范围是\([2^k+A,2^{k+1})\)。
这三个部分取并就是答案了。
#include<iostream>
#include<cstdio>
using namespace std;
#define ll long long
ll A,B,ans;
int main()
{
cin>>A>>B;
if(A==B){puts("1");return 0;}
int k=60;
for(;~k;--k)
{
if((A>>k&1)^(B>>k&1))break;
if(A>>k&1)A^=1ll<<k,B^=1ll<<k;
}
int l=k-1;
while((~l)&&!(B&(1ll<<l)))--l;
ll L=(!~l)?B:((1ll<<k)+(1ll<<(l+1))-1);
ll R=(1ll<<k)+A;
ans=(1ll<<(k+1))-A;
if(L+1<R)ans-=R-L-1;
printf("%lld\n",ans);
return 0;
}
E - Mr.Aoki Incubator
数轴上有\(n\)个人,第\(i\)个人一开始在\(X_i\)位置,用\(v_i\)的初速度往正方向移动。一开始有若干个人被染色,被染色的人如果碰到了未被染色的人,就把会未被染色的人给染色。
问在所有的\(2^n\)种初始的染色方案中,有多少种染色方法在足够长的时间之后能够让所有人都被染色。
首先最终的序列一定是唯一的,即所有人都按照速度的顺序依次排开,并且所有人相遇的时间也是固定的,那么唯一需要考虑的就只有染色的情况。
考虑现在只有一个点被染色,考虑哪些点会被染色。显然是位置在它左侧其速度比它快的,以及位置在它右侧并且速度比它慢的点会被追上。
把所有点按照速度从大往小排序,那么,对于一个点,在速度比它大的点中找到最靠右的那个,记做\(L\)。在速度比它小的点中找到最靠左的那个,记做\(R\) 。那么\([L,R]\)都会被染色(看起来挺容易证明的)。
于是问题等价于有\(n\)个区间,选出若干个使得他们的并恰好是全集的方案数。
那么这个东西直接\(dp\)就行了。
把所有区间按照右端点排序,设\(f[i]\)表示考虑了前\(i\)个区间,且\([1,R_i]\)都已经被覆盖的方案数。
这个东西可以用前缀和很方便的进行转移。
#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
#define MOD 1000000007
#define MAX 200200
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;
}
struct Node{int x,v;}p[MAX];
int S[MAX],top;
bool operator<(Node a,Node b){return a.v<b.v;}
struct Line{int l,r;}a[MAX];
bool operator<(Line a,Line b){if(a.r!=b.r)return a.r<b.r;return a.l<b.l;}
int f[MAX],s[MAX],ans,n;
int main()
{
n=read();
for(int i=1;i<=n;++i)p[i].x=read(),p[i].v=read();
sort(&p[1],&p[n+1]);
for(int i=1;i<=n;++i)
{
if(!top||p[i].x>p[S[top]].x)S[++top]=i;
int l=1,r=top,ret=i;
while(l<=r)
{
int mid=(l+r)>>1;
if(p[S[mid]].x>=p[i].x)r=mid-1,ret=mid;
else l=mid+1;
}
a[i].l=S[ret];
}
top=0;
for(int i=n;i>=1;--i)
{
if(!top||p[i].x<p[S[top]].x)S[++top]=i;
int l=1,r=top,ret=i;
while(l<=r)
{
int mid=(l+r)>>1;
if(p[S[mid]].x<=p[i].x)r=mid-1,ret=mid;
else l=mid+1;
}
a[i].r=S[ret];
}
sort(&a[1],&a[n+1]);
for(int i=1,p=1;i<=n;++i)
{
while(a[p].r<a[i].l-1)++p;
f[i]=(s[i-1]-s[p-1]+MOD)%MOD;
if(a[i].l==1)f[i]=(f[i]+1)%MOD;
if(a[i].r==n)ans=(ans+f[i])%MOD;
s[i]=(s[i-1]+f[i])%MOD;
}
printf("%d\n",ans);
return 0;
}
F - Kenus the Ancient Greek
给你\(n,m\),求\(x\in [1,n],y\in[1,m]\)中,让\(x,y\)做辗转相除的最大递归次数是多少。
并求出能够取到最大值的\((x,y)\)的对数。
首先最大值很容易算,不难发现就是斐波那契数列最大的相邻的两项,满足都在范围内。
考虑怎么统计答案,我们不妨令\(x\ge y\)。假设最大值是\(k\),递归次数是\(g(x,y)\),\(f_i\)为斐波那契数列第\(i\)项,且\(f_0=f_1=1\)。 那么\(g(x,y)\)的最大值为满足\(f_{k+1}\le x,f_{k}\le y\)的最大\(k\)。
剩下的部分戳这里吧
#include<iostream>
#include<cstdio>
#include<vector>
using namespace std;
#define ll long long
#define MOD 1000000007
#define pi pair<ll,ll>
#define mp make_pair
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 f[110];
vector<pi> a[110];
int main()
{
int T=read();
f[0]=f[1]=1;
for(int i=2;i<=100;++i)f[i]=f[i-1]+f[i-2];
a[1].push_back(mp(1,2));a[1].push_back(mp(1,3));a[1].push_back(mp(1,4));
for(int i=1;i<=100;++i)
for(int j=0,l=a[i].size();j<l;++j)
{
ll x=a[i][j].second,y=a[i][j].first+x;
while(y<=f[i+3]+f[i])a[i+1].push_back(mp(x,y)),y+=x;
}
while(T--)
{
ll n=read(),m=read();if(n>m)swap(n,m);
if(n==1&&m==1){puts("1 1");continue;}
int p,ans=0;for(int i=1;;++i)if(!(n>=f[i]&&m>=f[i+1])){p=i-1;break;}
printf("%d ",p);if(p==1){printf("%lld\n",(n%MOD)*(m%MOD)%MOD);continue;}
for(int j=0,l=a[p-1].size();j<l;++j)
{
ll x=a[p-1][j].first,y=a[p-1][j].second;
if(y<=n)ans=(ans+(m-x)/y)%MOD;
if(y<=m)ans=(ans+(n-x)/y)%MOD;
}
printf("%d\n",ans);
}
}