A.
显然可以构造出
S
=
a
a
a
.
.
.
a
b
c
a
b
c
.
.
.
.
S=aaa...abcabc....
S=aaa...abcabc....,即前
k
k
k个字符都为
a
a
a,后面为
a
b
c
abc
abc的循环。
代码:
#include <iostream>
#include <cstdio>
using namespace std;
int T,n,k;
int main()
{
scanf("%d",&T);
while (T--)
{
scanf("%d%d",&n,&k);
for (int i=1;i<=k-1;i++) printf("a");
int d=0;
for (int i=k;i<=n;i++)
{
printf("%c",'a'+d);
d=(d+1)%3;
}
printf("\n");
}
}
B.
我们把树的最下的那一行的中间的格子看做这棵树的中心,那么设
f
[
i
]
[
j
]
f[i][j]
f[i][j]表示以
(
i
,
j
)
(i,j)
(i,j)为中心的树的最大大小,
l
[
i
]
[
j
]
l[i][j]
l[i][j]为第
i
i
i行第
j
j
j左边有多少个连续的
∗
*
∗(包括自己),
r
[
i
]
[
j
]
r[i][j]
r[i][j]第
i
i
i行第
j
j
j右边有多少个连续的
∗
*
∗(包括自己)。
那么
f
[
i
]
[
j
]
=
m
i
n
(
f
[
i
−
1
]
[
j
]
+
1
,
l
[
i
]
[
j
]
,
r
[
i
]
[
j
]
)
f[i][j]=min(f[i-1][j]+1,l[i][j],r[i][j])
f[i][j]=min(f[i−1][j]+1,l[i][j],r[i][j])。把所有点的
f
f
f加起来就是答案。
代码:
#include <iostream>
#include <cstdio>
#include <cstring>
const int maxn=507;
using namespace std;
int T,n,m,ans;
int l[maxn][maxn],r[maxn][maxn],f[maxn][maxn];
char s[maxn];
int main()
{
scanf("%d",&T);
while (T--)
{
scanf("%d%d",&n,&m);
memset(f,0,sizeof(f));
memset(l,0,sizeof(l));
memset(r,0,sizeof(r));
for (int i=1;i<=n;i++)
{
scanf("%s",s+1);
for (int j=1;j<=m;j++)
{
if (s[j]=='*') l[i][j]=l[i][j-1]+1;
else l[i][j]=0;
}
for (int j=m;j>0;j--)
{
if (s[j]=='*') r[i][j]=r[i][j+1]+1;
else r[i][j]=0;
}
}
ans=0;
for (int i=1;i<=n;i++)
{
for (int j=1;j<=m;j++)
{
f[i][j]=min(f[i-1][j]+1,min(l[i][j],r[i][j]));
ans+=f[i][j];
}
}
printf("%d\n",ans);
}
}
C.
如果序列本来就有序,输出1。
假设
a
[
p
+
1
]
a[p+1]
a[p+1]~
a
[
n
]
a[n]
a[n]已经排好序(即
a
[
x
]
=
x
a[x]=x
a[x]=x),那么给
1
1
1到
p
−
1
p-1
p−1位排序都是没有用的,而
p
p
p到
n
n
n位排序只需成功一次即可。
即
a
n
s
=
1
−
∏
r
i
>
=
p
(
1
−
p
i
)
ans=1-\prod_{r_i>=p}(1-p_i)
ans=1−∏ri>=p(1−pi)
代码:
#include <iostream>
#include <cstdio>
#include <algorithm>
const int maxn=1e5+7;
using namespace std;
int T,n,m,ret;
double ans;
int a[maxn];
struct rec{
int x;
double p;
}b[maxn];
bool cmp(rec a,rec b)
{
return a.x>b.x;
}
int main()
{
scanf("%d",&T);
while (T--)
{
scanf("%d%d",&n,&m);
for (int i=1;i<=n;i++) scanf("%d",&a[i]);
for (int i=1;i<=m;i++) scanf("%d%lf",&b[i].x,&b[i].p);
sort(b+1,b+m+1,cmp);
ret=n;
for (int i=n;i>0;i--)
{
if (a[i]==i) ret=i-1;
else break;
}
if (ret<=0)
{
printf("1.000000\n");
continue;
}
ans=1;
for (int i=1;i<=m;i++)
{
if (b[i].x>=ret) ans*=(1-b[i].p);
}
printf("%.7lf\n",1-ans);
}
}
D.
显然改变数列的顺序不影响产生的数组的和。
我们先把原序列排序,然后直接模拟,由于最多可能产生
n
l
o
g
n
nlogn
nlogn个数,所以我们先把询问离线,然后每产生一个数就去判断,复杂度应该是
O
(
n
l
o
g
2
n
)
O(nlog^2n)
O(nlog2n)的。
代码:
#include <iostream>
#include <cstdio>
#include <cmath>
#include <set>
#include <algorithm>
#define LL long long
const int maxn=1e5+7;
using namespace std;
int n,T,m,x;
int a[maxn],ans[maxn];
LL sum[maxn];
struct rec{
int x,y;
};
bool operator <(rec a,rec b)
{
if (a.x==b.x) return a.y<b.y;
return a.x<b.x;
}
set <rec> s;
set <rec> ::iterator it,it1;
void solve(int l,int r)
{
int mid=(a[l]+a[r])/2;
LL d=sum[r]-sum[l-1];
it=s.lower_bound((rec){d,0});
while (it!=s.end())
{
rec e=*it;
if (e.x==d)
{
ans[e.y]=1;
it1=it;
it++;
s.erase(it1);
}
else break;
}
if (d==(LL)a[l]*(LL)(r-l+1)) return;
int p=upper_bound(a+l,a+r+1,mid)-a;
solve(l,p-1);
solve(p,r);
}
int main()
{
scanf("%d",&T);
while (T--)
{
scanf("%d%d",&n,&m);
for (int i=1;i<=n;i++) scanf("%d",&a[i]);
sort(a+1,a+n+1);
for (int i=1;i<=n;i++) sum[i]=sum[i-1]+(LL)a[i];
s.clear();
int x;
for (int i=1;i<=m;i++)
{
scanf("%d",&x);
s.insert((rec){x,i});
ans[i]=0;
}
solve(1,n);
for (int i=1;i<=m;i++)
{
if (ans[i]) printf("Yes\n");
else printf("No\n");
}
}
}
E.
如果
x
>
=
y
x>=y
x>=y,那么我们只需要特殊处理第一天,后面的每一天水位都会下降
x
−
y
x-y
x−y,判断能否支持
t
t
t天即可。
如果
x
<
y
x<y
x<y,那么我们可以尽量维持低水位,只有当当前水位不够用时,即
l
≤
k
<
l
+
x
l≤k<l+x
l≤k<l+x时加水。
并记录加水前的水位,如果两次到达一个相同的且处于
[
l
,
l
+
x
)
[l,l+x)
[l,l+x)的水位,则说明我们可以通过一些步骤维持水位,那么就一定可以实现要求。
如果超过
t
t
t天同样可以实现,而如果
[
l
,
l
+
x
)
[l,l+x)
[l,l+x)加水超过上界,那么就无法实现。
因为
x
≤
1
0
6
x≤10^6
x≤106,
x
+
1
x+1
x+1次加水操作后一定能出现重复的水位,所以复杂度为
O
(
x
)
O(x)
O(x)。
代码:
#include <iostream>
#include <cstdio>
#define LL long long
const int maxn=1e6+7;
using namespace std;
LL k,l,r,t,x,y;
bool v[maxn];
int main()
{
scanf("%lld%lld%lld%lld%lld%lld",&k,&l,&r,&t,&x,&y);
if (x>=y)
{
LL p;
if (k+y<=r) p=x-y;
else p=x;
if (x==y)
{
if (k-p>=l) printf("Yes\n");
else printf("No\n");
}
else
{
if ((k-l-p)/(x-y)>=(t-1)) printf("Yes\n");
else printf("No\n");
}
}
else
{
for (LL i=1;i<=x+1;i++)
{
LL p=(k-l)/x;
t-=p;
k-=x*p;
if (t<=0)
{
printf("Yes\n");
return 0;
}
if (v[k%x])
{
printf("Yes\n");
return 0;
}
v[k%x]=1;
if (k+y<=r) k+=y;
else
{
printf("No\n");
return 0;
}
}
}
}
F.
对于运算符可以分四类讨论。
只含一种运算符,直接所有位置使用该运算符。
含有
+
+
+和
−
-
−,则全部相加。
含有
−
-
−和
∗
*
∗,如果序列没有
0
0
0或者第一位是
0
0
0,则全部相乘;否则设最左边的
0
0
0的位置为
m
(
m
>
1
)
m(m>1)
m(m>1),则前
m
−
1
m-1
m−1位相乘,后
m
m
m位相乘(等于0),再相减。
含有
+
+
+和
∗
*
∗或者全部运算符都有(显然
−
-
−不考虑),我们设
f
[
i
]
f[i]
f[i]表示前
i
i
i位的最大值,显然有转移
f
[
i
]
=
f
[
i
−
1
]
+
a
[
i
]
f[i]=f[i-1]+a[i]
f[i]=f[i−1]+a[i]
f
[
i
]
=
min
j
=
1
i
−
1
f
[
j
−
1
]
+
∏
k
=
j
i
a
[
k
]
f[i]=\min_{j=1}^{i-1} f[j-1]+\prod_{k=j}^{i}a[k]
f[i]=minj=1i−1f[j−1]+∏k=jia[k]
我们可以分析得到,如果
a
[
i
]
=
1
a[i]=1
a[i]=1或者
a
[
i
]
=
0
a[i]=0
a[i]=0,那么一定是第一种转移更优。
而第二种转移只需考虑在
a
[
j
]
≠
1
a[j]≠1
a[j]=1且
a
[
j
]
≠
0
a[j]≠0
a[j]=0处的转移即可,而如果
[
j
,
i
]
[j,i]
[j,i]中有
0
0
0,那么这个区间也可以不考虑。
并且当
[
j
,
i
]
[j,i]
[j,i]的积最大值大于一个我们设定的
l
i
m
lim
lim时,那么后面的位置的转移也是这个
j
j
j最优。因为从任意位置断开这个区间(其他位置的转移)都没有直接乘起来大。
有了这个限制,意味着我们的转移点就会很少,假设我们有 k k k个转移点,那么就会存在一段至少 2 k 2^k 2k的积,所以转移点的个数少于 l o g 2 ( l i m ) log2(lim) log2(lim)。而如果遇到 0 0 0那么转移点就可以全部清除。
我设的
l
i
m
=
2
∗
n
lim=2*n
lim=2∗n就可以通过此题了,然后记录转移路径就可以输出方案了。
具体可以参考代码注释。
代码:
#include <iostream>
#include <cstdio>
#include <cstring>
const int maxn=1e5+7;
using namespace std;
int n,cnt;
int a[maxn],q[maxn],sum[maxn],f[maxn],g[maxn];
char s[4];
bool op[4];
void solve(int x)
{
if (x==0) return;
solve(g[x]);
printf("%d",a[g[x]+1]);
for (int i=g[x]+2;i<=x;i++) printf("*%d",a[i]);
if (x!=n) printf("+");
}
int main()
{
scanf("%d",&n);
for (int i=1;i<=n;i++) scanf("%d",&a[i]);
scanf("%s",s+1);
int len=strlen(s+1);
for (int i=1;i<=len;i++)
{
if (s[i]=='+') op[1]=1;
if (s[i]=='-') op[2]=1;
if (s[i]=='*') op[3]=1;
}
if (!op[3])
{
if (op[1])
{
for (int i=1;i<n;i++) printf("%d+",a[i]);
printf("%d",a[n]);
}
else
{
for (int i=1;i<n;i++) printf("%d-",a[i]);
printf("%d",a[n]);
}
}
else
{
if (op[1])
{
int tot=0,flag=0;
for (int i=1;i<=n;i++)
{
f[i]=f[i-1]+a[i]; //第一种转移
g[i]=i-1;
if (a[i]==0) //遇到0直接清掉前面的转移点
{
tot=0;
flag=0;
}
else
{
if (a[i]!=1)
{
for (int j=1;j<=tot;j++) //更新前面的转移点到i的积
{
if (sum[j]*a[i]>=2*n) //如果转移点超过lim,直接认为乘在一起最优
{
flag=1;
break;
}
else sum[j]*=a[i];
if (f[q[j]]+sum[j]>f[i]) //第二种转移
{
f[i]=f[q[j]]+sum[j];
g[i]=q[j];
}
}
q[++tot]=i-1,sum[tot]=a[i]; //加入当前转移点
if (flag) g[i]=q[1]; //显然q[1]位置的积最大,而且后面的位置都用它来转移,不需要具体确定其f值
}
}
}
solve(n);
}
else
{
if (op[2])
{
int ret=-1;
for (int i=1;i<=n;i++)
{
if (!a[i])
{
ret=i;
break;
}
}
if ((ret==1) || (ret==-1))
{
for (int i=1;i<n;i++) printf("%d*",a[i]);
printf("%d",a[n]);
}
else
{
for (int i=1;i<=ret-2;i++) printf("%d*",a[i]);
printf("%d-",a[ret-1]);
for (int i=ret;i<n;i++) printf("%d*",a[i]);
printf("%d",a[n]);
}
}
else
{
for (int i=1;i<n;i++) printf("%d*",a[i]);
printf("%d",a[n]);
}
}
}
}