AtCoder Grand Contest 007
AtCoder Grand Contest 007
A - Shik and Stone
翻译
题解
傻逼玩意
#include<cstdio>
int n,m,tot;char ch[10];
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;++i)
{
scanf("%s",ch+1);
for(int j=1;j<=m;++j)
tot+=ch[j]=='#';
}
puts(tot==n+m-1?"Possible":"Impossible");
return 0;
}
B - Construct Sequences
翻译
题解
诶,简单构造题我也不会做,真的是对于构造一窍不通。
我们让\(a\),\(b\)是两个等差数列,保证\(a_i+b_i\)相等,然后公差比\(n\)大就好,每次读进来一个\(p_i\),你就让对应的\(a\)减去一个\(n-i\)就好了。
#include<iostream>
#include<cstdio>
using namespace std;
#define MAX 20200
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,a[MAX],b[MAX];
int main()
{
n=read();
for(int i=1;i<=n;++i)a[i]=i*(n+1),b[i]=(n-i+1)*(n+1);
for(int i=1;i<=n;++i)a[read()]-=(n-i+1);
for(int i=1;i<=n;++i)printf("%d ",a[i]);puts("");
for(int i=1;i<=n;++i)printf("%d ",b[i]);puts("");
}
C - Pushing Balls
翻译
数轴上有\(n\)个球和\(n+1\)个洞,每个球都在两个洞的中间,假设把所有东西放在一起,假设相邻两个物品的距离为\(d_i\),那么\(d_i-d_{i-1}=x\)。每次会等概率选择一个球,并且等概率选择它向左还是向右,它会一直朝着那个方向走,直到掉进坑里,如果这个洞里已经有球,它会从这个洞上面直接过去,继续移动。求所有合法方案中球移动的距离和的期望。一个合法方案是所有球都恰好进入了一个洞,并且只有一个洞没有球。
题解
神仙题,不会。DZYO的题解
官方题解:
在移动完一个球之后,重编号所有的洞和球,每个球仍然在两个洞之间。再重新计算期望意义下相邻的球和洞之间的距离,发现期望距离仍然是一个等差数列,然后从一号球开始顺次计算答案。
代码是照着打的。
#include<iostream>
#include<cstdio>
using namespace std;
double d,x,n,ans;
int main()
{
cin>>n>>d>>x;
for(int i=n;i;--i)
{
double sum=(d*2*n+n*(n+n-1)*x);
ans+=sum/2/n;
double dd=(d*(n+n-2)+d+2*x+3*d+3*x)/2/n;
sum-=(4*d+4*n*x-2*x)/2/n;--n;
d=dd;x=(sum-2*n*d)/n/(n+n-1);
}
printf("%.10lf\n",ans);
return 0;
}
D - Shik and Game
翻译
(什么傻吊题面)
有一个数轴,初始情况下玩家在\(0\)位置,出口在\(E\)位置,数轴上还有\(n\)只熊,你只要到了它的位置,再过\(T\)个单位时间它所在的位置就会出现一个金币。求从出发到捡完所有金币再到出口的最短时间。
题解
显然是把熊按照维护分成若干段,先访问过这一段的所有熊,再回到第一个熊,再顺次拿走所有金币。考虑一个暴力\(dp\),设\(f[i]\)表示当前到了\(i\)并且前面的金币都拿完的最短时间,然后得到式子\(f[i]=min(f[j]+max(t,2*(x[i]-x[j+1])))\),转移很显然。
发现走一段回去再走过来访问到每只熊的时间恰好是这段路程的两倍。意味着维护一下当且可以转移过来的位置,使得距离大于\(t\),那么它一定是一段前缀,那么记一下前缀最小值就完事了。
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
#define MAX 100100
#define ll long long
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,E,T,x[MAX];
ll ans,f[MAX],mn=1e18;
int main()
{
n=read();E=read();T=read();
for(int i=1;i<=n;++i)x[i]=read();
memset(f,63,sizeof(f));f[0]=0;
for(int i=1,j=0;i<=n;++i)
{
for(;T<=(x[i]-x[j+1])<<1;++j)mn=min(mn,f[j]-2*x[j+1]);
if(j<i)f[i]=min(f[i],f[j]+T);
f[i]=min(f[i],mn+2*x[i]);
}
cout<<f[n]+E<<endl;
return 0;
}
E - Shik and Travel
翻译
给定一棵二叉树,每个节点的儿子数要么是\(2\)要么是\(0\)(除叶子节点外的所有点的度数都是\(2\))。现在你要访问所有叶子节点。要求从根节点出发,最后再回到根。每天你可以选择一个没有去过的叶子节点,然后走过去,花费就是路径上的权值之和。每条边都必须恰好被走过两次,总花费就是除了第一天从根节点出发以及最后一天回到根节点之外的每一天的花费的最大值。最小化这个最大值。
题解
最小化最大值,显然二分。现在考虑如何判定二分值是否合法。
因为每条边必须经过恰好两次,所以一旦进入了一棵子树,必定要走完所有其中所有叶子才能出去。
定义二元组\((a,b)\)表示进入到\(u\)的子树中的时候在子树内产生的花费是\(a\),离开子树那天,在子树内产生的贡献是\(b\)。合并答案的时候枚举两个儿子的所有二元组,不妨设左子树\(i\)是\((a,b)\),右子树\(j\)是\((c,d)\),首先会合并出一条新路径\(b+c+V_i+V_j\),判定是否合法(与二分值比较),如果合法的话,意味着我们可以合并一条路径\((a+V_i,d+V_j)\),调换左右儿子的顺序可以类似得到一个二元组\((c+V_j,b+V_i)\)。发现这样子每次向上维护这个集合内的元素即可维护出答案。然而这样子的状态增长太快,我们需要减少状态。比如说我们匹配出来的结果是\((a+V_i,d+V_j)\),那么我们并不在意\(b,c\)是什么,只需要他们合法就行了。那么对于每一个\(a\),我们能够匹配的\(d\)一定是越小越好。反过来相同。我们设节点\(u\)的二元组集合为\(S_u\),发现\(|S_u|\le 2*min(|S_i|,|S_j|)\)。我们可以选择元素个数较小的那一边,用它的第一维匹配元素较多的那一边的第二维。这样子总的状态数就被压成了\(O(nlgon)\)级别的,再加上二分的答案复杂度就是\(O(nlog(n)log(ans))\)。
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<vector>
using namespace std;
#define MAX 132000
#define ll long long
#define pi pair<ll,ll>
#define pb push_back
#define mp make_pair
#define fr first
#define sd second
#define vp vector<pi>
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,w;}e[MAX];
int h[MAX],cnt=1;
inline void Add(int u,int v,int w){e[cnt]=(Line){v,h[u],w};h[u]=cnt++;}
int n;ll mid,mn[MAX];
vp f[MAX];
bool cmpx(pi a,pi b){return a.fr<b.fr;}
bool cmpy(pi a,pi b){return a.sd<b.sd;}
void Merge(vp &u,vp ls,vp rs)
{
if(ls.size()>rs.size())swap(ls,rs);
int t1=ls.size(),t2=rs.size();
if(!t1||!t2)return;
sort(ls.begin(),ls.end(),cmpy);
sort(rs.begin(),rs.end(),cmpx);
mn[0]=rs[0].sd;for(int i=1;i<t2;++i)mn[i]=min(mn[i-1],rs[i].sd);
for(int i=0,j=t2-1;i<t1;++i)
{
while(j>=0&&ls[i].sd+rs[j].fr>mid)--j;
if(j>=0)u.pb(mp(ls[i].fr,mn[j]));
}
sort(ls.begin(),ls.end(),cmpx);
sort(rs.begin(),rs.end(),cmpy);
mn[0]=rs[0].fr;for(int i=1;i<t2;++i)mn[i]=min(mn[i-1],rs[i].fr);
for(int i=0,j=t2-1;i<t1;++i)
{
while(j>=0&&ls[i].fr+rs[j].sd>mid)--j;
if(j>=0)u.pb(mp(mn[j],ls[i].sd));
}
}
void dfs(int u)
{
f[u].clear();if(!h[u]){f[u].pb(mp(0,0));return;}
for(int i=h[u];i;i=e[i].next)dfs(e[i].v);
for(int i=h[u];i;i=e[i].next)
for(int j=0,l=f[e[i].v].size();j<l;++j)
f[e[i].v][j].fr+=e[i].w,f[e[i].v][j].sd+=e[i].w;
if(h[u])Merge(f[u],f[e[h[u]].v],f[e[e[h[u]].next].v]);
}
int main()
{
n=read();
for(int i=2,a,v;i<=n;++i)a=read(),v=read(),Add(a,i,v);
ll l=0,r=1e9,ret=1e9;
while(l<=r)
{
mid=(l+r)>>1;dfs(1);
if(f[1].empty())l=mid+1;
else ret=mid,r=mid-1;
}
cout<<ret<<endl;
return 0;
}
F - Shik and Copying String
翻译
题解
大概是,你画个图,发现只需要这样子折一下就好了。那么答案就是最多的折的次数。说不清啊。看一下官方题解的图就好了。
#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
using namespace std;
#define MAX 1000010
int n,ans;
char s[MAX],t[MAX];
queue<int> Q;
int main()
{
scanf("%d",&n);scanf("%s",s+1);scanf("%s",t+1);
if(!strcmp(s+1,t+1)){puts("0");return 0;}
for(int i=n,p=n;i;--i)
{
if(t[i]==t[i-1])continue;
p=min(p,i);while(p&&s[p]!=t[i])--p;
if(!p){puts("-1");return 0;}
while(!Q.empty())
if((int)Q.front()-(int)Q.size()>=i)Q.pop();
else break;
Q.push(p);if(i^p)ans=max(ans,(int)Q.size());
}
printf("%d\n",ans+1);return 0;
}