Codeforces Round #689 (Div. 2) CF1461A-F 題解
技術標籤:codeforces
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]
那麼
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]);
}
}
}
}