1 条题解

  • 0
    @ 2026-8-27 16:20:57

    题意已经很清楚了,就是问我们有多少种合法的握手方式,使得每个小朋友都能和两个人握手。

    我们发现 n8n \le 8,所以我们考虑轮廓线 DP(不会的请翻到最后),每一位上 11 代表和右边的人握手,否则就不握。然后就可以做了。

    我们发现 m2311m \le 2^{31}-1,但是 k100k \le 100,所以我们可以猜测对于每两个有老师的列的中间那些没有老师的列,我们可以快速解决,考虑矩阵优化 DP。我们发现对于没有老师的列之间转移是平凡的,所以我们可以预处理出从 ii 转移到 jj 的矩阵,然后就可以做了。

    所以整道题的思路就是先预处理出每一个状态 ii 转移到另一个状态 jj 的矩阵。然后对于有老师的一列做轮廓线 DP,对于两个老师之间的空列就直接矩阵快速幂就可以了。

    时间复杂度 O((k+2n)n2n+23nlogl)O((k+2^n)n2^n + 2^{3n} \log l),其中 ll 为所有连续空列的长度之积。因为 n8,k100,m2311n \le 8, k \le 100, m \le 2^{31}-1。又因为常数比较大,所以可能超时,考虑因为每次做矩阵快速幂的时候会重复算多次转移矩阵的 kk 次方,故我们可以通过预处理将其存下来,时间复杂度就优化成 O((k+2n)n2n+22nlogl+23nlogm)O((k+2^n)n2^n + 2^{2n}\log l + 2^{3n} \log m)。可以通过此题。

    #include<bits/stdc++.h>
    using namespace std;
    #define N 100005
    #define intl long long
    #define mod (1000000007)
    #define For(i,a,b) for(intl i=a;i<=b;i++)
    #define deo(i,a,b) for(intl i=a;i>=b;i--)
    intl read() {
    	intl x=0,k=1;char ch=getchar();
    	while(!isdigit(ch)) {if(ch == '-') k=-1;ch=getchar();}
    	while(isdigit(ch)) {x=(x<<3)+(x<<1)+(ch^48);ch=getchar();}
    	return x*k;
    }
    intl n, m, k; 
    map<intl,vector<intl>> pos;
    void get(auto &res, intl s) {
    	vector<vector<intl>> dp(1ll<<n, vector<intl>(2,0));
    	For(i,0,(1ll<<n)-1) dp[i][0] = res[i];
    	For(i,0,n-1) {
    		intl flg = (s>>i)&1;
    		vector<vector<intl>> tmp(1ll<<n, vector<intl>(2,0));
    		For(j,0,(1ll<<n)-1) For(v,0,1) if(dp[j][v]) {
    			intl l = (j>>i)&1;
    			For(r,0,1) For(vt,0,1) if(!(i == n-1 && vt)){
    				if(flg) {
    					if(l + r + v + vt == 0) (tmp[j][vt] += dp[j][v]) %= mod;
    				} else {
    					if(l + r + v + vt == 2) (tmp[(j&(~(l<<i))|(r<<i))][vt] += dp[j][v]) %= mod;
    				}
    			}
    		}
    		dp = tmp;
    	}
    	For(i,0,(1ll<<n)-1) res[i] = dp[i][0];
    }
    struct Mat{
    	intl a[256][256];
    	Mat operator * (const Mat&b) const {
    		Mat res;memset(res.a,0,sizeof res.a);
    		For(k,0,(1ll<<n)-1) For(i,0,(1ll<<n)-1) if(a[i][k]) For(j,0,(1ll<<n)-1) if(b.a[k][j]) 
    			(res.a[i][j] += a[i][k] * b.a[k][j]) %= mod;
    		return res;
    	}
    	vector<intl> operator + (const vector<intl>&b) const {
    		vector<intl> res((1ll<<n),0);
    		For(i,0,(1ll<<n)-1) For(j,0,(1ll<<n)-1) (res[i] += a[i][j]*b[j]) %= mod;
    		return res;
    	}
    }mat[32]; 
    void Fpowv(intl b,auto &res) {
    	intl cnt = 0;
    	for(;b;b>>=1,cnt ++) if(b&1) res = mat[cnt] + res; 
    }
    int main() {
    	n = read(), m = read(), k = read();
    	vector<intl> dp(1ll<<n);
    	For(i,1,k) {
    		intl x = read()-1, y = read();
    		pos[y].push_back(x);		
    	}
    	For(i,0,(1ll<<n)-1) {
    		vector<intl> init(1ll<<n,0);
    		init[i] = 1;
    		get(init,0);
    		For(j,0,(1ll<<n)-1) mat[0].a[j][i] = init[j];
    	}
    	For(i,1,31) mat[i] = mat[i-1]*mat[i-1];
    	intl las = 1;dp[0] = 1; 
    	for(auto [c,vec]:pos) {
    		if(c > las) Fpowv(c-las,dp);
    		intl mask = 0;
    		for(auto x:vec) mask |= (1ll<<x);
    		get(dp, mask);
    		las = c + 1;
    	}
    	if(las <= m) Fpowv(m-las+1, dp); 
    	printf("%lld\n", dp[0]);
    	return 0;
    }
    
    

    轮廓线DP

    • 1

    信息

    ID
    5994
    时间
    1000ms
    内存
    128MiB
    难度
    10
    标签
    递交数
    1
    已通过
    1
    上传者