2020-2021 ACM-ICPC Brazil Subregional Programming Contest——题解+补题

A. Sticker Album

题目链接:https://codeforces.ml/gym/102861/problem/A

题目大意:

现在需要取出n张纸牌,每次可以取x∈[A,B]个,问最后取的n张纸牌的方案数的期望是多少。

题目分析:

1.首先,我们很容易想到一个简单的dp:

  • L=B-A+1即能够转移的状态数,dp[i][j]表示前i张纸牌取j次的期望。
  • 状态转移方程式为:

     dp[i][j]=1/L*(dp[i-B][j-1]+1)+1/L*(dp[i-B+1][j-1]+1)+……+1/L*(dp[i-A][j-1]+1)

                =1/L(dp[i-B][j-1]+dp[i-B+1][j-1]+……+dp[i-A])+1

  • 因为对期望做dp实质上就是求状态(i,j)的期望,而期望本身也可以作为元素去求期望,所以这里直接对期望进行dp。

2.很明显,时间复杂度为O(n(n/A)),极限情况下为O(n2),我们发现对于题目n为106的范围不仅会超时,而且会爆空间。所以我们要想办法对方法进行优化。
3.首先i只能由比i小的状态转移,而j只能由j-1转移,所以只开一维数组来解决这个问题。
4.然后我们观察新的状态转移方程中括号内的内容:

dp[i]->dp[i-B]+dp[i-B+1]+……+dp[i-A]

dp[i-1]->dp[i-1-B]+dp[i-1-B+1]+……+dp[i-1-A]

我们很容易发现,两段中有很多一样的片段,如果我们取:

sum=dp[i-1-B]+dp[i-1-B+1]+……+dp[i-1-A]

则对于状态i:

dp[i]->sum-dp[i-1-B]+dp[i-A]

这样一来我们就可以在O(1)的时间内进行转移了。新的状态转移方程为:

dp[i]=1/L*(sum-dp[i-1-B]+dp[i-A])+1

5.最后考虑一个特殊的情况,由于去掉了第二维j,所以当A=0时,dp[i]的转移方程中还有dp[i],而dp[i]还没求出来,所以我们稍作调正,把dp[i]移到左侧:

dp[i]=1/(L-1)*(sum-dp[i-1-B])+L/(L-1)

6.这样一来,时间复杂度和空间复杂度都变成了O(n),满足题目条件。
7.为了省去判断越界的情况,我把有i定义为剩余的空位数,思路不变。

正解程序:

#include 
#include 
#include 
#include 
#include 
#include 

using namespace std;
typedef long long ll;
double dp[3000010];
ll N,A,B;
int main()
{
	scanf("%lld%lld%lld",&N,&A,&B);
	ll L=B-A+1,count1=0;
	double sum=0;
    for(ll i=N-1;i>=0;i--)
	{
		if(A>0)
			dp[i]=sum/(double)L+1;
		else
			dp[i]=sum/(double)(L-1)+L/(double)(L-1);
        sum-=dp[i+B];
		if(A>0)
			sum+=dp[i+A-1];
        else
        	sum+=dp[i];
    }
    printf("%.8lf",dp[0]);
	
	return 0;
}

B. Battleship

题目链接:https://codeforces.ml/gym/102861/problem/B

题目大意:

给定船头的坐标和船的朝向,判断是否有船越出边界或者有多条船在同一个位置。

题目分析:

简单的模拟题

正解程序:

#include 
#include 
#include 
#include 
#include 
#include 

using namespace std;
typedef long long ll;
bool vis[20][20];

bool check(ll x)
{
	if(x>=1 && x<=10)
		return true;
	return false;
}
int main()
{
	memset(vis,false,sizeof(vis));
	ll n;
	scanf("%lld",&n);
	for(ll i=1;i<=n;i++)
	{
		ll D,L,R,C;
		scanf("%lld%lld%lld%lld",&D,&L,&R,&C);
		if(D==0)
		{
			for(ll j=1;j<=L;j++)
			{
				if(check(R) && check(C+j-1) && !vis[R][C+j-1])
					vis[R][C+j-1]=true;
				else
				{
					printf("N\n");
					return 0;
				}
			}
		}
		else
		{
			for(ll j=1;j<=L;j++)
			{
				if(check(R+j-1) && check(C) && !vis[R+j-1][C])
					vis[R+j-1][C]=true;
				else
				{
					printf("N\n");
					return 0;
				}
			}
		}
	}
	printf("Y\n");
	
	return 0;
}

F. Fastminton

题目链接:https://codeforces.ml/gym/102861/problem/F

题目大意:

给定一个字符串,S表示发球者获胜,R表示接球者获胜,Q要求输出实时比分。获胜的人继续发球。

题目分析:

简单的模拟题

正解程序:

#include 
#include 
#include 
#include 
#include 
#include 

using namespace std;
typedef long long ll;
string mystr;
int main()
{
	cin>>mystr;
	ll A=0,B=0;
	ll R1=0,R2=0,times=0,flag=0;
	ll x=1;
	for(ll i=0;i<mystr.size();i++)
	{
		if(mystr[i]=='S' && !flag)
		{
			A++;
			if((A>=5 && A-B>=2) || (A>=10))
			{
				R1++;
				if(R1==2)
					flag=1;
				A=0;B=0;
			}
		}
		else if(mystr[i]=='R' && !flag)
		{
			times++;
			x=(x+1)%2;
			B++;
			if((B>=5 && B-A>=2) || (B>=10))
			{
				R2++;
				if(R2==2)
					flag=1;
				A=0;B=0;
			}
			swap(A,B);
			swap(R1,R2);
		}
		else if(mystr[i]=='Q')
		{
			if(times&1)
			{
				if(flag)
				{
					if(R2==2)
						printf("2 (winner) - %lld\n",R1);
					else
						printf("%lld - 2 (winner)\n",R2);
				}
				else
				{
					if(x==0)
						printf("%lld (%lld) - %lld (%lld*)\n",R2,B,R1,A);
					else
						printf("%lld (%lld*) - %lld (%lld)\n",R2,B,R1,A);
				}
					
			}
			else
			{
				if(flag)
				{
					if(R1==2)
						printf("2 (winner) - %lld\n",R2);
					else
						printf("%lld - 2 (winner)\n",R1);
				}
				else
				{
					if(x==0)
						printf("%lld (%lld) - %lld (%lld*)\n",R1,A,R2,B);
					else
						printf("%lld (%lld*) - %lld (%lld)\n",R1,A,R2,B);
				}
			}
		}
	}
	
	return 0;
}

G. Game Show!

题目链接:https://codeforces.ml/gym/102861/problem/G

题目大意:

给定一个n和n个数,其中有负数,求最大前缀和。

题目分析:

正解程序:

#include 
#include 
#include 
#include 
#include 
#include 

using namespace std;
typedef long long ll;
int main()
{
	ll n,Max=100,now=100;
	scanf("%lld",&n);
	while(n--)
	{
		ll temp;
		scanf("%lld",&temp);
		now+=temp;
		Max=max(now,Max);
	}
	printf("%lld\n",Max);
	
	return 0;
}

L. Lavaspar

题目链接:https://codeforces.ml/gym/102861/problem/L

题目大意:

给定一个L×C的字符矩阵,和n个单词。同一行,同一列或同一对角线的一串连续字符若与某个单词在忽略字符顺序的情况下相同,则这些位置被覆盖的次数加一。现在要求求出被覆盖次数大于1的点有多少个。

题目分析:

数据范围过小,直接暴力

正解程序:

#include 
#include 
#include 
#include 
#include 
#include 

using namespace std;
typedef long long ll;
struct node
{
	ll x;
	ll y;
}rec[10100];
ll temp[10100],base[1010][1010],ma[100][100],n,m,N,ans[100][100][100],k;
void check(ll st)
{
	for(ll i=1;i<=N;i++)
	{
		bool flag=true;
		for(ll j=0;j<26;j++)
			if(temp[j]!=base[i][j])
				flag=false;
		if(flag)
			for(ll j=1;j<=st;j++)
				ans[rec[j].x][rec[j].y][i]=1;
	}
}
void search(ll i,ll j,ll k,ll st)
{
	if(i>n||j>m||i<1||j<1)
		return;
	temp[ma[i][j]]++;
	rec[st]={i,j};
	check(st);
	if(k==1)search(i+1,j,k,st+1);
	if(k==2)search(i,j+1,k,st+1);
	if(k==3)search(i+1,j+1,k,st+1);
	if(k==4)search(i+1,j-1,k,st+1);
	temp[ma[i][j]]--;
}
int main()
{
	cin>>n>>m;
	for(ll i=1;i<=n;i++)
	{
		for(ll j=1;j<=m;j++)
		{
			char c;
			cin>>c;
			ma[i][j]=c-'A';
		}
	}
	cin>>N;
	for(ll i=1;i<=N;i++)
	{
		string s;
		cin>>s;
		for(ll j=0;j<s.size();j++)
			base[i][s[j]-'A']++;
	}
	ll fans=0;
	for(ll i=1;i<=n;i++)
	{
		for(ll j=1;j<=m;j++)
		{
			for(ll k=1;k<=4;k++)
				search(i,j,k,1);
			ll cnt=0;
			for(ll k=1;k<=N;k++)
				cnt+=ans[i][j][k];
			if(cnt>=2)
				fans++;
		}
	}
	cout<<fans<<endl;
	
	return 0;
}

N. Number Multiplication

题目链接:https://codeforces.ml/gym/102861/problem/N

题目大意:

有编号1 ~ m的m个质数(a[i]),与n个数(b[j])之间存在由m序列到n序列的边,表示b[j]中有一个因子a[i]。同样一个数对(a[i],b[j])可以存在多条边。
现在给定m,n,k(边数)和k条边的情况,要求求出1 ~ m这m个数的值。

题目分析:

1.因为n中的数的数据范围在1015,所以筛法求质数,然后试除的方法显然是不行的因为时间复杂度为O(m* m a x ( b ) \sqrt{max(b)} max(b) )显然是会超市的。
2.然后我们发现,1 ~ m个数是从小到大排序的,所以a[i] ( m a x ( b ) \sqrt{(max(b)} (max(b) )作为a[i]的值。
3.因为如果a[i]所连的第一条边的数能被当前枚举的数整除,那么其他的边一定能被整除。
4.进行b[j]/=a[i],直到不能整除为止,然后删除这条边(减去入度)。
5.因为枚举的时候是从小到大枚举的,所以第一次出现b[j]%a[i]==0的时候a[i]一定是质数,因为如果此时a[i]是合数,a[i]的某个质因数一定能整除b[j],那么应该在之前就已经完成了除法运算,此时的b[j]不应当能被a[i]整除了。
6.如果发现b[j]只连了一条边,那么显然a[i]==b[j]。所以我们需要记录一下b的入度,每次找到质因子并处理后,将入度减去。
7.这个算法的时间复杂度为O( ( m a x ( b ) \sqrt{(max(b)} (max(b) )左右,满足题目要求。

正解程序:

#include 
#include 
#include 
#include 
#include 
#include 
#include 

using namespace std;
typedef long long ll;
const ll maxn=1010;
struct node
{
	ll v,cnt;
	node(ll a,ll b)
	{
		v=a;
		cnt=b;
	}
};
ll m,n,k;
ll c[maxn],fina[maxn],du[maxn];
vector<node> E[maxn];
ll quickmi(ll a,ll p)
{
	ll ans=1;
	while(p)
	{
    	if(p&1)
        	ans*=a;
    	a*=a;
    	p>>=1;
	}
	return ans;
}
void work(ll u,ll prime)
{
    for(ll i=0;i<E[u].size();i++)
	{
        c[E[u][i].v]/=quickmi(prime,E[u][i].cnt);
        du[E[u][i].v]-=E[u][i].cnt;
    }
}
int main()
{
	scanf("%lld%lld%lld",&m,&n,&k);
	for(ll i=1;i<=n;i++)
    	scanf("%lld",&c[i]);
	for(ll i=1;i<=k;i++)
	{
    	ll u,v,w;
    	scanf("%lld%lld%lld",&u,&v,&w);
    	E[u].push_back(node(v,w));
    	du[v]+=w;
	}
	ll prime=2;
	for(ll i=1;i<=m;i++)
	{
        ll tar=E[i][0].v,d=E[i][0].cnt;
    	if(du[tar]==1)
		{
            fina[i]=c[tar];
            work(i,fina[i]);
            prime=fina[i]+1;
        	continue;
        }
        while(prime*prime<=1e15)
		{
            if(c[tar]%quickmi(prime,d)==0)
			{
                fina[i]=prime;
                work(i,prime);
                prime++;
                break;
            }
            prime++;
        }
	}
	for(ll i=1;i<=m;i++)
		printf("%lld ",fina[i]);

	return 0;
}

你可能感兴趣的:(2020-2021 ACM-ICPC Brazil Subregional Programming Contest——题解+补题)