A - Shik and Stone
只要判断一下’#'的数量即可,如果只向右和下走的话,走过的格子数量一定是 n + m − 1 n+m-1 n+m−1个。
#include<bits/stdc++.h>
using namespace std;
#define RI register int
int n,m,js;char ch[10];
int main()
{
scanf("%d%d",&n,&m);
for(RI i=1;i<=n;++i) {
scanf("%s",ch+1);
for(RI j=1;j<=m;++j) js+=(ch[j]=='#');
}
if(js==n+m-1) puts("Possible");
else puts("Impossible");
return 0;
}
B - Construct Sequences
先将a和b构造成两个等差数列,公差 d d d比较大(至少大于 n n n吧),假设其中最大项是 s s s(显然 s = 1 + ( n − 1 ) d s=1+(n-1)d s=1+(n−1)d)。
而 a i + b i = ( 1 + ( i − 1 ) d ) + ( 1 + ( n − i + 1 ) d ) = 2 + ( n − 1 ) d a_i+b_i=(1+(i-1)d)+(1+(n-i+1)d)=2+(n-1)d ai+bi=(1+(i−1)d)+(1+(n−i+1)d)=2+(n−1)d
我们只要让 a p i a_{p_i} api增加 i − 1 i-1 i−1,就可以让 a p i + b p i = s + i a_{p_i}+b_{p_i}=s+i api+bpi=s+i了。
而由于公差大于 n n n,所以这么增加还是能让序列a满足条件。
#include<bits/stdc++.h>
using namespace std;
#define RI register int
int read() {
int q=0;char ch=' ';
while(ch<'0'||ch>'9') ch=getchar();
while(ch>='0'&&ch<='9') q=q*10+ch-'0',ch=getchar();
return q;
}
const int N=100005;
int n,a[N],b[N];
int main()
{
int x;
n=read();
for(RI i=1;i<=n;++i) a[i]=b[n-i+1]=1+(i-1)*(n+1);
for(RI i=1;i<=n;++i) x=read(),a[x]+=i-1;
for(RI i=1;i<=n;++i) printf("%d ",a[i]);
puts("");
for(RI i=1;i<=n;++i) printf("%d ",b[i]);
puts("");
return 0;
}
C - Pushing Balls
我们来到操场的沙坑上,赶走在练习跳远的人,然后挖四个坑,然后找体育组借三个实心球放在四个坑直接,那么这些物品两两的初始距离是:
d d d, d + x d+x d+x, d + 2 x d+2x d+2x, d + 3 x d+3x d+3x, d + 4 x d+4x d+4x, d + 5 x d+5x d+5x
考虑一下不同的推球方法,当一个球进入一个坑后,就默不作声地用沙子将这个球埋起来,假装什么都没有发生过,将这些球重新编号。你会发现物品两两间距离有这么几种可能(表格头的编号是重编过的):
情况 | 坑1与球1 | 球1与坑2 | 坑2与球2 | 球2与坑3 |
---|---|---|---|---|
球1入坑1 | d + 2 x d+2x d+2x | d + 3 x d+3x d+3x | d + 4 x d+4x d+4x | d + 5 x d+5x d+5x |
球1入坑2 | 3 d + 3 x 3d+3x 3d+3x | d + 3 x d+3x d+3x | d + 4 x d+4x d+4x | d + 5 x d+5x d+5x |
球2入坑2 | d d d | 3 d + 6 x 3d+6x 3d+6x | d + 4 x d+4x d+4x | d + 5 x d+5x d+5x |
球2入坑3 | d d d | d + x d+x d+x | 3 d + 9 x 3d+9x 3d+9x | d + 5 x d+5x d+5x |
球3入坑3 | d d d | d + x d+x d+x | d + 2 x d+2x d+2x | 3 d + 12 x 3d+12x 3d+12x |
球3入坑4 | d d d | d + x d+x d+x | d + 2 x d+2x d+2x | d + 3 x d+3x d+3x |
期望 | 8 d + 5 x 6 \frac{8d+5x}{6} 68d+5x | 8 d + 15 x 6 \frac{8d+15x}{6} 68d+15x | 8 d + 25 x 6 \frac{8d+25x}{6} 68d+25x | 8 d + 35 x 6 \frac{8d+35x}{6} 68d+35x |
哇,好神奇啊,期望的物品间距离居然还是个等差数列~
你会对这个发现感到异常惊奇,但还是不要忘了把实心球挖出来还回去。
现在来观察,等差数列的第一项就是我们表格的坑1与球1这一列,你会发现不管初始用几个球,这一列只有前两行是 d + 2 x d+2x d+2x和 3 d + 3 x 3d+3x 3d+3x,其他行都是 d d d。所以 d ′ = d + 2 d + 5 x 2 n d'=d+\frac{2d+5x}{2n} d′=d+2n2d+5x。至于公差,我们观察一下球2与坑2这一列,发现这一列的期望永远会是 d + x + 2 d + 9 x 2 n d+x+\frac{2d+9x}{2n} d+x+2n2d+9x,所以公差 x ′ = x + 4 x 2 n x'=x+\frac{4x}{2n} x′=x+2n4x。
这样我们做 n n n次对 d d d和 x x x(其实还有 n n n别忘了)的变换,即可得到答案。
oh,对,至于每种局面下的推球期望距离,应该是 d + 2 n − 1 2 x d+\frac{2n-1}{2x} d+2x2n−1,具体为什么自己推(zhǎo)一(guī)推(lǜ)吧。
#include<bits/stdc++.h>
using namespace std;
#define RI register int
typedef double db;
int n;db d,x,ans;
int main()
{
scanf("%d%lf%lf",&n,&d,&x);
db nn=n+n;
for(RI i=n;i>=1;--i) {
ans+=d+(nn-1)/2*x;
db dd=d+(2*d+5*x)/nn;
db xx=x+4*x/nn;
x=xx,d=dd,nn-=2;
}
printf("%.10lf\n",ans);
return 0;
}
D - Shik and Game
显然我们每次给一个区间的熊喂完糖,然后走到区间的第一只熊那里,依次捡金币过去即可。那么DP(只考虑捡金币的时间,正常走到出口的时间另加即可)
f i = m i n ( f j + m a x ( T , 2 ( d i − d j + 1 ) ) f_i=min(f_j+max(T,2(d_i-d_{j+1})) fi=min(fj+max(T,2(di−dj+1))
就维护一下满足 2 ( d i − d j + 1 ) 2(d_i-d_{j+1}) 2(di−dj+1)的 j j j中最小的 f j − 2 d j + 1 f_j-2d_{j+1} fj−2dj+1即可,因为 f i f_i fi单调不降,所以从第一不满足 2 ( d i − d k + 1 ) 2(d_i-d_{k+1}) 2(di−dk+1)的 k k k处做 f k + T f_k+T fk+T这个转移即可。
#include<bits/stdc++.h>
using namespace std;
#define RI register int
int read() {
int q=0;char ch=' ';
while(ch<'0'||ch>'9') ch=getchar();
while(ch>='0'&&ch<='9') q=q*10+ch-'0',ch=getchar();
return q;
}
typedef long long LL;
const int N=100005;
int n;LL f[N],d[N],mi,E,T;
int main()
{
n=read(),E=read(),T=read();
for(RI i=1;i<=n;++i) d[i]=read();
mi=1e15;
for(RI i=1,j=0;i<=n;++i) {
while(j<i&&(d[i]-d[j+1])*2>T) mi=min(mi,f[j]-2*d[j+1]),++j;
f[i]=mi+2*d[i];
if(j<i&&f[j]+T<f[i]) f[i]=f[j]+T;
}
printf("%lld\n",f[n]+E);
return 0;
}
E - Shik and Travel
首先二分答案,那么每次叶子到叶子之间的距离就不能超过我们二分出的答案。
由于每条边必须且只能走两次,所以必须一棵子树里的叶子都被走完,才能走别的叶子。我们发现不断合并即可。
假设要合并一个节点 x x x的左子树和右子树,到左儿子和右儿子的边权分别是 v 0 v_0 v0和 v 1 v_1 v1。
每棵子树上维护一堆数对 ( a , b ) (a,b) (a,b),表示从这个子树的根走到第一片叶子的费用是 a a a,最后一片叶子走到根的距离是 b b b。假设左子树有数对 ( a , b ) (a,b) (a,b),右子树有数对 ( c , d ) (c,d) (c,d),且 b + c + v 0 + v 1 ≤ a n s b+c+v_0+v_1 \leq ans b+c+v0+v1≤ans,则可以合出数对 ( a + v 0 , d + v 1 ) (a+v_0,d+v_1) (a+v0,d+v1)。假如这个 ( a , b ) (a,b) (a,b)已经被钦定了,我们只要找出满足条件的右子树中 d d d最小的一对来合并即可。
于是我们将数对数量较少的那个,分别钦定为 ( a , b ) (a,b) (a,b)和 ( c , d ) (c,d) (c,d),做两次合并,每次合出的数对数量不会超过两倍数量较少的那个子树的数对数量,类似于启发式合并,复杂度是 O ( n log n ) O(n \log n) O(nlogn)的。如果到根节点还有数对,说明这个 a n s ans ans要大于等于最终答案,否则它小于最终答案。
总复杂度 O ( n log n log a n s ) O(n \log n \log ans) O(nlognlogans)
#include<bits/stdc++.h>
using namespace std;
#define RI register int
int read() {
int q=0;char ch=' ';
while(ch<'0'||ch>'9') ch=getchar();
while(ch>='0'&&ch<='9') q=q*10+ch-'0',ch=getchar();
return q;
}
typedef long long LL;
const int N=132000;
int n;
int s[N][2];LL v[N][2];
typedef pair<LL,LL> PR;
vector<PR> q[N];
bool cmpx(PR a,PR b) {return a.first<b.first;}
bool cmpy(PR a,PR b) {return a.second<b.second;}
void dfs(int x,int lim) {
if(!s[x][0]) {q[x].push_back((PR){0,0});return;}
dfs(s[x][0],lim),dfs(s[x][1],lim);
int s0=s[x][0],s1=s[x][1],v0=v[x][0],v1=v[x][1];
int sz0=q[s0].size(),sz1=q[s1].size();
if(sz0>sz1) swap(s0,s1),swap(v0,v1),swap(sz0,sz1);
LL mi=1e9;
sort(q[s0].begin(),q[s0].end(),cmpy);
sort(q[s1].begin(),q[s1].end(),cmpx);
for(RI i=sz0-1,j=0;i>=0;--i) {
while(j<sz1&&q[s1][j].first+q[s0][i].second+v1+v0<=lim)
mi=min(mi,q[s1][j].second),++j;
if(j) q[x].push_back((PR){q[s0][i].first+v0,mi+v1});
}
sort(q[s0].begin(),q[s0].end(),cmpx);
sort(q[s1].begin(),q[s1].end(),cmpy);
mi=1e9;
for(RI i=sz0-1,j=0;i>=0;--i) {
while(j<sz1&&q[s1][j].second+q[s0][i].first+v1+v0<=lim)
mi=min(mi,q[s1][j].first),++j;
if(j) q[x].push_back((PR){mi+v1,q[s0][i].second+v0});
}
q[s0].clear(),q[s1].clear();
}
int main()
{
int x,y;
n=read();
for(RI i=2;i<=n;++i) {
x=read(),y=read();
if(s[x][0]) s[x][1]=i,v[x][1]=y;
else s[x][0]=i,v[x][0]=y;
}
LL l=0,r=1e9,res=1e9;
while(l<=r) {
LL mid=(l+r)>>1;q[1].clear(),dfs(1,mid);
if(q[1].empty()) l=mid+1;
else res=mid,r=mid-1;
}
printf("%lld\n",res);
return 0;
}
F - Shik and Copying String
又一道玄学大神题。
首先我们会发现,此题就是从某个S字母出发,每次向下方或者右下方或者右方走,直到走到T的与该字母相同的位置,所有的路径不能相交,问中间至少要填充几行。
我们可以维护T中每一个字母必须要由S中哪个字母抵达,连续的相同字母则由S中同一个字母抵达即可。
首先判断ans=0的情况。然后我们先留一行,用于左右走动。
连续一段相同的字母就用一个相同的起点即可。
会产生若干起点区间,折线紧紧地相贴。假如这个区间有 k k k条折线,对于最后面的一个起点 p p p,每个拐点的往前数 k − 1 k-1 k−1个格子都会被折线控制。如此如此,我们玄学地用队列维护一下区间,区间里有 k k k条折线,则至少要 k k k行。(加上左右走动的行,是 k + 1 k+1 k+1行)
呃,要我的理解有哪里错了,欢迎把我打一顿然后告诉我哪错了QAQ
#include<bits/stdc++.h>
using namespace std;
#define RI register int
const int N=1000005;
int n,ans,he,ta,q[N];
char S[N],T[N];
int main()
{
scanf("%d",&n);
scanf("%s",S+1),scanf("%s",T+1);
if(!strcmp(S+1,T+1)) {puts("0");return 0;}
he=1;
for(RI i=n,j=n;i>=1;--i) {
if(T[i]==T[i-1]) continue;
while(j&&(S[j]!=T[i]||j>i)) --j;
if(!j) {puts("-1");return 0;}
while(he<=ta&&q[he]-(ta-he)>i) ++he;
q[++ta]=j;if(i!=j) ans=max(ans,ta-he+1);
}
printf("%d\n",ans+1);
return 0;
}