Preface
补题,这样一来军训这段时间口胡的题目就都写完了,后面空余的时间就优先做学校要求的专题了(主要是几何,因为字符串已经基本做完了)
唉现在的计数水平说实话练了这么多还是没有太大长进,有些巧妙的地方就是想不过去,所以打Atcoder就会很难受
不过相信多练总比不练好,可能到时候需要专门试着去总结一下计数题的套路和trick啥的
A - Ekiden Race
考虑如果\(i\)可能为答案,则必然不能存在一个\(j>i\),满足\(p_i>p_j\),因为这样\(j\)的返回速度一定比\(i\)快
因此答案就是所有值等于后缀最小值的位置数量
#include<cstdio>
#include<iostream>
#define RI register int
#define CI const int&
using namespace std;
const int N=1005;
int t,n,a[N];
int main()
{
//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
for (scanf("%d",&t);t;--t)
{
RI i; for (scanf("%d",&n),i=1;i<=n;++i) scanf("%d",&a[i]);
int mi=1e9,ans=0; for (i=n;i>=1;--i) if (mi=min(mi,a[i]),mi==a[i]) ++ans;
printf("%d\n",ans);
}
return 0;
}
B - Insertion Sort 2
首先简单手玩发现当逆序对数目为奇数时一定无解,否则一定有解
而构造答案的话我们考虑从前往后复原每个位置,如果\(a_i\ne i\)就把后面的\(i\)换到这一位上来
要注意如果\(i\)在位置\(n\)上,我们就需要先把最后两个数往前移一位再操作
举个例子比如当\(a=\{4,3,2,1\}\)时:
这也是为什么题目给出了\(2n\)次步数限制的原因
#include<cstdio>
#include<iostream>
#define RI register int
#define CI const int&
using namespace std;
const int N=1005;
int n,a[N],x[N<<1],y[N<<1],cnt,num;
int main()
{
//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
RI i,j; for (scanf("%d",&n),i=1;i<=n;++i) scanf("%d",&a[i]);
for (i=1;i<=n;++i) for (j=1;j<i;++j) num+=a[j]>a[i];
if (num&1) return puts("No"),0;
for (i=1;i<=n;++i)
{
if (a[i]==i) continue;
int pos; for (j=1;j<=n;++j) if (a[j]==i) { pos=j; break; }
if (pos==n)
{
x[++cnt]=n-1; y[cnt]=n-3; int tmp=a[n-2];
a[n-2]=a[n-1]; a[n-1]=a[n]; a[n]=tmp; pos=n-1;
}
x[++cnt]=pos; int t1=a[pos],t2=a[pos+1];
for (j=pos;j<=n-2;++j) a[j]=a[j+2];
y[cnt]=i-1; for (j=n-2;j>=i;--j) a[j+2]=a[j];
a[i]=t1; a[i+1]=t2;
//for (j=1;j<=n;++j) printf("%d%c",a[j]," \n"[j==n]);
}
for (printf("Yes\n%d\n",cnt),i=1;i<=cnt;++i) printf("%d %d\n",x[i],y[i]);
return 0;
}
C - Mex Game on Tree
比较简单的思维题,首先要注意到第二个人加入数一定是加\(K\)是最优的,因为这样可以导致这个点的所有祖先一定不满足要求
然后我们很容易发现如果第一个人想获胜,就必须只操作\(0\)次或\(1\)次就获胜,不然第二个人总能想办法阻止掉
而这两种情况都比较简单,因为数据范围较小,可以直接暴力枚举每个点然后枚举其子树内所有点,简单讨论一下即可
感觉应该可以想办法做到\(O(n\times \operatorname{Poly}(\log n))\)的,不过没有细想了
#include<cstdio>
#include<iostream>
#include<vector>
#define RI register int
#define CI const int&
using namespace std;
const int N=1005;
int t,n,k,x,a[N],bkt[N]; vector <int> v[N];
inline void DFS(CI now)
{
++bkt[~a[now]?a[now]:n+1]; for (int to:v[now]) DFS(to);
}
inline int mex(int *bkt)
{
int x=0; while (bkt[x]) ++x; return x;
}
int main()
{
//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
for (scanf("%d",&t);t;--t)
{
RI i,j; for (scanf("%d%d",&n,&k),i=1;i<=n;++i) v[i].clear();
for (i=2;i<=n;++i) scanf("%d",&x),v[x].push_back(i);
for (i=1;i<=n;++i) scanf("%d",&a[i]);
bool flag=0; for (i=1;i<=n&&!flag;++i)
{
for (j=0;j<=n+1;++j) bkt[j]=0; //-1->n+1
if (DFS(i),!bkt[n+1]) flag=mex(bkt)==k;
else if (bkt[n+1]==1)
{
if (bkt[k]) continue;
for (j=0;j<k;++j) if (!bkt[j]) { bkt[j]=1; break; }
flag=mex(bkt)==k;
}
}
puts(flag?"Alice":"Bob");
}
return 0;
}
D - Smallest Vertices
好题,不过因为我把Prufer序列这个东西忘完了所以第一步就无从下手了
一般对于这种边的选择是任意的树的个数统计(有边的限制的话就是矩阵树定理),尤其是和度数沾边的,都要考虑Prufer序列
但一般的Prufer序列都是针对无根树的,这里是以\(1\)为根的有根树怎么处理呢,很简单,我们修改定义要求每次选编号最大的节点删除,这样留下了的就一定是\(1\)号节点了
不过由于这里的度数的定义其实是出度,因此和一般情况还有点区别,具体地,满足题意的树的个数应该是:
然后考虑每个点的贡献,假设\(x\)是good的,那么它子树中的点\(y\)只能在\([x,n]\)中取
同时由于度数关系,设子树中的点集为\(S\),则\(\sum_{y\in S} d_y=|S|-1\)
然后我们试着写出它的贡献表达式,注意这里前面一部分是子树内的方案数,后面一部分是子树外的方案数:
不难发现计算答案只需要知道\(|S|\)即可,因此可以考虑DP,设\(f_{i,j,k}\)表示只用\(\ge i\)的点,其中选中的点的\(|S|=j\),且\(\sum_{y\in S} d_y=k\)的方案数
转移的话就是一个类似背包的形式,不过要注意特殊处理\(1\)号点和所有叶结点,它们的贡献就是整体的方案数
#include<cstdio>
#include<iostream>
#include<vector>
#define RI register int
#define CI const int&
using namespace std;
const int N=505,mod=998244353;
int n,d[N],fact[N],ifac[N],f[N][N][N],all,mul=1,ans;
inline void inc(int& x,CI y)
{
if ((x+=y)>=mod) x-=mod;
}
inline int quick_pow(int x,int p=mod-2,int mul=1)
{
for (;p;p>>=1,x=1LL*x*x%mod) if (p&1) mul=1LL*mul*x%mod; return mul;
}
inline void init(CI n)
{
RI i; for (fact[0]=i=1;i<=n;++i) fact[i]=1LL*fact[i-1]*i%mod;
for (ifac[n]=quick_pow(fact[n]),i=n-1;i>=0;--i) ifac[i]=1LL*ifac[i+1]*(i+1)%mod;
}
int main()
{
//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
RI i,j,k; for (scanf("%d",&n),init(n),i=1;i<=n;++i)
scanf("%d",&d[i]),mul=1LL*mul*ifac[d[i]]%mod;
for (all=1LL*fact[n-2]*mul%mod*d[1]%mod,f[n+1][0][0]=1,i=n;i>=1;--i)
{
for (j=1;j<=n;++j) for (k=d[i];k<=n;++k) inc(f[i][j][k],f[i+1][j-1][k-d[i]]);
if (!d[i]||i==1) inc(ans,all); else
for (j=2;j<=n;++j) inc(ans,1LL*f[i][j][j-1]*d[1]%mod*d[i]%mod*mul%mod*fact[n-1-j]%mod*fact[j-2]%mod);
for (j=0;j<=n;++j) for (k=0;k<=n;++k) inc(f[i][j][k],f[i+1][j][k]);
}
return printf("%d",ans),0;
}
E - Strange Constraints
好题,Atcoder的计数题都只能直呼高妙
不妨设\(x_i\)表示序列\(B\)中等于\(i\)的位置的个数,则\(\sum_{i=1}^n x_i=n\),且对于\(\forall i\in[1,n]\),均满足\(x_i\le A_i\)
考虑第二个限制,其实就是把所有的\(x_i\)分配到\(A_j\ge x_i\)的位置上,我们考虑给\(x_i\)降序排序使其满足\(x_1\ge x_2\ge\cdots\ge x_n\)
同时如果我们记\(S(i)=\sum_{j=1}^n [a_j\ge i]\),那么可以写出关于\(x_i\)的方案数表达式:
直观的想法就是对于每个位置的\(x_i\)讨论,虽然这样很容易满足\(x_i\le A_i\)的限制,但是却不能处理需要排序才能计算贡献的问题
那么我们这里就要想办法优先处理不好搞的这一块,我们考虑按照\(x_i\)从大到小的顺序来DP,同时我们发现原来要满足的\(x_i\le A_i\)的限制其实也很好处理
具体的,如果我们设\(f_{i,j,k}\)表示考虑了\(j\)个数,此时最小的数为\(i\),所有已经考虑了的\(x\)的和为\(k\)的方案数
转移的话我们枚举\(x=i\)的个数\(t\),则对于原来\(x_i\le A_i\)的限制,其实就是在\(S(i)-j\)个位置中选出\(t\)个来匹配
而后面那部分的系数可以拆开化简,具体地:
那么转移的式子就很显然了:
总体复杂度乍一看是\(O(n^4)\)的,但冷静分析一下会发现复杂度其实是:
因此可以直接通过这题,不过要注意最后的答案应该是\(f_{-1,n,n}\)因为不能忽略\(x_i=0\)的贡献
#include<cstdio>
#include<iostream>
#include<cstring>
#define RI register int
#define CI const int&
using namespace std;
const int N=505,mod=998244353;
int n,a[N],fact[N],ifac[N],f[2][N][N],g[N];
inline void inc(int& x,CI y)
{
if ((x+=y)>=mod) x-=mod;
}
inline int quick_pow(int x,int p=mod-2,int mul=1)
{
for (;p;p>>=1,x=1LL*x*x%mod) if (p&1) mul=1LL*mul*x%mod; return mul;
}
inline void init(CI n)
{
RI i; for (fact[0]=i=1;i<=n;++i) fact[i]=1LL*fact[i-1]*i%mod;
for (ifac[n]=quick_pow(fact[n]),i=n-1;i>=0;--i) ifac[i]=1LL*ifac[i+1]*(i+1)%mod;
}
inline int C(CI n,CI m)
{
if (n<m||n<0||m<0) return 0;
return 1LL*fact[n]*ifac[m]%mod*ifac[n-m]%mod;
}
int main()
{
//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
RI i,j,k,t; for (scanf("%d",&n),init(n),i=1;i<=n;++i)
scanf("%d",&a[i]),++g[a[i]]; for (i=n-1;i>=0;--i) g[i]+=g[i+1];
int nw=0; f[nw][0][0]=1; for (i=n;i>=0;--i,nw^=1)
{
memset(f[nw^1],0,sizeof(f[nw^1])); int coef;
for (j=0;j<=g[i];++j) for (k=0;k<=g[i];++k) if (f[nw][j][k])
for (t=0,coef=1;k+i*t<=g[i]&&j+t<=g[i];++t,coef=1LL*coef*ifac[i]%mod)
inc(f[nw^1][j+t][k+i*t],1LL*f[nw][j][k]*C(g[i]-j,t)%mod*fact[g[i]-k]%mod*coef%mod*ifac[g[i]-(k+i*t)]%mod);
}
return printf("%d",f[nw][n][n]),0;
}
Postscript
可惜今晚的ARC因为要开科研组会所以又没法现场打了,连着两周的ARC打不了有点难受的说
- AtCoder Regular Contest 162atcoder regular contest 162 atcoder regular contest 165 minimization atcoder regular contest atcoder regular contest 167 atcoder regular contest 164 atcoder regular contest 166 atcoder regular contest degree atcoder regular contest sliding atcoder regular contest builder atcoder regular contest 163