AGC 046 部分简要题解
D - Secret Passage
考虑倒推:每次就是选择一个之前的字符扔到前面来,并且再新加一个字符。考虑哪些字符会扔到前面,显然是删除尽量少的字符满足剩下的是原串后缀。那么每个状态可以用 \(i,j,k\) 表示,即当前串长为 \(i\) ,有 \(j\) 个 \(0\) ,\(k\) 个 \(1\) 需要插入到前面。
剩下的就是一个 trivial 的 dp 了。
#include<bits/stdc++.h>
using namespace std;
const int N = 310;
typedef long long ll;
const int mod = 998244353;
inline int add(int a,int b){a+=b;return a>=mod?a-mod:a;}
inline int sub(int a,int b){a-=b;return a<0?a+mod:a;}
inline int mul(int a,int b){return 1ll*a*b%mod;}
inline int qpow(int a,int b){int ret=1;for(;b;b>>=1,a=mul(a,a))if(b&1)ret=mul(ret,a);return ret;}
/* math */
int g[N][N][N],f[N][N][N],n;
// j zeros to add, k ones to add;
char s[N];
int main()
{
scanf("%s",s+1);
n=strlen(s+1);reverse(s+1,s+n+1);
s[n+1]='2';
f[0][0][0]=g[0][0][0]=1;
for(int i=0;i<n;i++){
for(int j=0;j<=i;j++){
for(int k=0;j+k<=i;k++){
if(g[i][j][k]){
int _d=i-j-k+1;
int dir=s[_d]-'0';
g[i+1][j][k]=add(g[i][j][k],g[i+1][j][k]);
if(dir)g[i+1][j+1][k]=add(g[i+1][j+1][k],g[i][j][k]);
else g[i+1][j][k+1]=add(g[i+1][j][k+1],g[i][j][k]);
}
}
}
}
f[n][0][0]=1;
for(int i=n-1;i;i--){
for(int j=0;j<=i;j++){
for(int k=0;j+k<=i;k++){
int dir=s[i-j-k+1]-'0',dir2=s[i-j-k+2]-'0';
int nt[3];
nt[0]=j,nt[1]=k,nt[2]=0;nt[0]++;
if(nt[dir]){
nt[dir]--;
if(nt[dir2]&&(dir2==0||dir==0))nt[dir2]--;
}
f[i][j][k]|=f[i+1][nt[0]][nt[1]];
nt[0]=j,nt[1]=k;nt[1]++;
if(nt[dir]){
nt[dir]--;
if(nt[dir2]&&(dir2==1||dir==1))nt[dir2]--;
}
f[i][j][k]|=f[i+1][nt[0]][nt[1]];
}
}
}
int ans=0;
for(int i=1;i<=n;i++)
for(int j=0;j<=i;j++)for(int k=0;k+j<=i;k++)
if(f[i][j][k])ans=add(ans, g[i][j][k]);
cout << ans << endl;
return 0;
}
E - Permutation Cover
充要条件是所有长度为 \(k\) 的排列覆盖整个序列。考虑如果有三个排列,同时覆盖一个点,我们只需要考虑其中的两个。
枚举最终要考虑 \(k\) 个序列,那么每个数字需要覆盖所有的序列,那么显然最少需要 \(\lceil\frac{k}{2} \rceil\) 个,最多 \(k\) 个。
那么不难得到:有解当且仅当:
现在考虑贪心,每次贪心加入新的一段排列(可能与之前有的重合)。考虑当前序列是 \(S\),最后 \(k\) 个是 \(P\)(显然是个排列),\(i\) 还要加入 \(b_i\) 个,考虑剩下的区间有 \(t\) 个,类似前面,不同的是最前面的一段是确定的,所以当 \(\min(b_i)*2 +1 = \max(b_i)\) 的时候,仍然是有可能的,但是需要有一个附加条件:\(P\) 中所有的 \(i\) 满足 \(b_i=\max(b)\) 要在 \(b_i=\min(b)\) 前面。
所以确定了拓展的长度之后,剩下的就是一个简单的贪心,复杂度 \(O(nk^2)\)。
#include<bits/stdc++.h>
using namespace std;
const int K = 110, N = 1010;
int a[K],n,k;
int ret[N], curlen;
inline bool check(){
int mn=a[1], mx=a[1];
for(int i=1;i<=k;i++)mx=max(mx,a[i]), mn=min(mn,a[i]);
return mn*2>=mx;
}
int vis[K];
inline void merge(int *x,int *y,int *z,int len1,int len2){
int i=1,j=1;
int l=0;
while(i<=len1&&j<=len2){
if(x[i]<y[j])z[++l]=x[i++];
else z[++l]=y[j++];
}
while(i<=len1)z[++l]=x[i++];
while(j<=len2)z[++l]=y[j++];
}
int b[K], x[K], y[K], z[K], l1, l2, l3;
vector<int> solve_ext(int len){
l1=l2=l3=0;
for(int i=1;i<=k;i++)vis[i]=0,b[i]=a[i];
for(int i=0;i<k-len;i++){
vis[ret[curlen-i]]=curlen-i;
}
for(int i=1;i<=k;i++){if(!vis[i])b[i]--;if(b[i]<0)return vector<int>(1,k+1);}
int mn,mx;mn=mx=b[1];
for(int i=1;i<=k;i++){
mn=min(mn, b[i]);
mx=max(mx, b[i]);
}
if(mn*2>=mx){
vector<int> ret;
for(int i=1;i<=k;i++)if(!vis[i])ret.push_back(i);
return ret;
}
else if(mn*2+1==mx){
vector<int> res;
for(int i=1;i<=k;i++)if(!vis[i] && b[i]==mx)x[++l1]=i;
for(int i=1;i<=k;i++)if(!vis[i] && b[i]==mn)x[++l1]=i;
for(int i=1;i<=k;i++)if(!vis[i] && b[i]!=mx && b[i]!=mn)y[++l2]=i;
merge(x,y,z,l1,l2);l3=l1+l2;
res=vector<int>(z+1,z+l3+1);
int p1=0,p2=0;
for(int i=curlen-(k-len-1);i<=curlen;i++){
if(b[ret[i]]==mx)p1=i;
if(b[ret[i]]==mn&&!p2)p2=i;
}
for(int i=1;i<=l3;i++){
if(b[z[i]]==mx)p1=i+curlen;
if(b[z[i]]==mn&&!p2)p2=i+curlen;
}
if(p1<p2)return res;
else return vector<int>(1,k+1);
}else
return vector<int>(1,k+1);
}
bool cmp(vector<int> a, vector<int> b){
for(size_t i=0;i<a.size();i++){
if(i>=b.size())return 1;
if(a[i]>b[i])return 1;
if(a[i]<b[i])return 0;
}
return 0;
}
inline void extend(){
vector<int> ext(1,k+1);
for(int extlen=1;extlen<=k&&curlen+extlen<=n;extlen++){
int d=k-extlen;
if(curlen-d+1>0){
vector<int> nw=solve_ext(extlen);
if(cmp(ext,nw))ext=nw;
}
}
for(size_t i=0;i<ext.size();i++){
printf("%d ",ext[i]);
ret[++curlen]=ext[i];
a[ext[i]]--;
}
return ;
}
int main()
{
cin >> k;
for(int i=1;i<=k;++i)scanf("%d",&a[i]),n+=a[i];
if(!check()){
puts("-1");
return 0;
}
while(curlen<n){
extend();
}puts("");
}
F - Forbidden Tournament
图一定是前面一堆大小为 \(1\) 的强连通分量,然后跟一个大的联通分量。
先枚举这个长度,剩下的即求 \(n-i\) 个点,入度小于等于 \(k-i\) ,要求整个图是强连通分量,我们考虑解决这个问题。
考虑 \(1\) 号节点的所有出度 \(T\),剩下的点集合是 \(S\) 。显然的是 \(S\) 中不可能有三圆环,因为三个点都会指向 \(1\)。
考虑有连向 \(S\) 集合的边的节点集合 \(W\),剩下的是 \(W_2\),显然 \(W\) 不可能是空集。考虑如果 \(T\) 中的 \(x\) 指向了 \(T\) 中的 \(y\) ,\(S\) 中的 \(t\) 则有:\(x,t,1,y\) 四个节点不满足要求。所以 \(y\) 也应该指向 \(t\) 。于是 \(W_2\) 中所有节点不存在 \(W\) 中的入边,且 \(W\) 中无三圆环(否则三个点必定同时指向一个节点)。因为 \(W_2\) 中都向 \(W\) 中的点连边,\(W_2\) 也不能有三圆环。
所以 \(T\) 也是一个 \(\text{DAG}\)。
现在给 \(S\) 中的点按照拓扑序排序 \(x_1, x_2, \cdots, x_k\),\(T\) 中同理排成 \(y_1,y_2,\cdots ,y_l\)。我们只需要考虑他们之间的边。将其写成一个矩阵。
因为 \(y_i\) 连向 \(x_t\) 则 \(\forall j\ge i, t_j\) 连向 \(x_t\) (上证)。所以一定有 \(y_l\to x_1\)(否则 \(x_1\) 没有入度,不是强连通分量)。那么考虑 \(y_i, y_l, x_1, x_t\),有:\(y_i\to y_l,y_l\to x_1,x_1\to x_t,y_1\to x_t,y_l\to x_t\),所以一定是 \(y_l\to x_1\) 。继续考虑:对于 \(\forall j\ge i\),有 \(y_j\to x_1,y_j\to x_t\)。那么考虑:\(y_j,y_l,x_1,x_t\),可以得到 \(y_j\to x_t\)。
所以若矩阵上 \((i,j)=1\) 表示 \(y_j\to x_i\),那么 \((i,j)=1\) 可以得到 \((i-1,j)=1, (i,j+1) =1\)。剩下的就是一个简单 dp。
复杂度 \(O(n^4)\)。
#include<bits/stdc++.h>
using namespace std;
const int N = 210;
int mod;
inline int add(int a,int b){a+=b;return a>=mod?a-mod:a;}
inline int sub(int a,int b){a-=b;return a<0?a+mod:a;}
inline int mul(int a,int b){return 1ll*a*b%mod;}
inline int qpow(int a,int b){int ret=1;for(;b;b>>=1,a=mul(a,a))if(b&1)ret=mul(ret,a);return ret;}
/* math */
int n,_lim;
int binom[N][N],fac[N];
int dp[N][N];
inline int getmat(int k,int l,int lim){
for(int i=1;i<=l;i++){
for(int j=0;j<k;j++){
if(i==1&&!j)dp[i][j]=1;
else dp[i][j]=0;
if(i)dp[i][j]=add(dp[i][j],dp[i-1][j]);
if(j)dp[i][j]=add(dp[i][j],dp[i][j-1]);
if(l-i+1+j-1>lim&&j)dp[i][j]=0;
}
for(int j=0;j<k;j++)if(k-j+i-1>lim)dp[i][j]=0;
}
int ans=0;
for(int j=1;j<k;j++)ans=add(ans, dp[l][j]);
return ans;
}
inline int solve(int lim,int n){
if(n==1)return 0;
if(n==0)return 1;
if(lim<0)return 0;
int ret=0;
for(int k=1;k-1<=lim&&k<=n;k++){
int tmp=mul(binom[n-1][k-1],mul(fac[n-k],fac[k-1]));
ret=add(ret,mul(tmp, getmat(k,n-k,lim)));
}
return ret;
}
int main()
{
cin >> n >> _lim >> mod;
for(int i=0;i<=n;i++)for(int j=binom[i][0]=1;j<=i;j++)binom[i][j]=add(binom[i-1][j],binom[i-1][j-1]);
for(int i=fac[0]=1;i<=n;i++)fac[i]=mul(fac[i-1],i);
int ans=0;
for(int i=0;i<=_lim+1;i++){
ans=add(ans, mul(mul(fac[i],binom[n][i]),solve(_lim-i, n-i)));
}
cout << ans << endl;
}