1. 程式人生 > 其它 >Codeforces Round #689 (Div. 2) CF1461A-F 題解

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]

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]);
			}
		}
	}
}