【AtCoder】AtCoder Grand Contest 024 解题报告(A~E)

点此进入比赛

(A):Fairness(点此看题面

  • 给定(A,B,C),每次操作令(A=B'+C',B=A'+C',C=A'+B')
  • (K)次操作后(A-B)的值。
  • (A,B,Cle10^9,Kle10^{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
",(k&1)?B-A:A-B),0;//判断k的奇偶性输出答案
}

(B):Backfront(点此看题面

  • 给定一个长度为(n)的排列,每次操作你可以将一个数移到序列开头或结尾。
  • 问最少操作多少次使得整个序列有序。
  • (nle2 imes10^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-t),0;//每个数从比它小1的数转移
}

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

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

结论

先考虑判无解,如果(A_1>0)(exists iin[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
",ans),0;//分两类计算答案
}

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

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

枚举中心

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

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

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

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

代码:(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
",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 iin[1,n],A_{i})的字典序大于(A_{i-1})
  • (n,kle300)

奇怪的(DP)

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

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

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

转移分三种:(刷表)

  1. 这个位置后不放数:(f_{i,j,k-1} exttt{+=}f_{i,j,k}(k>0))
  2. 重新开始放下一个数字:(f_{i,j+1,i} exttt{+=}f_{i,j,k}(k=0))
  3. 放这个数:(f_{i+1,j,k} exttt{+=}f_{i,j,k} imes(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
",f[n][k][0]),0;//输出答案
}
原文地址:https://www.cnblogs.com/chenxiaoran666/p/AtCoderAGC024.html