\(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\)个位置可以放)。
转移分三种:(刷表)
- 这个位置后不放数:\(f_{i,j,k-1}\texttt{+=}f_{i,j,k}(k>0)\)。
- 重新开始放下一个数字:\(f_{i,j+1,i}\texttt{+=}f_{i,j,k}(k=0)\)。
- 放这个数:\(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;//输出答案
}