1. 程式人生 > >2018.12.08【NOIP提高組】模擬B組 JZOJ 5123 diyiti

2018.12.08【NOIP提高組】模擬B組 JZOJ 5123 diyiti

描述

給定 n n 根長度為 a i a_i 的直的木棍,要從中選出6根木棍,滿足:能用這6 根木棍拼出一個正方形。注意木棍不能彎折。問方案數。
正方形:四條邊都相等、四個角都是直角的四邊形。

資料範圍:
n 5000 , a i 1 0

7 n\leq 5000,a_i\leq 10^7


思路

我們將六根木棍分成4份,那麼顯然只能是3,1,1,1或2,2,1,1,也就是3條相同的邊(以下簡稱三同邊)和兩條相同的邊(以下簡稱二同邊)

對於有三同邊的情況,我們可以列舉這個三同邊,再列舉剩餘當中的其中1條邊,接著通過陣列處理出另外兩條邊之和是否存在即可

對於有二同邊的情況,有以下狀況

  1. 當四邊相同時
  2. 剩餘兩邊相同

對於第二種情況,首先列舉二同邊,再列舉另外的邊,用一個 c n t cnt 儲存 s [ j ] s[j] 的個數 × \times s [ a [ i ] a [ j ] ] s[a[i]-a[j]] 的個數(即為剩餘兩邊的方案數)

對於另外的特殊情況,需要更改去重的過程

去重咋去?

首先根據上面說的分為三種情況

  1. 兩邊重複的,方案數 C x 2 C_x^2
  2. 三邊重複的,方案數 C x 3 C_x^3
  3. 四邊重複的,方案數 C x 4 C_x^4

根據組合數的性質,這些我們可以 O ( n ) O(n) 處理,判斷的時候 O ( 1 ) O(1) 除掉就行


程式碼

#include<cstdio>
#include<cctype>
#include<algorithm>
#define file(x) freopen(#x".in","r",stdin);freopen(#x".out","w",stdout)
#define r(i,a,b) for(register int i=a;i<=b;i++)
using namespace std;typedef long long LL;
LL num[10000005],sum[10000005],f[5005],g[5005],h[5005],s[5005],ans,cnt;
int n;
inline char Getchar()
{
    static char buf[100000],*p1=buf+100000,*pend=buf+100000;
    if(p1==pend)
	{
        p1=buf; pend=buf+fread(buf,1,100000,stdin);
        if (pend==p1) return -1;
    }
    return *p1++;
}
inline LL read()
{
	char c;int d=1;LL f=0;
	while(c=Getchar(),!isdigit(c))if(c==45)d=-1;f=(f<<3)+(f<<1)+c-48;
	while(c=Getchar(),isdigit(c)) f=(f<<3)+(f<<1)+c-48;
	return d*f;
}
inline void write(LL x)
{
	if(x<0)write(45),x=-x;
	if(x>9)write(x/10);
	putchar(x%10+48);
	return;
}
signed main()
{
	file(yist);
	n=read();
	r(i,1,n) s[i]=read(),num[s[i]]++;//輸入+預處理
	sort(s+1,s+1+n);//排序
	f[2]=g[3]=h[4]=1;
    for(register int i=2;i<n;i++)//預處理
	{
	if(i>1) f[i+1]=f[i]*(i+1)/(i-1);
    	if(i>2) g[i+1]=g[i]*(i+1)/(i-2);
    	if(i>3) h[i+1]=h[i]*(i+1)/(i-3);
    }
    for(register int i=1;i<n;i++)//剩下三條邊中的任意一條邊
    {
    	for(register int j=i+1;j<=n;j++)
    	if(num[s[j]]>=3)//列舉三同邊
    	{
    		ans+=sum[s[j]-s[i]]*g[num[s[j]]];//加上方案數,由於三同邊本身可能會重複算,要乘g
    		while(s[j+1]==s[j]) j++;//去重
		}
		for(register int j=1;j<i;j++)
		if(s[i]+s[j]<=1e7) sum[s[i]+s[j]]++;//剩下三條變中的剩下兩條邊
	}
	n=unique(s+1,s+1+n)-s-1;//去重
	for(int i=1;i<=n;i++)
	if(num[s[i]]>=2)//列舉二同邊
	{
		cnt=0;
		for(int j=i-1;j>0;j--)
		{
			if(s[j]+s[j]<s[i]) break;//長度不夠,組成不了,這裡break是配合上面的排序以便減少迴圈
			if(s[j]+s[j]==s[i]){ans+=(h[num[s[j]]]+f[num[s[j]]]*cnt)*f[num[s[i]]];break;}//出現四邊相等
			ans+=(f[num[s[j]]]*f[num[s[i]-s[j]]]+num[s[j]]*num[s[i]-s[j]]*cnt)*f[num[s[i]]];//正常情況
            cnt+=num[s[j]]*num[s[i]-s[j]];
		}
	}
	write(ans);
}