组合数学总结
---恢复内容开始---
又水过一个专题。。。教练说这题都不好做///
进了组合数学,当年叱吒的DP居然成了暴力。。。一些神奇的操作令我大口吃屎大开脑洞。
一些常写的代码
1 int qpow(int x,int k) 2 { 3 int ans=1; 4 for(;k;k>>=1,x=1ll*x*x%mod) if(k&1) ans=1ll*ans*x%mod; 5 return ans; 6 } 7 long long C(int n,int m) {return 1ll*fac[n]*inv[m]%mod*inv[n-m]%mod;} 8 int main() 9 { 10 scanf("%d%d",&n,&k);fac[0]=1; 11 for(int i=1;i<=n;i++) fac[i]=1ll*fac[i-1]*i%mod; 12 inv[n]=qpow(fac[n],mod-2); 13 for(int i=n;i;i--) inv[i-1]=1ll*inv[i]*i%mod;
然后有一些常用的知识
:乘法原理 加法原理 容斥原理 古典概型转组合数。。。 插空挡板等等
看一下题目长什么样。。
A.排队(自主研发) 这是个高考数学题 就是用插空法,但是要注意先考虑n 名男同学,m 名女同学和两名老师要排队,女生和老师都不能相邻,如果先让老师不邻,再考虑女生不相邻会算少,老师相邻时中间插女生会漏掉。
#75880 | #A. 排队 | Accepted | 100 | 674 ms | 604 KiB | starsing (平安) | 2019-06-26 18:50:09 |
#75800 | #A. 排队 | Wrong Answer | 20 | 428 ms | 608 KiB | starsing (平安) | 2019-06-26 15:58:51 |
#include<iostream> #include<cstring> #include<cstdio> #include<string> using namespace std; int a[4000000],b[4000020],c[4000020],n,m; void ch(int a[],int i) { int x=0,j; for(j=1;j<=a[0];j++) { a[j]=a[j]*i+x; x=a[j]/10; a[j]%=10; } a[j]=x; while(a[j]>9) { a[j+1]=a[j]/10; a[j]%=10; j++; } while(a[j]==0&&j>1) j--; a[0]=j; } void add(int a[],int b[]) { int i=1,x=0; while(i<=a[0]||i<=b[0]) { //cout<<a[i]<<" "<<b[i]<<" "; c[i]=a[i]+b[i]+x; //cout<<c[i]<<endl; x=c[i]/10; c[i]=c[i]%10; i++; } if(x!=0&&i>1) { c[0]=i; c[i]=x; } else c[0]=i-1; } int main() { scanf("%d%d",&n,&m); if(m>n+3) return printf("0\n"),0; a[0]=a[1]=1;b[0]=b[1]=1; ch(a,n);ch(a,n+1);ch(b,(2*n+2)*m); for(int i=n-m+4;i<=n+3;i++) ch(a,i); for(int i=n-m+4;i<=n+2;i++) ch(b,i); add(a,b); for(int i=1;i<=n;i++) ch(c,i); for(int i=c[0];i;i--) printf("%d",c[i]); puts(""); } //排队 题解 /* g++ 1.cpp -o 1 ./1 1 1 */
B.Perm排列计数(自主研发) Pi<Pi/2的1~n排列令我大口吃屎。。。居然是下标。。。。然后搞一个二叉树,左右子树不影响,递归DP一下(题眼),bobo说学过树的都应该想到。
#include<iostream> #include<cstdio> using namespace std; int n,p; int f[2000020],s[2000020],fac[2000020],inv[2000020]; void dfs(int k) { s[k]=1; if((k<<1)<=n) dfs(k<<1); if((k<<1|1)<=n) dfs(k<<1|1); s[k]+=s[k<<1]+s[k<<1|1]; //if(s[k]==1) f[k]=1 f[k]=1ll*fac[s[k]-1]*inv[s[k<<1]]%p*inv[s[k]-s[k<<1]-1]%p; if(f[k]==0)f[k]=1; if(f[k<<1]) f[k]=1ll*f[k]*f[k<<1]%p; if(f[k<<1|1]) f[k]=1ll*f[k]*f[k<<1|1]%p; //cout<<k<<" "<<s[k]<<" "<<f[k]<<endl; } int qpow(int x,int k) { int ans=1; for(;k;k>>=1,x=1ll*x*x%p) if(k&1) ans=1ll*x*ans%p; return ans; } int main() { scanf("%d%d",&n,&p);fac[0]=1; for(int i=1;i<=n*2+1;i++) f[i]=1; for(int i=1;i<=n;i++) fac[i]=1ll*i*fac[i-1]%p; inv[n]=qpow(fac[n],p-2); for(int i=n;i>=1;i--) inv[i-1]=1ll*i*inv[i]%p; dfs(1); printf("%d\n",f[1]); } /* g++ 1.cpp -o 1 ./1 20 23 */
C.集合计数(颓了一半题解) 会做么?不会就好题。是在大小为n的集合中选任意个集合,交集元素为k的方案个数。
我首先想到了把交集为k个提出来,然后求n-k个选交集为0,即f[n-k][0],可以从2^(2^(n-k))-1个减f[n-k][j]转移过来,递推n^2实现很简单就没打,然后想颓了一眼题解(容斥原理),但是难以理解,想了一天又去找DeepinC大神,发现其实是容斥集合的定义和本题的集合不太一样,容斥的集合是包含一个元素的所有方案组成的集合。。。容斥应该作为一个随想随用的算法,不要心理上耸掉,只要好好搞一个容斥系数就可以了
#include<iostream> #include<cstdio> #include<cmath> using namespace std; const int mn=1e6+20,mod=1e9+7; int f[mn],fac[mn],inv[mn],n,k; int qpow(int x,int k) { int ans=1; for(;k;k>>=1,x=1ll*x*x%mod) if(k&1) ans=1ll*ans*x%mod; return ans; } long long C(int n,int m) {return 1ll*fac[n]*inv[m]%mod*inv[n-m]%mod;} int main() { scanf("%d%d",&n,&k);fac[0]=1; for(int i=1;i<=n;i++) fac[i]=1ll*fac[i-1]*i%mod; inv[n]=qpow(fac[n],mod-2); for(int i=n;i;i--) inv[i-1]=1ll*inv[i]*i%mod; long long ans=0,tmp=C(n,k),tp=1;n-=k; int op=(n&1)?-1:1; for(int i=n;~i;i--) { ans=(ans+op*C(n,i)*tp%mod)%mod; tp=(tp*tp%mod+2*tp%mod)%mod; op=-op; } ans=ans*tmp%mod; printf("%lld\n",(ans+mod)%mod); } //for sigma i=0->n C(n,i)(2^(2^(n−i))−1) /* g++ 1.cpp -o 1 ./1 4 1 */
D.DZY loves math 2(全颓题解) 劳资他娘的颓了题解还不会,只能等我变强之后在回过头再做。。。
E.虔诚的母猪人墓主人 (暴力水过,正解算是颓了题解10个字) 这是个数据结构题,思维量很小,就想到一个离散化不影响答案,
然后每一行有一段段的横区间,区间内左右组合数一定,要维护竖着的ΣC上*C下就行了,没sort调了3节课骂了无数的街。。
#include<iostream> #include<cstdio> #include<algorithm> #include<vector> #define ll long long using namespace std; const unsigned int mod=2147483647; struct point { int x,y; bool friend operator <(const point &a,const point &b) { if(a.x==b.x) return a.y<b.y; return a.x<b.x; } }t[100020]; unsigned int x[100020],y[100020],lx[100020],ly[100020],n,m,nx,ny,w; unsigned int b[100020],s[100020],pre[100020]; unsigned int a[100020],C[100020][15]; vector<int> r[100020]; int rd() { int s=0,w=1; char cc=getchar(); while(cc<'0'||cc>'9') {if(cc=='-') w=-1;cc=getchar();} while(cc>='0'&&cc<='9') s=(s<<3)+(s<<1)+cc-'0',cc=getchar(); return s*w; } void add(int i,int w) {for(;i<=ny;i+=(i&(-i))) a[i]+=w;} unsigned int ask(int i) { unsigned int ans=0; for(;i;i-=(i&(-i))) ans+=a[i]; return ans; } bool cmp(const int &a,const int &b) { return t[a].y<=t[b].y; } int main() { //freopen("25.in","r",stdin); n=rd(),m=rd(); w=rd(); for(int i=1;i<=w;i++) { x[i]=lx[i]=rd(); y[i]=ly[i]=rd(); } int k=rd(); for(int i=0;i<=w;i++) C[i][0]=1; for(int i=1;i<=w;i++) for(int j=1;j<=min(i,k);j++) C[i][j]=C[i-1][j]+C[i-1][j-1]; sort(lx+1,lx+w+1);sort(ly+1,ly+w+1); nx=unique(lx+1,lx+w+1)-lx-1,ny=unique(ly+1,ly+w+1)-ly-1; for(int i=1;i<=w;i++) { t[i].x=lower_bound(lx+1,lx+nx+1,x[i])-lx; t[i].y=lower_bound(ly+1,ly+ny+1,y[i])-ly; } sort(t+1,t+w+1); for(int i=1;i<=w;i++) { r[t[i].x].push_back(i); pre[i]=s[t[i].y]++; } unsigned int ans=0; for(int i=1;i<=nx;i++) { //cout<<i<<endl; int len=r[i].size(); sort(r[i].begin(),r[i].end(),cmp); if(i!=1) { for(int j=k;j<=len-k;j++) { int ai=t[r[i][j]].y,bi=t[r[i][j-1]].y; if(ai<bi) swap(ai,bi); //if(ai==bi+1) continue; ans+=(ask(ai-1)-ask(bi))*C[j][k]*C[len-j][k]; //printf("%d %d %d %d\n",ask(ai-1)-ask(bi),j,C[j][k],ans); } } for(int j=0;j<len;j++) { int pr=pre[r[i][j]],nt=s[t[r[i][j]].y]-pr-1,ai=t[r[i][j]].y; int wi=C[pr+1][k]*C[nt][k]-ask(ai)+ask(ai-1); //printf("%d %d %d %d\n",j,ask(ai)-ask(ai-1),pr,nt); //cout<<r[i][j]<<" "<<pr+1<<" "<<nt<<endl; add(ai,wi); } } ans&=mod; printf("%d\n",ans); } /* n%(1<<k)=n&((1<<k)-1) g++ 1.cpp -o 1 ./1 5 6 13 0 2 0 3 1 2 2 0 1 3 3 3 2 1 2 4 2 5 2 6 3 2 5 2 4 3 2 */
F. 地精部落 (半题解) 他们都用两维数组做得。。。这是个DP题,要找无连续3个数单调,即锯齿状的序列,问题出在维护信息过多很难P。。然后从状压打起,逐渐减少维护信息,然后再从长度为i,最后一位为k,是峰或谷的状态定义逐渐发现转移错误,后来经tdcp点拨,外加自己思考,发现第二维会转移重复,然后想到其实谁是谁无所谓,只要关心排名是多少就行了,第二维定义为排名为k的方案。
一般的题解第一句话就是设XXX,转移XXX,然后代码。。。其实最关键的是状态的定义,定义对了转移就显然,状态的定义就要抓住题目的特殊条件,如果维护信息过多就先从状压想起,逐渐减少无用信息,一般就能想到正经状态了
---恢复内容结束---
又水过一个专题。。。
进了组合数学,当年叱吒的DP居然成了暴力。。。一些神奇的操作令我大口吃屎大开脑洞。
一般有一些常写的代码
1 int qpow(int x,int k) 2 { 3 int ans=1; 4 for(;k;k>>=1,x=1ll*x*x%mod) if(k&1) ans=1ll*ans*x%mod; 5 return ans; 6 } 7 long long C(int n,int m) {return 1ll*fac[n]*inv[m]%mod*inv[n-m]%mod;} 8 int main() 9 { 10 scanf("%d%d",&n,&k);fac[0]=1; 11 for(int i=1;i<=n;i++) fac[i]=1ll*fac[i-1]*i%mod; 12 inv[n]=qpow(fac[n],mod-2); 13 for(int i=n;i;i--) inv[i-1]=1ll*inv[i]*i%mod;
然后有一些常用的知识
:乘法原理加法原理 容斥原理
看一下题目长什么样。。
A.排队 这是个高考数学题 就是用插空法,但是要注意先考虑老师不相邻,在考虑女生不相邻会算少,老师相邻时中间插女生会漏掉。
B.Per排列 Pi<Pi/2令我大口吃屎。。。居然是下标。。。。然后搞一个二叉树左右子树不影响(题眼)
C.集合计数 这tm题日了我两天。。
---恢复内容结束---
又水过一个专题。。。
进了组合数学,当年叱吒的DP居然成了暴力。。。一些神奇的操作令我大口吃屎大开脑洞。
一些常写的代码
1 int qpow(int x,int k) 2 { 3 int ans=1; 4 for(;k;k>>=1,x=1ll*x*x%mod) if(k&1) ans=1ll*ans*x%mod; 5 return ans; 6 } 7 long long C(int n,int m) {return 1ll*fac[n]*inv[m]%mod*inv[n-m]%mod;} 8 int main() 9 { 10 scanf("%d%d",&n,&k);fac[0]=1; 11 for(int i=1;i<=n;i++) fac[i]=1ll*fac[i-1]*i%mod; 12 inv[n]=qpow(fac[n],mod-2); 13 for(int i=n;i;i--) inv[i-1]=1ll*inv[i]*i%mod;
然后有一些常用的知识
:乘法原理加法原理 容斥原理
看一下题目长什么样。。
A.排队(自主研发) 这是个高考数学题 就是用插空法,但是要注意先考虑n 名男同学,m 名女同学和两名老师要排队,女生和老师都不能相邻,如果先让老师不邻,再考虑女生不相邻会算少,老师相邻时中间插女生会漏掉。
#75880 | #A. 排队 | Accepted | 100 | 674 ms | 604 KiB | starsing (平安) | 2019-06-26 18:50:09 |
#75800 | #A. 排队 | Wrong Answer | 20 | 428 ms | 608 KiB | starsing (平安) | 2019-06-26 15:58:51 |
#include<iostream> #include<cstring> #include<cstdio> #include<string> using namespace std; int a[4000000],b[4000020],c[4000020],n,m; void ch(int a[],int i) { int x=0,j; for(j=1;j<=a[0];j++) { a[j]=a[j]*i+x; x=a[j]/10; a[j]%=10; } a[j]=x; while(a[j]>9) { a[j+1]=a[j]/10; a[j]%=10; j++; } while(a[j]==0&&j>1) j--; a[0]=j; } void add(int a[],int b[]) { int i=1,x=0; while(i<=a[0]||i<=b[0]) { //cout<<a[i]<<" "<<b[i]<<" "; c[i]=a[i]+b[i]+x; //cout<<c[i]<<endl; x=c[i]/10; c[i]=c[i]%10; i++; } if(x!=0&&i>1) { c[0]=i; c[i]=x; } else c[0]=i-1; } int main() { scanf("%d%d",&n,&m); if(m>n+3) return printf("0\n"),0; a[0]=a[1]=1;b[0]=b[1]=1; ch(a,n);ch(a,n+1);ch(b,(2*n+2)*m); for(int i=n-m+4;i<=n+3;i++) ch(a,i); for(int i=n-m+4;i<=n+2;i++) ch(b,i); add(a,b); for(int i=1;i<=n;i++) ch(c,i); for(int i=c[0];i;i--) printf("%d",c[i]); puts(""); } //排队 题解 /* g++ 1.cpp -o 1 ./1 1 1 */
B.Perm排列计数(自主研发) Pi<Pi/2的1~n排列令我大口吃屎。。。居然是下标。。。。然后搞一个二叉树,左右子树不影响,递归DP一下(题眼),bobo说学过树的都应该想到。
#include<iostream> #include<cstdio> using namespace std; int n,p; int f[2000020],s[2000020],fac[2000020],inv[2000020]; void dfs(int k) { s[k]=1; if((k<<1)<=n) dfs(k<<1); if((k<<1|1)<=n) dfs(k<<1|1); s[k]+=s[k<<1]+s[k<<1|1]; //if(s[k]==1) f[k]=1 f[k]=1ll*fac[s[k]-1]*inv[s[k<<1]]%p*inv[s[k]-s[k<<1]-1]%p; if(f[k]==0)f[k]=1; if(f[k<<1]) f[k]=1ll*f[k]*f[k<<1]%p; if(f[k<<1|1]) f[k]=1ll*f[k]*f[k<<1|1]%p; //cout<<k<<" "<<s[k]<<" "<<f[k]<<endl; } int qpow(int x,int k) { int ans=1; for(;k;k>>=1,x=1ll*x*x%p) if(k&1) ans=1ll*x*ans%p; return ans; } int main() { scanf("%d%d",&n,&p);fac[0]=1; for(int i=1;i<=n*2+1;i++) f[i]=1; for(int i=1;i<=n;i++) fac[i]=1ll*i*fac[i-1]%p; inv[n]=qpow(fac[n],p-2); for(int i=n;i>=1;i--) inv[i-1]=1ll*i*inv[i]%p; dfs(1); printf("%d\n",f[1]); } /* g++ 1.cpp -o 1 ./1 20 23 */
C.集合计数(颓了一半题解) 会做么?不会就好题。是在大小为n的集合中选任意个集合,交集元素为k的方案个数。
我首先想到了把交集为k个提出来,然后求n-k个选交集为0,即f[n-k][0],可以从2^(2^(n-k))-1个减f[n-k][j]转移过来,递推n^2实现很简单就没打,然后想颓了一眼题解(容斥原理),但是难以理解,想了一天又去找DeepinC大神,发现其实是容斥集合的定义和本题的集合不太一样,容斥的集合是包含一个元素的所有方案组成的集合。。。容斥应该作为一个随想随用的算法,不要心理上耸掉,只要好好搞一个容斥系数就可以了
#include<iostream> #include<cstdio> #include<cmath> using namespace std; const int mn=1e6+20,mod=1e9+7; int f[mn],fac[mn],inv[mn],n,k; int qpow(int x,int k) { int ans=1; for(;k;k>>=1,x=1ll*x*x%mod) if(k&1) ans=1ll*ans*x%mod; return ans; } long long C(int n,int m) {return 1ll*fac[n]*inv[m]%mod*inv[n-m]%mod;} int main() { scanf("%d%d",&n,&k);fac[0]=1; for(int i=1;i<=n;i++) fac[i]=1ll*fac[i-1]*i%mod; inv[n]=qpow(fac[n],mod-2); for(int i=n;i;i--) inv[i-1]=1ll*inv[i]*i%mod; long long ans=0,tmp=C(n,k),tp=1;n-=k; int op=(n&1)?-1:1; for(int i=n;~i;i--) { ans=(ans+op*C(n,i)*tp%mod)%mod; tp=(tp*tp%mod+2*tp%mod)%mod; op=-op; } ans=ans*tmp%mod; printf("%lld\n",(ans+mod)%mod); } //for sigma i=0->n C(n,i)(2^(2^(n−i))−1) /* g++ 1.cpp -o 1 ./1 4 1 */
D.DZY loves math 2(全颓题解) 劳资他娘的颓了题解还不会,只能等我变强之后在回过头再做。。。
E.虔诚的母猪人墓主人 (暴力水过,正解算是颓了题解10个字) 这是个数据结构题,思维量很小,就想到一个离散化不影响答案,
然后每一行有一段段的横区间,区间内左右组合数一定,要维护竖着的ΣC上*C下就行了,没sort调了3节课骂了无数的街。。
#include<iostream> #include<cstdio> #include<algorithm> #include<vector> #define ll long long using namespace std; const unsigned int mod=2147483647; struct point { int x,y; bool friend operator <(const point &a,const point &b) { if(a.x==b.x) return a.y<b.y; return a.x<b.x; } }t[100020]; unsigned int x[100020],y[100020],lx[100020],ly[100020],n,m,nx,ny,w; unsigned int b[100020],s[100020],pre[100020]; unsigned int a[100020],C[100020][15]; vector<int> r[100020]; int rd() { int s=0,w=1; char cc=getchar(); while(cc<'0'||cc>'9') {if(cc=='-') w=-1;cc=getchar();} while(cc>='0'&&cc<='9') s=(s<<3)+(s<<1)+cc-'0',cc=getchar(); return s*w; } void add(int i,int w) {for(;i<=ny;i+=(i&(-i))) a[i]+=w;} unsigned int ask(int i) { unsigned int ans=0; for(;i;i-=(i&(-i))) ans+=a[i]; return ans; } bool cmp(const int &a,const int &b) { return t[a].y<=t[b].y; } int main() { //freopen("25.in","r",stdin); n=rd(),m=rd(); w=rd(); for(int i=1;i<=w;i++) { x[i]=lx[i]=rd(); y[i]=ly[i]=rd(); } int k=rd(); for(int i=0;i<=w;i++) C[i][0]=1; for(int i=1;i<=w;i++) for(int j=1;j<=min(i,k);j++) C[i][j]=C[i-1][j]+C[i-1][j-1]; sort(lx+1,lx+w+1);sort(ly+1,ly+w+1); nx=unique(lx+1,lx+w+1)-lx-1,ny=unique(ly+1,ly+w+1)-ly-1; for(int i=1;i<=w;i++) { t[i].x=lower_bound(lx+1,lx+nx+1,x[i])-lx; t[i].y=lower_bound(ly+1,ly+ny+1,y[i])-ly; } sort(t+1,t+w+1); for(int i=1;i<=w;i++) { r[t[i].x].push_back(i); pre[i]=s[t[i].y]++; } unsigned int ans=0; for(int i=1;i<=nx;i++) { //cout<<i<<endl; int len=r[i].size(); sort(r[i].begin(),r[i].end(),cmp); if(i!=1) { for(int j=k;j<=len-k;j++) { int ai=t[r[i][j]].y,bi=t[r[i][j-1]].y; if(ai<bi) swap(ai,bi); //if(ai==bi+1) continue; ans+=(ask(ai-1)-ask(bi))*C[j][k]*C[len-j][k]; //printf("%d %d %d %d\n",ask(ai-1)-ask(bi),j,C[j][k],ans); } } for(int j=0;j<len;j++) { int pr=pre[r[i][j]],nt=s[t[r[i][j]].y]-pr-1,ai=t[r[i][j]].y; int wi=C[pr+1][k]*C[nt][k]-ask(ai)+ask(ai-1); //printf("%d %d %d %d\n",j,ask(ai)-ask(ai-1),pr,nt); //cout<<r[i][j]<<" "<<pr+1<<" "<<nt<<endl; add(ai,wi); } } ans&=mod; printf("%d\n",ans); } /* n%(1<<k)=n&((1<<k)-1) g++ 1.cpp -o 1 ./1 5 6 13 0 2 0 3 1 2 2 0 1 3 3 3 2 1 2 4 2 5 2 6 3 2 5 2 4 3 2 */
F. 地精部落 (半题解) 他们都用两维数组做得。。。这是个DP题,要找无连续3个数单调,即锯齿状的序列数,问题出在维护信息过多很难P。。然后从状压打起,逐渐减少维护信息,然后再从长度为i,最后一位为k,是峰或谷的状态定义逐渐发现转移错误,后来经tdcp点拨,外加自己思考,发现第二维会转移重复,然后想到其实谁是谁无所谓,只要关心排名是多少就行了,第二维定义为排名为k的方案。
一般的题解第一句话就是设XXX,转移XXX,然后代码。。。其实最关键的是状态的定义,定义对了转移就显然,状态的定义就要抓住题目的特殊条件,如果维护信息过多就先从状压想起,逐渐减少无用信息,一般就能想到正经状态了。
#include<iostream> #include<cstdio> #include<algorithm> using namespace std; int n,p,ans=0; int f[2][4320][2],sum[2][4320][2]; int main() { scanf("%d%d",&n,&p); f[2&1][2][1]=f[2&1][1][0]=1; sum[2&1][2][1]=sum[2&1][1][0]=1;sum[2&1][2][0]=1; for(int i=3;i<=n;i++) { for(int j=1;j<=i;j++) { f[i&1][j][0]=(sum[i&1^1][i-1][1]-sum[i&1^1][j-1][1])%p; f[i&1][j][1]=(sum[i&1^1][j-1][0])%p; sum[i&1][j][0]=(sum[i&1][j-1][0]+f[i&1][j][0])%p; sum[i&1][j][1]=(sum[i&1][j-1][1]+f[i&1][j][1])%p; //printf("%d %d %d %d\n",i,j,f[i][j][0],f[i][j][1]); } } int ans=0; for(int j=1;j<=n;j++) { ans+=f[n&1][j][0]+f[n&1][j][1]; ans%=p; } while(ans<0) ans+=p; printf("%d\n",ans); } /* g++ 1.cpp -o 1 ./1 10 101520127 */
G.看电影(打表+一节物理自习) 这令我无颜说是自主研发的。。。。看这题样例给的这么紧,先打打表。。。然后狗出了规律。。
目前还没搞懂到底是为什么,只能再等我变强之后再回过头来去看他。。。。
总之组合数学是个坑,做了几道简单题发现令人绝望。。。。
方法:打表找规律,不会就DP,难P就暴力,不会就种地。。。。