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\) 个。

那么不难得到:有解当且仅当:

\[\min(a_i)*2\ge \max(a_i) \]

现在考虑贪心,每次贪心加入新的一段排列(可能与之前有的重合)。考虑当前序列是 \(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;
}
posted @ 2020-07-01 19:07  jerome_wei  阅读(661)  评论(0编辑  收藏  举报