2018.12.08【NOIP提高组】模拟B组 JZOJ 5123 diyiti

版权声明:虽然本蒟蒻很菜,但各位dalao转载请注明出处谢谢。 https://blog.csdn.net/xuxiayang/article/details/84893925

描述

给定 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);
}

猜你喜欢

转载自blog.csdn.net/xuxiayang/article/details/84893925