【AtCoder】AtCoder Grand Contest 024 解题报告($A\sim E$)

点此进入比赛

\(A\):Fairness(点此看题面

  • 给定\(A,B,C\),每次操作令\(A=B'+C',B=A'+C',C=A'+B'\)。
  • 求\(K\)次操作后\(A-B\)的值。
  • \(A,B,C\le10^9,K\le10^{18}\)

签到题

考虑同时将\(A,B,C\)减去一个数并不会影响答案。

一次操作后\(A=B'+C',B=A'+C',C=A'+B'\),故\(A-B=B'-A'\)。

两次操作后\(A=2A'+B'+C',B=2B'+A'+C',C=2C'+A'+B'\),同时减去\(A'+B'+C'\)得到\(A=A',B=B',C=C'\)。

所以只要判断\(K\)的奇偶性即可。

代码:\(O(1)\)

#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
using namespace std;
int A,B,C;long long k;
int main()
{
	return scanf("%d%d%d%lld",&A,&B,&C,&k),printf("%d\n",(k&1)?B-A:A-B),0;//判断k的奇偶性输出答案
}

\(B\):Backfront(点此看题面

  • 给定一个长度为\(n\)的排列,每次操作你可以将一个数移到序列开头或结尾。
  • 问最少操作多少次使得整个序列有序。
  • \(n\le2\times10^5\)

最长连续上升子序列

显然的结论,我们可以直接把要操作的数从序列中删去,因为它们能够以任意的顺序随意放在开头或结尾。

那么我们只要使得序列中剩余的数满足条件,这必然是一个连续上升的序列(注意这里的连续指的是数的值)。

于是问题就转化为求原序列的最长连续上升子序列,应该是很好做的。

代码:\(O(n)\)

#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 200000
using namespace std;
int n,f[N+5];
int main()
{
	RI i,x,t=0;for(scanf("%d",&n),i=1;i<=n;++i)
		scanf("%d",&x),t=max(t,f[x]=f[x-1]+1);return printf("%d\n",n-t),0;//每个数从比它小1的数转移
}

\(C\):Sequence Growing Easy(点此看题面

  • 给定一个长度为\(n\)的序列\(A\),你有一个初始全为\(0\)的序列\(X\)。
  • 每次操作你可以任选一个\(i\)令\(X_{i+1}=X_i+1\)。
  • 问最少操作多少次使得\(X\)变成\(A\)。
  • \(n\le2\times10^5,A\le10^9\)

结论

先考虑判无解,如果\(A_1>0\)或\(\exists i\in[2,n],A_i>A_{i-1}+1\),就无解。

否则,若\(A_i=A_{i-1}+1\),显然我们只要一次操作就可以直接从\(A_{i-1}\)得到\(A_i\)。

不然,只能操作\(A_i\)次专门生成\(A_i\)。

这个可以自行举几个例子画画图,应该是很好理解的。

代码:\(O(n)\)

 #include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 200000
using namespace std;
int n,a[N+5];long long ans;
int main()
{
	RI i;for(scanf("%d",&n),i=1;i<=n;++i) scanf("%d",a+i);
	if(a[1]) return puts("-1"),0;for(i=2;i<=n;++i) if(a[i]>a[i-1]+1) return puts("-1"),0;//判无解
	for(i=2;i<=n;++i) a[i]==a[i-1]+1?++ans:(ans+=a[i]);return printf("%lld\n",ans),0;//分两类计算答案
}

\(D\):Isomorphism Freak(点此看题面

  • 对于一棵无根树,若以某两点为根得到的有根树同构,就给这两点染上相同的颜色。
  • 给定一棵\(n\)个点的无根树,你可以任意添加节点。
  • 问最终得到的树至少需要染多少种颜色,且在此前提下最少有几个叶节点。
  • \(n\le100\)

枚举中心

考虑我们对这棵树枚举一个中心,可以是点,也可以是边。

当选中某一个中心后,最少的颜色数就是最远的叶节点的深度

为实现这个颜色数,我们需要把这棵树填满,也就是说,对于任意一层的节点,它们的叶节点数都相同

因此只要统计下每一层叶节点数的最大值,然后乘起来就是答案。

代码:\(O(n^2)\)

#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 100
#define add(x,y) (e[++ee].nxt=lnk[x],e[lnk[x]=ee].to=y)
#define LL long long
using namespace std;
int n,ee,lnk[N+5],Mx[N+5];struct line {int x,y;}l[N+5];
struct edge {int to,nxt;}e[N<<1];
I int Work(CI x,CI lst=0)//求最远的叶节点深度
{
	RI t=0;for(RI i=lnk[x];i;i=e[i].nxt) e[i].to^lst&&(t=max(t,Work(e[i].to,x)));return t+1;
}
I void dfs(CI x,CI lst=0,CI d=1)//统计每层叶节点数最大值
{
	RI t=0;for(RI i=lnk[x];i;i=e[i].nxt) e[i].to^lst&&(dfs(e[i].to,x,d+1),++t);Mx[d]=max(Mx[d],t);
}
I LL Calc(CI x,CI y=0)//计算叶节点数
{
	RI i;LL t=1;for(i=1;i<=n;++i) Mx[i]=0;dfs(x,y),y&&(dfs(y,x),0);
	for(i=1;i<=n&&Mx[i];++i) t*=Mx[i];return t*(y?2:1);//求乘积,如果以边为中心还要额外乘2
}
int main()
{
	RI i;for(scanf("%d",&n),i=1;i^n;++i)
		scanf("%d%d",&l[i].x,&l[i].y),add(l[i].x,l[i].y),add(l[i].y,l[i].x);
	RI s=n;for(i=1;i<=n;++i) s=min(s,Work(i));//以点为中心
	for(i=1;i^n;++i) s=min(s,max(Work(l[i].x,l[i].y),Work(l[i].y,l[i].x)));//以边为中心
	LL g=1LL<<60;for(i=1;i<=n;++i) Work(i)==s&&(g=min(g,Calc(i)));//以点为中心
	for(i=1;i^n;++i) (max(Work(l[i].x,l[i].y),Work(l[i].y,l[i].x)))==s&&(g=min(g,Calc(l[i].x,l[i].y)));//以边为中心
	return printf("%d %lld\n",s,g),0;//输出答案
}

\(E\):Sequence Growing Hard(点此看题面

  • 有\(n+1\)个值域为\([1,k]\)的序列\(A_0,A_1,...,A_n\),问有多少种可能的序列组满足下列条件:
    • \(A_0\)为空,其余\(A_i\)是由\(A_{i-1}\)插入一个元素得到的。
    • \(\forall i\in[1,n],A_{i}\)的字典序大于\(A_{i-1}\)。
  • \(n,k\le300\)

奇怪的\(DP\)

一个很套路的思路,就是从小到大插入数,这样明显随便插入都可以满足字典序的要求。

但是,为了避免计算重复,我们必须强制对于相同的数,新插入的数必须放在最后面。

因此设\(f_{i,j,k}\)表示进行到第\(i\)个操作,放到数字\(j\),有\(k\)个数之后可以放(加上开头,共有\(k+1\)个位置可以放)。

转移分三种:(刷表)

  1. 这个位置后不放数:\(f_{i,j,k-1}\texttt{+=}f_{i,j,k}(k>0)\)。
  2. 重新开始放下一个数字:\(f_{i,j+1,i}\texttt{+=}f_{i,j,k}(k=0)\)。
  3. 放这个数:\(f_{i+1,j,k}\texttt{+=}f_{i,j,k}\times(k+1)\)。

代码:\(O(n^3)\)

#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 300
#define Inc(x,y) ((x+=(y))>=X&&(x-=X))
using namespace std;
int n,k,X,f[N+5][N+5][N+5];
int main()
{
	RI i,j,x;scanf("%d%d%d",&n,&k,&X),f[0][1][0]=1;//初始化
	for(i=0;i<=n;++i) for(j=1;j<=k;++j) for(x=i;~x;--x)//DP
		x?Inc(f[i][j][x-1],f[i][j][x]):Inc(f[i][j+1][i],f[i][j][x]),//前两种转移
		f[i+1][j][x]=(1LL*(x+1)*f[i][j][x]+f[i+1][j][x])%X;//第三种转移
	return printf("%d\n",f[n][k][0]),0;//输出答案
}
上一篇:小鲁班客服电话——全国24小时帮助中心


下一篇:v27 下载