1. 程式人生 > >jzoj5983. 【北大2019冬令營模擬2019.1.1】多邊形 (組合數學)

jzoj5983. 【北大2019冬令營模擬2019.1.1】多邊形 (組合數學)

這其實是道打表題……你看我程式碼就知道了……

咳咳來點嚴謹證明好了……

前方高能請注意

首先,正多邊形近似於圓,可以看做在圓裡內接多邊形。圓內接多邊形最多隻有三個銳角。因為凸多邊形的外角和為\(360\)度,如果有大於等於\(4\)個銳角,那麼有大於等於\(4\)個外角大於\(90\)度,外角和肯定大於\(360\)度,矛盾(話說我當時只猜想出了結論不知道怎麼證明……初中數學全還給老師了→_→)

那麼分情況討論\(k=0,1,2,3\)的情況就好了。順便注意\(n\)為奇數,所以不可能存在直角的情況

k=3

此時,這個多邊形肯定是三角形,所以如果\(m\neq 3\)無解。(因為我證不來所以就當做是顯然好了)

所以這裡可以容斥,用總的三角形個數減去鈍角三角形的個數

對於鈍角三角形,三個點肯定在直徑的同一側,我們可以列舉兩個點,然後第三個點肯定在這兩個點在圓上較短的那條圓弧上,且上面任意一個點與這兩個點組成的三角形都是鈍角

從下圖中不難看出,直徑一邊的點數最多為\(\left\lfloor\frac{n}{2}\right\rfloor+1\)

於是我們可以列舉每一個點然後逆時針列舉第二個點,那麼當第二個點每逆時針走一格,第三個點能選的位置也相對應增加一個。而第三個點最多能選的位置是\(\left\lfloor\frac{n}{2}\right\rfloor-1\)(減去位於兩端的第一第二個點),那麼對於這一個點它能形成的鈍角三角形就是\(\sum_{i=1}^{\left\lfloor\frac{n}{2}\right\rfloor-1}i\)

,用等差數列求和公式求和,然後再乘上\(n\)就是總的鈍角三角形個數,用總的三角形個數減去它就是銳角三角形個數了

k=2

首先,如果一個角是銳角,那麼與這個點\(i\)相鄰的兩個點,他們不經過\(i\)的那段圓弧必定小於周長的一半。其次,不難發現如果\(k=2\),那麼兩個銳角相對應的點必定在多邊形上相鄰

於是我們可以列舉銳角相對應的點,設為\(A,B\),如果下圖所示,剩下的點只有落在紫色區域才能使\(A,B\)都是銳角且不存在第三個銳角(如果\(m>3\)都行,如果\(m=3\)只能在上面那塊紫色區域)

於是照例列舉\(A\)並逆時針列舉\(B\),設\(AB\)之間有\(k\)

個點(不包括\(A,B\)),即上面那個紫色區域裡可選的點有\(k\)個,易知下面那塊紫色區域可選的點為\(k+1\)個(可選的點都是在正\(n\)邊形上的)。上面那塊紫色區域,\(k\)最大為\(\left\lfloor\frac{n}{2}\right\rfloor-1\),於是每一個\(A\)上面那塊紫色區域的貢獻就是\(\sum_{i=1}^{\left\lfloor\frac{n}{2}\right\rfloor-1}C_{i}^{m-2}=C_{\left\lfloor\frac{n}{2}\right\rfloor}^{m-1}\)。如果\(m\neq 3\)就把下面那一塊的貢獻也加上去。最後將貢獻乘上\(n\)就是每一個點的貢獻了

這裡順便說一下形如\(\sum_{i=1}^nC_{i}^m\)的東西怎麼求和。把它拆出來,前面加上\(C_{0}^m\)\(C_{0}^{m+1}\)(這兩個都等於\(0\))。於是原式為\(C_0^{m+1}+C_0^m+C_{1}^m+C_2^m+...+C_n^m=C_1^{m+1}+C_1^m+...\),然後不斷合併前兩項,得到\(C_{n+1}^{m+1}\)

k=1

這種情況很麻煩,我們要保證除了列舉的點其它都不是銳角。可以按下圖所示的方案來,列舉銳角\(A\),設\(E,F\)為剩下所有點的兩個邊界,那麼\(EF\)不經過\(A\)的那段圓弧的長必定小於圓周長的一半。我們要保證\(E,F\)所對的兩個角不是銳角,設\(C,D\)為與\(E,F\)相鄰的第一個點,只有在\(C\)\(E\)\(F\)\(D\)都在如圖直徑的同一側時,才能保證\(E,F\)都是鈍角

於是我們可以列舉\(C,D\),則\(C,D\)必在這條直徑的兩側,我們可以列舉\(C,D\)之間的點數\(k\)(不包含\(C,D\)),設\(T=\left\lfloor\frac{n}{2}\right\rfloor+1\)(即直徑的一邊最多能有的點數),則\(k\)的上界為\(T-4\),對於每一個\(k\),考慮\(C,D\)的放法,共有\(k+1\)種,對於每一種\(C,D\)的放法,因為\(E,F\)之間包含\(E,F\)的點數最多為\(T\),所以當\(E\)逆時針移動時,\(F\)能放的位置也逆時針移動,且每次增加\(1\),當\(E,C\)在正\(n\)邊形上相鄰時,\(F\)能放的位置為\(T-k-3\),於是\(E,F\)放置的方案數為\(\sum_{i=1}^{T-k-3}i\)(先別用等差數列求和公式化掉)

綜上,對於每一個作為銳角的點,可行的方案數為\[Ans=\sum_{k=0}^{T-4}C_{k}^{m-5}(k+1)\sum_{i=1}^{T-k-3}i\]
然後答案乘上個\(n\)就是總的方案數。順便從上式看出如果\(m<5\)無解

然而上式的計算是\(O(n)\)的,太慢

考慮變換求和順序,有\[Ans=\sum_{i=1}^{T-3}i\sum_{k=0}^{T-i-3}C_{k}^{m-5}(k+1)\]
考慮形如\(\sum_{i=0}^n (i+1)C_i^m\)的東西,有\[\sum_{i=0}^n (i+1)C_i^m=\sum_{i=0}^n\frac{(i+1)!}{(m+1)!(m-i)!}(m+1)\]
則這玩意兒等於\((m+1)\sum_{i=0}^nC_{i+1}^{m+1}\),再按照\(k=2\)那個時候最下面說的化一下,又等於\((m+1)C_{n+2}^{m+2}\)

於是\[\sum_{k=0}^{T-i-3}C_{k}^{m-5}(k+1)=(m-4)C_{T-i-1}^{m-3}\]

原式變為\[Ans=(m-4)\sum_{i=1}^{T-3}i\times C_{T-i-1}^{m-3}\]

\(T-i-1<m-3\)時組合數為\(0\),所以可以寫成\[Ans=(m-4)\sum_{i=1}^{T-m+2}i\times C_{T-i-1}^{m-3}\]
考慮後面那一坨東西,令\(M=m-3\),展開之後為\(C_{T-2}^M+2C_{T-3}^M+...+(T-M-1)C_M^M\)
我們可以在後面加上一堆\(C_{i}^M(i<M)\)反正都等於\(0\),於是上式可以化為\(C_{T-1}^{M+1}+C_{T-2}^{M+1}+...+C_{M+1}^{M+1}=C_{T}^{M+2}\)

綜上\(k=1\)時每一個點的答案為\(Ans=(m-4)C_T^{m-1}\)

k=0

只要用所有的情況減去銳角分別為\(1,2,3\)的情況就好了

//minamoto
#include<cstdio>
#include<cmath>
#include<cstring>
#include<iostream>
#define R register
#define fp(i,a,b) for(R int i=a,I=b+1;i<I;++i)
#define fd(i,a,b) for(R int i=a,I=b-1;i>I;--i)
#define go(u) for(int i=head[u],v=e[i].v;i;i=e[i].nx,v=e[i].v)
char buf[1<<21],*p1=buf,*p2=buf;
inline char getc(){return p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<21,stdin),p1==p2)?EOF:*p1++;}
int read(){
    R int res,f=1;R char ch;
    while((ch=getc())>'9'||ch<'0')(ch=='-')&&(f=-1);
    for(res=ch-'0';(ch=getc())>='0'&&ch<='9';res=res*10+ch-'0');
    return res*f;
}
char sr[1<<21],z[20];int K=-1,Z=0;
inline void Ot(){fwrite(sr,1,K+1,stdout),K=-1;}
void print(R int x){
    if(K>1<<20)Ot();if(x<0)sr[++K]='-',x=-x;
    while(z[++Z]=x%10+48,x/=10);
    while(sr[++K]=z[Z],--Z);sr[++K]='\n';
}
const int N=1e6,P=1000109107,inv2=500054554;
inline int add(R int x,R int y){return x+y>=P?x+y-P:x+y;}
inline int dec(R int x,R int y){return x-y<0?x-y+P:x-y;}
inline int mul(R int x,R int y){return 1ll*x*y-1ll*x*y/P*P;}
int ksm(R int x,R int y){
    R int res=1;
    for(;y;y>>=1,x=mul(x,x))if(y&1)res=mul(res,x);
    return res;
}
int fac[N+5],inv[N+5];
int n,m,k,res;
inline int calc(R int n){return 1ll*n*(n/2)%P*(n/2-1)%P*inv2%P;}
inline int C(R int n,R int m){if(m>n)return 0;return 1ll*fac[n]*inv[m]%P*inv[n-m]%P;}
void init(){
    inv[0]=fac[0]=1;fp(i,1,N)fac[i]=mul(fac[i-1],i);
    inv[N]=ksm(fac[N],P-2);fd(i,N-1,1)inv[i]=mul(inv[i+1],i+1);
}
int calc3(){
    if(m!=3)return 0;
    int res=C(n,3);
    res=dec(res,calc(n));
    return res;
}
int calc2(){
    int res=C(n/2,m-1);
    if(m!=3)res=add(res,C(n/2+1,m-1));
    res=mul(res,n);
    return res;
}
int calc1(){
    if(m<5)return 0;
    int res=0,T=n/2+1;
    res=C(T,m-1);
    res=mul(res,m-4);
    return mul(res,n);
}
int main(){
//  freopen("testdata.in","r",stdin);
    freopen("polygon.in","r",stdin);
    freopen("polygon.out","w",stdout);
    int T=read();init();
    while(T--){
        n=read(),m=read(),k=read();
        switch(k){
            case 3:res=calc3();break;
            case 2:res=calc2();break;
            case 1:res=calc1();break;
            case 0:{
                res=C(n,m);
                res=dec(res,calc1()),res=dec(res,calc2()),res=dec(res,calc3());
                break;
            }
            default:res=0;break;
        }print(res);
    }return Ot(),0;
}