AtCoder Grand Contest 011
AtCoder Grand Contest 011
upd:这篇咕了好久,前面几题是三周以前写的。。。
AtCoder Grand Contest 011
A - Airport Bus
翻译
有\(n\)个乘客到达了飞机场,现在他们都要坐车离开机场。第\(i\)个乘客到达的时间是\(T_i\),一个乘客必须在\([T_i,T_i+k]\)时刻做到车,否则他会生气。一辆车最多可以坐\(C\)个人。问最少安排几辆车可以让所有人都不生气。
题解
从前往后贪心即可。
#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
#define MAX 100100
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,C,K,T[MAX],ans,s,t;
int main()
{
n=read();C=read();K=read();
for(int i=1;i<=n;++i)T[i]=read();
sort(&T[1],&T[n+1]);
for(int i=1;i<=n;++i)
if(!s)++ans,s=1,t=T[i];
else
{
if(s==C)++ans,s=1,t=T[i];
else
{
if(T[i]-t>K)++ans,s=1,t=T[i];
else ++s;
}
}
printf("%d\n",ans);
return 0;
}
B - Colorful Creatures
翻译
题解
显然不合法的在排序之后是一段前缀,那么倒推哪些合法即可。
#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
#define ll long long
#define MAX 100100
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,ans=1;ll a[MAX],s;
int main()
{
n=read();
for(int i=1;i<=n;++i)a[i]=read();
sort(&a[1],&a[n+1]);
for(int i=1;i<=n;++i)a[i]+=a[i-1];
for(int i=n-1;i;--i)
if(a[i]*2>=a[i+1]-a[i])++ans;
else break;
printf("%d\n",ans);
return 0;
}
C - Squared Graph
翻译
给定一个\(n\)个点\(m\)条边的图,构建一个\(n^2\)个点的图,新图的每个点都可以看成一个二元组,新图上的点\((a,b)\)和\((a',b')\)之间有边,当且仅当原图中\((a,a'),(b,b')\)之间有边,问新图的联通块个数。
题解
不太会,抄了一遍代码,从这里抄的
大概理解一下就是,首先考虑所有二元组很不好考虑,拆分行列,考虑情况。首先把所有没有连上的单点全部拿出来把贡献直接算好,接下来只会考虑所有存在边的联通块。考虑一下如果两个点\((a,b),(x,y)\)在新图中连在一起,那么必定在原图中存在一条\(a\rightarrow x,b\rightarrow y\)的路径,并且满足两条路径的长度相同。
如果一个联通块是一个二分图,那么它自身就会贡献\(2\)的答案,即把二分图黑白染色之后左右分开,显然把两边的点分别放在二元组的前面都会形成一个联通块;否则会贡献\(1\)的答案。然后考虑任选两个不连通的图,他们之间的边的关系都可以在新图之中构成一个新的联通块,所以把贡献加上就好了。
#include<iostream>
#include<cstdio>
using namespace std;
#define ll long long
#define MAX 100100
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 Line{int v,next;}e[MAX<<2];
int h[MAX],cnt=1;
inline void Add(int u,int v){e[cnt]=(Line){v,h[u]};h[u]=cnt++;}
bool fl;int sum;
int dep[MAX];bool vis[MAX];
int n,m;ll ans;
void dfs(int u,int ff)
{
vis[u]=true;++sum;
for(int i=h[u];i;i=e[i].next)
if(vis[e[i].v]&&e[i].v!=ff)fl|=dep[u]==dep[e[i].v];
else if(!vis[e[i].v])dep[e[i].v]=dep[u]^1,dfs(e[i].v,u);
}
int main()
{
n=read();m=read();
for(int i=1;i<=m;++i)
{
int u=read(),v=read();
Add(u,v);Add(v,u);
}
int node=0,t1=0,t2=0;
for(int i=1;i<=n;++i)
{
if(vis[i])continue;
fl=false;sum=0;dfs(i,0);
if(sum==1)ans+=n*2-1,++node;
else if(!fl)ans+=2,++t1;
else ++ans,++t2;
}
ans-=1ll*(node-1)*node;
ans+=1ll*t1*(t1-1)*2+1ll*t2*(t2-1)+1ll*t1*t2*2;
cout<<ans<<endl;
return 0;
}
D - Half Reflector
翻译
有一个神仙机器,你可以从它的左边或者右边丢一个球进去,它有两种状态\(A,B\)。如果处在\(A\)状态,你从哪里丢进来的就会从哪里吐出去,然后立即变成\(B\)状态。如果处在\(B\)状态,你从哪边丢进去就会从另外一边吐出来,然后立即变成\(A\)状态。现在把\(n\)台这样的机器放在一排,告诉你他们处在什么状态。然后从最左边放\(K\)次球,每当前面那个球出去之后就放第二个。问最后的机器的状态是啥。
题解
先想想一个球什么时候会从左边出来,什么时候从右边出来。如果它从左边出来,只可能是第一个机器处于\(A\)状态。我们来感性理解一下。首先加入你到达了中间某个位置\(i\),并且它在这里被弹了回来,因为你到了\(i\),所以\(i-1\)原先一定是\(B\)状态,所以它现在一定是\(A\)状态,那么你又被接着弹回去了,那么你又会回到\(i\),而\(i\)刚刚是\(A\),所以现在是\(B\),所以会走到\(i+1\),然后重复此个过程,它一定会向右走。
考虑一下这个串怎么变化。如果最高位是\(A\),直接变成\(B\)完事。否则的话,我们发现首先把它们全部取反,然后原来\(A\)之前的那个\(B\)没有变反,原来\(B\)前面的那个\(A\)也没有变反。所以这个操作可以看成先把它左移一位(高位截掉),再全部取反,最后一个位置显然是补上一个\(A\)。发现做完最多\(2n\)次之后就和原来的串无关了,就变成了\(ABABAB...\)或者\(BABABA...\)这样子了。那就暴力做个\(2n\)次,剩下的判断一下就好啦,当然这里情况可能有些复杂,仔细手玩一下再写代码。
蛤?怎么模拟?你把数组在后面延长不就好了吗?
#include<iostream>
#include<cstdio>
using namespace std;
#define MAX 200200
int n,K,l,r,p;
char S[MAX];int a[MAX<<2];
int main()
{
scanf("%d%d",&n,&K);scanf("%s",S+1);
l=1,r=n,p=0;
for(int i=1;i<=n;++i)a[i]=S[i]=='A';
for(int i=1;i<=n+n;++i)
{
if(a[l]^p)a[l]^=1;
else ++l,p^=1,a[++r]=p^1;
if(i==K)
{
for(int j=l;j<=r;++j)putchar((a[j]^p^1)+'A');
puts("");return 0;
}
}
if((n&1)&&(K&1))a[l]^=1;
for(int i=l;i<=r;++i)putchar((a[i]^p^1)+'A');
return 0;
}
E - Increasing Numbers
翻译
给定一个数\(n\),\(n\le 10^{500,000}\),问\(n\)最少可以拆分成几个不降数的和。一个不降数是在十进制位下,从高位往低位看,每个数都不会比高位的数更小的数。
题解
找啊找啊找性质。然而我太菜了,找不出性质。
首先意识不降的数一定能够拆分成若干个\(11....11\)的形式,因为数字不降,所以这个个数不会超过\(9\)个。那么按照这个方向考虑,假设最终的答案中我们的所有数字被我们拆成了若干个\(1...1\)的形式,他们分别有\(a_i\)个\(1\)。那么等式算一下就是\(\sum_{i=1}^{9k} (10^{a_{i-1}}-1)=9n\)。转化一下就是\(\sum_{i=1}^{9k} 10^{a_i}=9n+9k\),也就是\(9n+9k\)的所有位置上的数字之后恰好小于等于\({9k}\)。那就直接大力枚举\(k\)就好了。
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
#define MAX 500500
char ch[MAX];
int a[MAX<<1],n,s;
int main()
{
scanf("%s",ch+1);n=strlen(ch+1);
for(int i=n;i;--i)a[i]=(ch[n-i+1]-48)*9;
for(int i=1;i<=n;++i)a[i+1]+=a[i]/10,a[i]%=10;
if(a[n+1])++n;
for(int i=1;i<=n;++i)s+=a[i];
for(int i=1;i<=n*9;++i)
{
a[1]+=9;int p=1;s+=9;
while(a[p]>=10)
{
s-=a[p+1];a[p+1]+=a[p]/10;s+=a[p+1];
s-=a[p];a[p]%=10;s+=a[p];
++p;if(p>n)n=p;
}
if(s<=i*9){printf("%d\n",i);return 0;}
}
return 0;
}
F - Train Service Planning
翻译
有\(n\)段铁路,\(n+1\)个车站,第\(i\)段铁路连接着站台\(i-1\)和站台\(i\)。第\(i\)段铁路行驶时间为\(A_i\),铁路有两种,有一条路或者两条路,即一次可以开过一辆火车或者两辆火车。现在从\(0\)到\(n\)和从\(n\)到\(0\)每经过\(K\)个单位时间就会发出一辆车,一辆车可以在一个站台停任意久的时间,显然不能被后面的同向的车给追上,现在需要安排一个车的行驶方式,使得两侧运行的车的总运行时间最小。
题解
首先每隔\(K\)个单位时间就会发出一辆新车,意味着所有的运算可以等效的理解为在模\(K\)意义下进行。我们假设我们知道两辆车在每一个位置停下的时间,从\(0\rightarrow n\)的设为\(t1[i]\),反过来的设为\(t2[i]\),那么我们就可以得到一辆车通过一段路的时间的区间。设\(S\)为前缀和。从\(0\rightarrow n\)的车的通过第\(i\)段路的时间是\([S(A,i-1)+S(t1,i-1),S(A,i)+S(t1,i-1)]\),反过来运行的车的通过时间同理,我们可以用后缀和表示出来,然后后缀又可以改写成总和减去一段前缀,也就是\([Sum-S(A,i)-S(t2,i-1),Sum-S(A,i-1)-S(t2,i-1)]\)。而总和的话,我们可以强制认为后面从后往前的车在\(0\)车站还要多停一会儿,也就是强制让\(sum\)是\(K\)的倍数,那么在模\(K\)的意义下就可以直接把\(Sum\)省略掉。
对于双向轨道而言,我们并没有任何限制,对于单向轨道而言,显然强制两车通过同一段路的时间不能交。那么可以得到一系列的不等式。最终得到的也就是形如\(S(t1,i)+S(t2,i)\notin [-2S(A,i),-2S(A,i-1)]\)。当然,这些东西全部都在模意义下看。也因为在模意义下,所以不属于可以取补化为属于,即\(S(t1,i)+S(t2,i)\)要在某个特定区间内。而题目要求的东西就是要最小化\(S(t1,n)+S(t2,n)\)。而问题也同时变为,给定\(n\)个区间,每次都要向前走若干距离,并且第\(i\)步走完后位置必须在模意义下的给定区间中,求最小的移动距离。
因为可以在起始车站停留,所以我们可以任意选择起始位置,贪心的考虑,显然每次走到目标区间的左端点一定是最优的。那么我们只需要预处理从每一个位置出发一直走左端点的最小距离即可。设\(f[i]\)表示当前从第\(i\)个限制的左端点出发的距离。考虑倒推,每次更新完一个区间之后就可以用线段树更新取值,这样子就很容易知道下一个位置要从哪里转移。接下来只需要枚举起点即可计算答案。
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<vector>
using namespace std;
#define ll long long
#define MAX 200200
#define lson (now<<1)
#define rson (now<<1|1)
void WA(){puts("-1");exit(0);}
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,K,top;
ll a[MAX],b[MAX],t[MAX<<2];
ll L[MAX],R[MAX],f[MAX],s[MAX],S[MAX];
void pushdown(int now)
{
if(!t[now])return;
t[lson]=t[rson]=t[now];
t[now]=0;
}
void modify(int now,int l,int r,int L,int R,int w)
{
if(L>R)return;
if(L<=l&&r<=R){t[now]=w;return;}
int mid=(l+r)>>1;pushdown(now);
if(L<=mid)modify(lson,l,mid,L,R,w);
if(R>mid)modify(rson,mid+1,r,L,R,w);
}
int Query(int now,int l,int r,int p)
{
if(l==r)return t[now];
int mid=(l+r)>>1;pushdown(now);
if(p<=mid)return Query(lson,l,mid,p);
else return Query(rson,mid+1,r,p);
}
ll Query(int x)
{
int d=Query(1,1,top,x);
if(!d)return 0;
return f[d]+(S[L[d]]%K-S[x]%K+K)%K;
}
int main()
{
n=read();K=read();
for(int i=1;i<=n;++i)
{
a[i]=read(),b[i]=read();
s[i]=s[i-1]+a[i];
if(b[i]==1&&a[i]+a[i]>K)WA();
}
for(int i=n;i;--i)
if(b[i]==1)L[i]=(-2*s[i-1]%K+K)%K,R[i]=(-2*s[i]%K+K)%K;
else L[i]=0,R[i]=K-1;//No limit
for(int i=1;i<=n;++i)S[++top]=L[i],S[++top]=R[i];
sort(&S[1],&S[top+1]);top=unique(&S[1],&S[top+1])-S-1;
for(int i=n;i;--i)
{
L[i]=lower_bound(&S[1],&S[top+1],L[i])-S;
R[i]=lower_bound(&S[1],&S[top+1],R[i])-S;
f[i]=Query(L[i]);
if(L[i]>R[i])modify(1,1,top,R[i]+1,L[i]-1,i);
else modify(1,1,top,1,L[i]-1,i),modify(1,1,top,R[i]+1,top,i);
}
ll ans=1e18;
for(int i=top;i;--i)ans=min(ans,Query(i));
cout<<ans+s[n]+s[n]<<endl;
return 0;
}