Codeforces Round #688 (Div. 2) CF1453A-F 题解

A.
答案就是在行和列中同时出现的数的个数。

代码:

#include <iostream>
#include <cstdio>

const int maxn=107;

using namespace std;

int n,m,T,x,ans;
int a[maxn];

int main()
{
	scanf("%d",&T);
	while (T--)
	{
		scanf("%d%d",&n,&m);
		for (int i=1;i<=100;i++) a[i]=0;
		for (int i=1;i<=n;i++)
		{
			scanf("%d",&x);
			a[x]++;
		}
		ans=0;
		for (int i=1;i<=m;i++)
		{
			scanf("%d",&x);
			if (a[x]) ans++;
		}
		printf("%d\n",ans);
	}
 } 

B.
考虑差分,那么每一步就是把差分后的数组的一位 + 1 +1 +1或者 − 1 -1 1
所以不修改的答案就是 ∑ i = 2 n a b s ( a [ i ] − a [ i − 1 ] ) \sum_{i=2}^{n}abs(a[i]-a[i-1]) i=2nabs(a[i]a[i1])
考虑修改,如果改的是第一位,减少的步数为 a b s ( a [ 2 ] − a [ 1 ] ) abs(a[2]-a[1]) abs(a[2]a[1])
如果改的是第 n n n位,减少的步数为 a b s ( a [ n ] − a [ n − 1 ] ) abs(a[n]-a[n-1]) abs(a[n]a[n1])
否则减少的步数为 a b s ( a [ i ] − a [ i − 1 ] ) + a b s ( a [ i ] − a [ i + 1 ] ) − a b s ( a [ i + 1 ] − a [ i − 1 ] ) abs(a[i]-a[i-1])+abs(a[i]-a[i+1])-abs(a[i+1]-a[i-1]) abs(a[i]a[i1])+abs(a[i]a[i+1])abs(a[i+1]a[i1])。取最小即可。

代码:

#include <iostream>
#include <cstdio>
#include <cmath>
#define LL long long

const int maxn=2e5+7;

using namespace std;

int T,n;
LL sum,maxx;
LL a[maxn];

int main()
{
	scanf("%d",&T);
	while (T--)
	{
		scanf("%d",&n);
		for (int i=1;i<=n;i++) scanf("%lld",&a[i]);
		LL sum=0,maxx=0;
		
		for (int i=2;i<=n;i++) sum+=abs(a[i]-a[i-1]);
		maxx=abs(a[2]-a[1]);
		for (int i=2;i<n;i++)
		{
			LL x=abs(a[i]-a[i-1])+abs(a[i+1]-a[i])-abs(a[i+1]-a[i-1]);
			maxx=max(maxx,x);
		}
		LL x=abs(a[n]-a[n-1]);
		maxx=max(maxx,x);
		printf("%lld\n",sum-maxx);
	}
}

C.
考虑 ( x 1 , y 1 ) (x_1,y_1) (x1,y1) ( x 2 , y 2 ) (x_2,y_2) (x2,y2)的最大三角形面积的 2 2 2倍。
考虑平行于 x x x轴, S = m a x ( x 1 − 1 , n − x 1 , x 2 − 1 , n − x 2 ) ∗ a b s ( y 1 − y 2 ) S=max(x_1-1,n-x_1,x_2-1,n-x_2)*abs(y_1-y_2) S=max(x11,nx1,x21,nx2)abs(y1y2)
平行于 y y y轴同理。
我们设 f [ a ] [ 0..9 ] f[a][0..9] f[a][0..9]表示所有纵坐标的 a a a的点的 m a x ( x − 1 , n − x ) max(x-1,n-x) max(x1,nx)的最大值。
枚举 i i i j j j表示三角形的点在这两行处,
S = m a x ( f [ i ] [ k ] , f [ j ] [ k ] ) ∗ a b s ( i − j ) S=max(f[i][k],f[j][k])*abs(i-j) S=max(f[i][k],f[j][k])abs(ij)
列同样处理即可。

代码:

#include <iostream>
#include <cstdio>
#include <cmath>

const int maxn=2007;

using namespace std;

int T,n;
int a[maxn][maxn];
char s[maxn];
int b[maxn][10],c[maxn][10],ans[10];

int main()
{
	scanf("%d",&T);
	while (T--)
	{
		scanf("%d",&n);
		for (int i=1;i<=n;i++)
		{
			scanf("%s",s+1);
			for (int j=1;j<=n;j++) a[i][j]=s[j]-'0';
		}
		for (int i=1;i<=n;i++)
		{
			for (int k=0;k<=9;k++) b[i][k]=c[i][k]=0;
		}
		
		for (int i=1;i<=n;i++)
		{
			for (int j=1;j<=n;j++)
			{
				b[i][a[i][j]]=max(b[i][a[i][j]],max(j-1,n-j));
				c[j][a[i][j]]=max(c[j][a[i][j]],max(i-1,n-i));
			}
		}
		for (int k=0;k<=9;k++)
		{
			ans[k]=0;
			for (int i=1;i<=n;i++)
			{
				for (int j=i+1;j<=n;j++)
				{
					if ((b[i][k]) && (b[j][k]))
					{
						ans[k]=max(ans[k],(j-i)*max(b[i][k],b[j][k]));
					}
				}
			}
			for (int i=1;i<=n;i++)
			{
				for (int j=i+1;j<=n;j++)
				{
					if ((c[i][k]) && (c[j][k]))
					{
						ans[k]=max(ans[k],(j-i)*max(c[i][k],c[j][k]));
					}
				}
			}
			printf("%d ",ans[k]);
		}
		printf("\n");
	}2
}

D.
f [ i ] f[i] f[i]为从 1 1 1走到 i i i且中途没有记录点的期望步数。
可以推出 f [ 1 ] = 2 f[1]=2 f[1]=2 f [ i ] = f [ i − 1 ] ∗ 2 + 2 f[i]=f[i-1]*2+2 f[i]=f[i1]2+2,通项即为 f [ i ] = 2 i + 1 − 2 f[i]=2^{i+1}-2 f[i]=2i+12
也就是一段长为 l e n len len 10000.....001 10000.....001 10000.....001的期望就是 2 l e n 2^{len} 2len
根据这个构造即可。

代码:

#include <iostream>
#include <cstdio>
#include <cmath>
#define LL long long

const int maxn=2007;

using namespace std;

int T,cnt;
int a[maxn];
LL n,bit[maxn];

int main()
{
	bit[1]=2;
	for (int i=2;i<=60;i++) bit[i]=bit[i-1]*2;
	scanf("%d",&T);
	while (T--)
	{
		scanf("%lld",&n);
		if (n&1)
		{
			printf("-1\n");
			continue;
		}
		cnt=0;
		for (int i=60;i>=3;i--)
		{
			if (n>=bit[i])
			{
				a[++cnt]=1;
				for (int j=1;j<=i-2;j++) a[++cnt]=0;
				a[++cnt]=1;
				n-=bit[i];
			}
		}
		while (n>0)
		{
			a[++cnt]=1;
			n-=2;
		}
		printf("%d\n",cnt);
		for (int i=1;i<=cnt;i++) printf("%d ",a[i]);
		printf("\n");
	}
}

E.
先二分一个答案 m i d mid mid,设 f [ x ] f[x] f[x]表示遍历完以 x x x为根的子树最后停下的点距离 x x x的最小值。
显然,如果 x x x为叶子节点, f [ x ] = 0 f[x]=0 f[x]=0
如果 x x x有子节点 f [ x ] > = m i d f[x]>=mid f[x]>=mid,或者有两个子节点 f [ x ] = m i d − 1 f[x]=mid-1 f[x]=mid1(遍历完一棵子树无法跳到另一棵),那么 x x x就无法到达。
如果只有一个子节点 f [ x ] = m i d − 1 f[x]=mid-1 f[x]=mid1,那么 f [ x ] = m i d f[x]=mid f[x]=mid
否则 f [ x ] = min ⁡ y ∈ x . s o n f [ y ] + 1 f[x]=\min_{y\in x.son}f[y]+1 f[x]=minyx.sonf[y]+1
如果可以到达根,则 m i d mid mid合法。

代码:

#include <iostream>
#include <cstdio>

const int maxn=2e5+7;

using namespace std;

int n,x,y,cnt,T,ans,mid;
int f[maxn],ls[maxn],size[maxn];

struct edge{
	int y,next;
}g[maxn*2];

void add(int x,int y)
{
	g[++cnt]=(edge){y,ls[x]};
	ls[x]=cnt;
}

void dfs(int x,int fa)
{
	int num=0;
	for (int i=ls[x];i>0;i=g[i].next)
	{
		int y=g[i].y;
		if (y==fa) continue;
		dfs(y,x);
		size[x]+=size[y];
		if ((f[y]==-1) || (f[y]>=mid))
		{
			f[x]=-1;
			return;
		}
		if (f[y]==mid-1) num++;
		f[x]=min(f[x],f[y]+1);
	}
	if (size[x]==1)
	{
		f[x]=0;
		return;
	}
	if (num>1) f[x]=-1;
	else if (num==1) f[x]=mid;
}

int main()
{
	scanf("%d",&T);
	while (T--)
	{
		scanf("%d",&n);
		for (int i=1;i<=n;i++) ls[i]=0;
		for (int i=1;i<n;i++)
		{
			scanf("%d%d",&x,&y);
			add(x,y);
			add(y,x);
		}
		int l=1,r=n;
		ans=n;
		while (l<=r)
		{
			mid=(l+r)/2;
			for (int i=1;i<=n;i++)
			{
				f[i]=1e9;
				size[i]=1;
			}
			dfs(1,0);
			if (f[1]==-1) l=mid+1;
			         else ans=mid,r=mid-1;
		}
		printf("%d\n",ans);
	}
}

F.
f [ i ] [ j ] f[i][j] f[i][j]为最后一步是从 i i i j j j,且路径唯一需要删除的点的最小值。
c n t cnt cnt表示 i i i j j j间有多少个位置能到达 j j j,如果能到达都需要删去,显然 i i i都可以到达这些位置。
f [ i ] [ j ] = min ⁡ k = 1 i − 1 f [ k ] [ i ] + c n t   ( k + a [ k ] < j ) f[i][j]=\min_{k=1}^{i-1}f[k][i]+cnt\ (k+a[k]<j) f[i][j]=mink=1i1f[k][i]+cnt (k+a[k]<j)
可以知道,这样处理可以保证路径唯一。
其中 c n t cnt cnt可以通过倒着枚举 i i i统计。
我们设 g [ i ] = min ⁡ k = 1 i − 1 f [ k ] [ i ]   ( k + a [ k ] < j ) g[i]=\min_{k=1}^{i-1}f[k][i]\ (k+a[k]<j) g[i]=mink=1i1f[k][i] (k+a[k]<j)
我们可以在 k + a [ k ] k+a[k] k+a[k]处打一个标记,当枚举到这个标记时,更新 g g g即可。
复杂度为 O ( n 2 ) O(n^2) O(n2)

代码:

#include <iostream>
#include <cstdio>
#include <cmath>
#include <vector>

const int maxn=3007;

using namespace std;

int n,T;
int f[maxn][maxn],g[maxn];
int a[maxn],v[maxn];
vector <int> b[maxn];

int main()
{
	scanf("%d",&T);
	while (T--)
	{
		scanf("%d",&n);
		for (int i=1;i<=n;i++)
		{
			scanf("%d",&a[i]);
			b[i+a[i]].push_back(i);
		}
		for (int i=1;i<=n;i++)
		{
			for (int j=1;j<=n;j++) f[i][j]=0x3f3f3f3f;
			g[i]=0x3f3f3f3f;
			v[i]=0;
		}
		g[1]=0;		
		for (int j=2;j<=n;j++)
		{
			int cnt=0;
			for (int i=j-1;i>0;i--)
			{
				if (i+a[i]>=j)
				{
					f[i][j]=g[i]+cnt;
					if (v[i]) g[j]=min(g[j],f[i][j]);
					cnt++;
				}
			}
			for (int i=0;i<b[j].size();i++)
			{
				v[b[j][i]]=1;
				for (int k=b[j][i]+1;k<=j;k++) g[k]=min(g[k],f[b[j][i]][k]);
			}
			b[j].clear();
		}
		printf("%d\n",g[n]);
	}
 } 
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值