题意:有m种字符,要求构造两段长度为n的字符串,其中这两段不能有相同的字符
枚举左边选了i种字符,右边可以选1,2....min(n,m-i)种字符
这样就把问题转化为用k种字符构造n长度的字符串的种类有多少种
容斥:单独考虑每一位上的字符都有k种选择,k^n,但会有不够k种的情况,所以要减去只有k-1种颜色,只有k-2种颜色。。。。的情况
这里是用容斥处理一下
好,上面都是copy过来的东西,现在是自己写的了。。 首先,这道题目比赛的时候没写出来,虽然当时想到了用容斥处理,但是对容斥的情况没有理解清楚。
复习一下容斥,写容斥的时候,我们先要理出最小集合,这些集合的并能够得到我们想到的结果;在在就是集合的交有了一个新的认识,以前的理解太肤浅了。
对于这道题目,我们定理f(x)为x个字母全部用上的方案数,利用容斥,一般要考虑逆问题。f(x)的逆问题———k个元素中,至少有一个字母没用上的方案数。
那么最小集合为有一个字母没用上,其他字母可用可不用的情况,之后的容斥就可以了。
附上一个骚气的代码(自己写的老是超时,有点烦,贴了个其他大佬的):
#include <cstdio> #include <iostream> typedef long long LL; using namespace std; const int maxn =2005; const int mod = 1e9+7; LL res[maxn]; LL C[maxn][maxn]; LL mul[maxn][maxn]; LL mult(LL a,LL b) { if(mul[a][b])return mul[a][b]; // 这个记录的方式也是。。。 长见识了 LL ans=1,aa=a,bb=b; a%=mod; while(b) { if(b&1)ans=(ans*a)%mod; a=(a*a)%mod; b>>=1; } mul[aa][bb]=ans; return ans; } void init() { C[0][0]=1; for(int i=1; i<maxn; i++) for(int j=0; j<=i; j++) C[i][j]=(C[i-1][j-1]+C[i-1][j])%mod; } LL getget(LL n,LL m) { LL cnt=0; for(int k=0; k<=m; k++) { if(k&1)cnt=(cnt-C[m][k]*mult(m-k,n)%mod)%mod; else cnt=(cnt+C[m][k]*mult(m-k,n)%mod+mod)%mod; } return cnt; } template <class T> inline void scan_d(T &ret) { char c; ret = 0; while ((c = getchar()) < '0' || c > '9'); while (c >= '0' && c <= '9') ret = ret * 10 + (c - '0'), c = getchar(); } void Out(LL a) { if (a < 0) { putchar('-'); a = -a; } if (a >= 10) Out(a / 10); putchar(a % 10 + '0'); } int main() { init(); int T; scan_d(T); while(T--) { memset(res,0,sizeof(res)); int n,m; LL ans=0; scan_d(n); scan_d(m); for(int i=1; i<m; i++) res[i]=getget(n,i); for(int i=1; i<m; i++) for(int j=i; j<=m-i; j++)// 这里枚举两个数的时候,采用有序枚举的形式,降低一点复杂度,还可以去重。 { LL aa=C[m][i]*res[i]%mod; LL bb=C[m-i][j]*res[j]%mod; LL cnt = (aa*bb)%mod; if(i!=j) cnt = (cnt+cnt)%mod; ans=(cnt+ans)%mod; } Out(ans); putchar(' '); } return 0; }