【题解】Codeforces 1068 : Round #518 (Div. 2) [Thanks, Mail.Ru!] ABCD

版权声明:欢迎评论交流,转载请注明原作者。 https://blog.csdn.net/m0_37809890/article/details/83375642

A. Birthday 计算

一共n种硬币,小萌已经有了k种硬币,小萌的m个朋友想每人送给小萌一些硬币,且满足以下条件,求每个人最少送几枚硬币。

  1. 每人送的个数相等且所有要送的硬币种类不同。
  2. 小萌最后收到的硬币至少包含L种她没有的。

计算题,求满足 a n s m > = k + l a n s m < = n a n s ans*m >= k+l且ans*m<=n的最小ans

ll ans = (k+l+m-1)/m;
printf("%I64d\n",ans*m>n?-1:ans );

B. LCM 计算

给定b(1e10),对于1<=a<=1e18,求lcm(a,b)/a的不同取值个数。

lcm(a,b)/a = a*b/gcd(a,b)/a = b/gcd(a,b),即求b的因数个数

    ll b = read(), ans = 0, i;
    for(i=1;i*i<b;++i)
        if(b%i==0)
            ans+=2;
    if(i*i==b) ++ans;
    printf("%I64d\n",ans );

C. Colored Rooks 构造

给定n(100)个点,m(1000)条边的无向连通图。在1e9*1e9的坐标平面上放置不超过5000个象棋中的车,每个车都归属于原图中的某个节点。要求:

  1. 每个节点至少有一个车。
  2. 每个节点的车集连通。(上下,左右,不会被阻挡)
  3. 两个节点的车集的并集连通 当且仅当 两个节点有边直接相连.

构造这样的坐标平面,输出每个节点辖制的车的数量及每个车的坐标。

坐标平面足够,点数足够(只有1000条边,可以放5000个棋子),把每个节点辖制的车都放在一行则自然连通。如果两个节点有边相连,那么就在很远的列给他们两行都放一个点。

set<int> ans[M];
	int n=read(),m=read();
	for(int i=1;i<=n;++i)
		ans[i].insert(i);
	for(int i=1;i<=m;++i)
	{
		int a = read(), b=read();
		ans[a].insert(a*1000+b);
		ans[b].insert(a*1000+b);
	}
	for(int i=1;i<=n;++i)
	{
		printf("%d\n",ans[i].size() );
		for(auto x:ans[i])
			printf("%d %d\n",i,x );
	}

D. Array Without Local Maximums 求方案数,dp

长度为n(1e5)的数组,值域为1到200,且每个数的旁边必有一个大于等于它的数。有些数字被擦掉了,问共有几种填充的方案满足上述要求。

遇事不决写暴力,搜索不行就DP

大DP

  • 状态表示 d p [ p o s ] [ l s t ] [ t a g ] p o s p o s + 1 l s t t a g p o s + 2 l s t i + 1 i dp[pos][lst][tag]表示需要填充前pos位,第pos+1位为lst,tag表示第pos+2位是否大于等于lst(即i+1位是否对第i位没有需求了)
  • 状态边界 d p [ 0 ] [ l s t ] [ t a g ] = t a g dp[0][lst][tag]=tag,表示必须满足第一位数的右边大于等于它
  • 状态转移:当第pos位不为-1时,
    d p [ p o s ] [ l s t ] [ t a g ] = d p [ p o s 1 ] [ s a v e [ p o s ] ] [ l s t &gt; = s a v e [ p o s ] ] dp[pos][lst][tag]=dp[pos-1][save[pos]][lst&gt;=save[pos]]
    表示这一位无须填充,直接转移即可。
    否则
    d p [ p o s ] [ l s t ] [ t a g ] = Σ v a l = t a g ? 1 : l s t 200 d p [ p o s 1 ] [ v a l ] [ v a l &lt; = l s t ] dp[pos][lst][tag]=\Sigma_{val=tag?1:lst}^{200}dp[pos-1][val][val&lt;=lst]

我有一个绝妙的递归算法,但是栈空间太小,写不下。
改成递推,添加前缀和数组。

/* LittleFall : Hello! */
//#pragma comment(linker, "/STACK:102400000,102400000")
#include <bits/stdc++.h>
using namespace std; using ll = long long; inline int read();
const int M = 100016, MOD = 998244353;

int save[M];
ll dp[M][201][2], sum[2][201][2]; 
//dp[i][j][k]表示需要确定前i位,第i+1位为j,第i+2位是否大于等于第i+1位时的方案数.
int solve(int n)
{
	for(int nxt=1;nxt<=200;++nxt)
	{
		dp[0][nxt][1] = 1;
		sum[1][nxt][1] = sum[1][nxt-1][1] + 1;
	}
	for(int pos=1;pos<=n;++pos)
	{
		memcpy(sum[0],sum[1],sizeof(sum[1]));
		memset(sum[1],0,sizeof(sum[1]));
		for(int nxt = 1;nxt<=200;++nxt)
		{
			for(int tag=0;tag<=1;++tag)
			{
				if(save[pos]!=-1)
				{
					if(tag==0 && save[pos]<nxt)
						dp[pos][nxt][tag] = 0;
					else
						dp[pos][nxt][tag] = dp[pos-1][save[pos]][nxt>=save[pos]];
				}
				else
				{
					dp[pos][nxt][tag] += sum[0][nxt][1] - sum[0][tag ? 0 : nxt-1][1];
					dp[pos][nxt][tag] += sum[0][200][0] - sum[0][nxt][0];
				}
				dp[pos][nxt][tag] %= MOD;
				sum[1][nxt][tag] = sum[1][nxt-1][tag] + dp[pos][nxt][tag];
			}
		}
	}
	return dp[n][1][1];
}
int main(void)
{
	int n = read();
	for(int i=1;i<=n;++i)
		save[i] = read();

	printf("%d\n",solve(n) );
    return 0;
}

猜你喜欢

转载自blog.csdn.net/m0_37809890/article/details/83375642