2025.11.24 省选模拟

神秘场。

\(0+100+70\) 不会做 T1 遗憾离场了。不过为啥 hxq 和 yjh 不会 T2。

A 排列(perm)

原题

首先考虑容斥。记 \(dp_x\) 表示选出 \(x\) 个位置不满足限制的方案数。如果我们已经知道了 \(dp_x\) 那么我们可以通过容斥:

\[ans=\sum dp_x\times (-1)^k(n-k)! \]

轻松算出答案。难点在于如何算 \(dp_x\)

赛时想到了考虑图论建模。在 \(i\) 相同的 \((p_i,q_i)\) 间连边。然后就是一个点覆盖问题。

但是我不会一般图点覆盖。所以我赛时就不会了。

然而实际上你考虑我们这个图由于是由两个排列中的点构成的,度数 \(=2\) 且仅仅会包含环。(原题中有自环,但是搬题人很好心的吧自环去了)。

考虑环怎么做。你发现直接在环上做基本做不了。直接短环成链分讨一下。

\(f_{i,j}\) 表示在一个长度为 \(i\) 的链上选 \(j\) 条边的方案数,\(g_{i,j}\) 表示在一个长度为 \(i\) 的环上选 \(j\) 条边的方案数。

我们在断环成链之后考虑如果我们不选被断掉的这条边,那么方案数就应该是 \(g_{i,j}=f_{i,j}\)

如果选了这个被断掉的边,那么这时候就和首尾的点有关了。

\(w_{i,j,0/1,0/1}\) 表示在一个长度为 \(i\) 的链上选 \(j\) 条边且有没有选首尾点的方案数。

显然可以得到此时的 \(g_{i,j}=w_{i,j-1,0,0}+w_{i,j-1,0,1}+w_{i,j-1,0,0}+w_{i,j-1,1,0}\)

所以我们就得到了环的答案:

\[g_{i,j}=f_{i,j}+w_{i,j-1,0,0}+w_{i,j-1,0,1}+w_{i,j-1,0,0}+w_{i,j-1,1,0} \]

那么我们该怎么算 \(f_{i,j}\) 呢,显然直接用 \(w\) 算就行了:

\[f_{i,j}=w_{i,j,0,0}+w_{i,j,0,1}+w_{i,j,1,0}+w_{i,j-1,1,1} \]

\(w\) 的转移也是简单的。这个转移和第一个点的状态无关:

\[w_{i,j,0/1,0}=w_{i-1,j,0/1,0}+w_{i-1,j,0/1,1}+w_{i-1,j-1,0/1,0} \]

\[w_{i,j,0/1,1}=w_{i-1,j-1,0/1,1}+w_{i-1,j-1,0/1,0} \]

然后对于 \(dp_x\) 的转移也是简单的。直接 \(dp_x=dp_{x-j}\times g_{i,j}\) 即可。

最后跑一个背包合并就结束了。复杂度 \(O(n^2)\)

#include<bits/stdc++.h>
using namespace std;
#define int long long 
inline int read(){
    int s=0,w=1;
    char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
    while(ch>='0'&&ch<='9')s=s*10+ch-'0',ch=getchar();
    return s*w;
}
inline void out(int x){
    if(x==0){putchar('0');return;}
    int len=0,k1=x,c[10005];
    if(k1<0)k1=-k1,putchar('-');
    while(k1)c[len++]=k1%10+'0',k1/=10;
    while(len--)putchar(c[len]);
}
const int N=1e3+5,mod=1e9+7;
int addmod(int x,int y){x+=y;if(x>=mod)x-=mod;return x;}
int submod(int x,int y){x-=y;if(x<0)x+=mod;return x;}
int mulmod(int x,int y){return x*y%mod;}
int powmod(int x,int y){
    int ans=1;while(y){
        if(y&1)ans=mulmod(ans,x);
        y>>=1;x=mulmod(x,x);
    }return ans;
}vector<int>edge[N];
int fac[N],dp[N],a[N],b[N],vis[N],f[N][N],g[N][N],t[N][N][2][2];
void dfs(int x,int fa,int &s){
    if(vis[x])return ;s++;vis[x]=1;
    for(auto v:edge[x]){
        if(v==fa)continue;
        dfs(v,x,s);
    }return ;
}
void solve(){
    dp[0]=fac[0]=1;int n=read(),ans=0;
    for(int i=1;i<=n;i++)dp[i]=0,edge[i].clear(),vis[i]=0;
    for(int i=1;i<=n;i++)fac[i]=mulmod(fac[i-1],i);
    for(int i=1;i<=n;i++)a[i]=read();
    for(int i=1;i<=n;i++)b[i]=read();
    for(int i=1;i<=n;i++){
        if(a[i]==b[i])continue;
        edge[a[i]].push_back(b[i]),edge[b[i]].push_back(a[i]);        
    }for(int i=1;i<=n;i++){
        if(a[i]!=b[i])continue;
        for(int j=n;j>=1;j--)dp[j]=addmod(dp[j],dp[j-1]);vis[dp[i]]=1;
    }t[2][0][0][0]=t[2][1][0][1]=t[2][1][1][0]=1,f[2][1]=2,f[2][0]=1,g[2][1]=4,g[2][2]=2;
    for(int i=3;i<=n;i++){
        t[i][0][0][0]=1;g[i][i]=2;
        for(int j=1;j<i;j++){
            t[i][j][0][0]=addmod(t[i-1][j][0][0],addmod(t[i-1][j][0][1],t[i-1][j-1][0][0]));
            t[i][j][1][0]=addmod(t[i-1][j][1][0],addmod(t[i-1][j][1][1],t[i-1][j-1][1][0]));
            t[i][j][0][1]=addmod(t[i-1][j-1][0][0],t[i-1][j-1][0][1]);
			t[i][j][1][1]=addmod(t[i-1][j-1][1][0],t[i-1][j-1][1][1]);
			f[i][j]=addmod(addmod(t[i][j][1][0],t[i][j][1][1]),addmod(t[i][j][0][0],t[i][j][0][1]));
			g[i][j]=addmod(addmod(f[i][j],mulmod(t[i][j-1][0][0],2)),addmod(t[i][j-1][0][1],t[i][j-1][1][0]));
        }
    }
    for(int i=1;i<=n;i++){
        if(vis[i])continue;
        int cnt=0;dfs(i,i,cnt);
        for(int k=n;k>=1;k--){
            for(int j=1;j<=min(k,cnt);j++){
                dp[k]=addmod(dp[k],mulmod(dp[k-j],g[cnt][j]));
            }
        }
    }
    for(int k=0;k<=n;k++)ans=addmod(ans,mulmod(powmod(mod-1,k),mulmod(dp[k],fac[n-k])));
    cout<<ans<<"\n";
}
signed main(){
    freopen("perm.in","r",stdin);
    freopen("perm.out","w",stdout);
    int t=read();
    while(t--)solve();
    return 0;
}

B 吃瓜(gourds)

原题

我的做法比较大众。正着做感觉后效性很强,不妨考虑倒着做。将答案拆分成合法操作序列数和对于每种合法操作序列的合法方案数。然后随便想想就做完了,下面着重记录题解给出的做法。

把这题拆成两部分:首先找出 C 吃了的寿司按顺序是哪些,再考虑 C 对所有寿司的排名。这两者是独立的,后者甚至与 A,B 序列无关,

第二部分是好做的,对于第 \(k\) 喜欢的寿司,要么已经被前 \(2(k-1)\) 步选了,要么现在选。直接设 \(dp_{i.j}\) 表示考虑到第 \(i\) 喜欢的寿司,已经考虑了前 \(j\) 轮选寿司,可以 \(O(n^3)\) 解决。

再考虑前半部分。设 \(f_{i,j,k}\) 表示 A 考虑到第 \(i\) 喜欢的寿司,B 考虑到第 \(j\) 喜欢的
瓜,前面有 \(k\) 个没填的 C 需要放的空位,然后现在轮到 A 向前移,\(g_{i,j,k}\) 同理,但轮到
B 向前移。对于 \(f_{i,j}\),若 \(i+1\) 在B 眼中劣于 \(j\),那说明 \(i+1\) 早已被选过了。

否则可以选择选上 \(i+1\) 或不选,不选就必须给 C 并 \(k←k−1\)

\(g_{i,j,k}\) 的转移同理,但是不一样在于若 \(j+1\)\(i\) 相同就不能转移,以及转回到 \(f\) 的时候要让 \(k←k+1\),代表 C 多了一个可选空位。

过程被 dp 刻画成了 A 和 B 不断移动到他们选寿司的位置,直到移动出这个序列。

时空复杂度均 \(O(n^3)\)

#include <bits/stdc++.h>
#define fi first
#define se second
#define pii pair<int,int>
#define mp make_pair
#define pb push_back
#define space putchar(' ')
#define enter putchar('\n')
#define MAXN 20000005
#define eps 1e-10
//#define ivorysi
using namespace std;
typedef long long int64;
typedef unsigned int u32;
typedef double db;
template<class T>
void read(T &res) {
	res = 0;T f = 1;char c = getchar();
	while(c < '0' || c > '9') {
		if(c == '-') f = -1;
		c = getchar();
	}
	while(c >= '0' && c <= '9') {
		res = res * 10 + c - '0';
		c = getchar();
	}
	res *= f;
}
template<class T>
void out(T x) {
	if(x < 0) {x = -x;putchar('-');}
	if(x >= 10) {
		out(x / 10);
	}
	putchar('0' + x % 10);
}
const int MOD = 1000000007;
int inc(int a,int b) {
	return a + b >= MOD ? a + b - MOD : a + b;
}
int mul(int a,int b) {
	return 1LL * a * b % MOD;
}
void update(int &x,int y) {
	x = inc(x,y);
}
int fpow(int x,int c) {
	int res = 1,t = x;
	while(c) {
		if(c & 1) res = mul(res,t);
		t = mul(t,t);
		c >>= 1;
	}
	return res;
}
int dp[150][405],N,f[150][405][405],w,ans;
int a[405],b[405],fac[405],invfac[405];
bool visa[405],visb[405],vis[405][405];
int cnt[405][405],g[405];
int A(int n,int m) {
	if(n < m) return 0;
	return mul(fac[n],invfac[n - m]);
}
void Solve() {
	read(N);
	for(int i = 1 ; i <= N ; ++i) read(a[i]);
	for(int i = 1 ; i <= N ; ++i) read(b[i]);
	fac[0] = 1;
	for(int i = 1 ; i <= N ; ++i) fac[i] = mul(fac[i - 1],i);
	invfac[N] = fpow(fac[N],MOD - 2);
	for(int i = N - 1 ; i >= 0 ; --i) invfac[i] = mul(invfac[i + 1],i + 1);
	dp[1][2] = 1;
	for(int i = 1 ; i < N / 3 ; ++i) {
		for(int j = 0 ; j <= i * 2 ; ++j) {
			for(int h = 0 ; h <= j; ++h) {
				update(dp[i + 1][j + 2 - h],mul(A(j,h),dp[i][j]));
			}
		}
	}
	for(int j = 0 ; j <= (N / 3) * 2 ; ++j) update(w,mul(dp[N / 3][j],fac[j]));
	for(int i = 1 ; i <= N ; ++i) {
		visa[a[i]] = 1;
		cnt[i][0] = i;
		memset(visb,0,sizeof(visb));
		for(int j = 1 ; j <= N ; ++j) {
			visb[b[j]] = 1;
			cnt[i][j] = cnt[i][j - 1];
			if(!visa[b[j]]) cnt[i][j]++;
			if(!visa[b[j]] && !visb[a[i]]) vis[i][j] = 1;
		}
	}
	for(int t = N / 3 ; t >= 1 ; --t) {
		memset(g,0,sizeof(g));

		for(int i = N ; i >= 1 ; --i) {
			int s = (t == N / 3);
			for(int j = N ; j >= 1 ; --j) {
				if(vis[i][j]) f[t][i][j] = mul(s,N - 3 * (N / 3 - t) - cnt[i][j]);
				update(s,g[j]);
				update(g[j],f[t + 1][i][j]);
			}
		}
	}
	for(int i = 1 ; i <= N ; ++i) {
		for(int j = 1 ; j <= N ; ++j) {
			update(ans,f[1][i][j]);
		}
	}
	ans = mul(ans,w);
	out(ans);enter;
}
int main() {
	freopen("gourds.in","r",stdin);
	freopen("gourds.out","w",stdout);
	Solve();
}

#include<bits/stdc++.h>
using namespace std;
// #define int long long
#define ll long long
inline int read(){
    int s=0,w=1;
    char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
    while(ch>='0'&&ch<='9')s=s*10+ch-'0',ch=getchar();
    return s*w;
}
inline void out(int x){
    if(x==0){putchar('0');return;}
    int len=0,k1=x,c[10005];
    if(k1<0)k1=-k1,putchar('-');
    while(k1)c[len++]=k1%10+'0',k1/=10;
    while(len--)putchar(c[len]);
}
const int N=405,mod=1e9+7;
ll addmod(ll x,ll y){x+=y;if(x>=mod)x-=mod;return x;}
ll submod(ll x,ll y){x-=y;if(x<0)x+=mod;return x;}
ll mulmod(ll x,ll y){return x*y%mod;}
ll dp[N/3][N][N],f[N][N];
bool va[N][N],vb[N][N];int a[N],b[N];
signed main(){
    // freopen("gourds.in","r",stdin);
    // freopen("gourds.out","w",stdout);
    int n=read();for(int i=0;i<=n;i++)fill(dp[0][i],dp[0][i]+n+1,1);
    for(int i=1;i<=n;i++)a[i]=read();
    for(int i=1;i<=n;i++)b[i]=read();
    for(int i=1;i<=n;i++){copy(va[i-1],va[i-1]+n+1,va[i]);va[i][a[i]]=1;}
    for(int i=1;i<=n;i++){copy(vb[i-1],vb[i-1]+n+1,vb[i]);vb[i][b[i]]=1;}
    for(int i=0;i<=n;i++){
        for(int j=0;j<=n;j++){
            f[i][j]=i;
            for(int k=1;k<=j;k++){
                if(!va[i][b[k]])f[i][j]++;
            }
        }
    }
    // for(int i=1;i<=n;i++){
    //     for(int j=1;j<=n;j++){
    //         cout<<f[i][j]<<" ";
    //     }puts("");
    // }
    for(int i=1;i<=n/3;i++){
        for(int j=i;j<=n;j++){
            for(int k=i;k<=n;k++){
                dp[i][j][k]=submod(addmod(dp[i][j][k-1],dp[i][j-1][k]),dp[i][j-1][k-1]);
                // cout<<dp[i][j][k]<<"\n";
                if(!va[j][b[k]]&&!vb[k][a[j]]){
                    // cout<<"qwq ";
                    // cout<<f[j][k]<<"\n";
                    dp[i][j][k]=addmod(dp[i][j][k],mulmod(dp[i-1][j-1][k-1],submod(mulmod(3,i),f[j][k])));
                }
            }
        }
    }ll ans=dp[n/3][n][n];//cout<<ans<<" ";
    for(int i=1;i<=n/3;i++)ans=mulmod(ans,mulmod(mulmod(3,i)-1,mulmod(3,i)-2));
    out(ans%mod);
    return 0;
}

C 图(graph)

原题

反人类构造,感觉没啥价值,先不补了。

posted @ 2025-11-25 16:36  无名之雾  阅读(2)  评论(0)    收藏  举报