Codeforces Round #689 (Div. 2) CF1461A-F 题解

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[i1][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 p1位排序都是没有用的,而 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=1ri>=p(1pi)

代码:

#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 xy,判断能否支持 t t t天即可。
如果 x < y x<y x<y,那么我们可以尽量维持低水位,只有当当前水位不够用时,即 l ≤ k < l + x l≤k<l+x lk<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 x106 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 m1位相乘,后 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[i1]+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=1i1f[j1]+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=2n就可以通过此题了,然后记录转移路径就可以输出方案了。
具体可以参考代码注释。

代码:

#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]);
			}
		}
	}
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值