1. 程式人生 > >【BZOJ4927】第一題 雙指針+DP(容斥?)

【BZOJ4927】第一題 雙指針+DP(容斥?)

sam ans int 山東 main font 分類 ret 答案

【BZOJ4927】第一題

Description

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

Input

第一行一個整數n。 第二行包含n個整數ai,代表每根木棍的長度。 n ≤ 5000, 1 ≤ ai ≤ 10^7

Output

一行一個整數,代表方案數。

Sample Input

8
4 5 1 5 1 9 4 5

Sample Output

3

題解:這。。。這不是沈陽集訓的原題嗎?(xqz說是山東集訓的原題)

由於題目讓你拼的是正方形,那麽這個正方形的組成顯然只有兩種情況:3+1+1+1或2+2+1+1(這裏指的是正方形的四條邊),那麽我們分類討論這兩種情況。

如果是2+2+1+1,那麽我們可以枚舉最長的那2根木棍(也就是兩個1的長度)。顯然要先排序,並將長度相同的木棍合在一起。然後我們已知正方形的邊長,那麽問題就變成了如何選出4根木棍a,b,c,d使得a+b=c+d=這個邊長。這裏我采用的是雙指針法。兩個指針從兩段向中間移動,就可以順便統計出有多少對木棍符合條件。於是ans+=C(最長的木棍的條數,2)*C(符合條件的對數,2),當然,別忘了去重!

如果是3+1+1+1,我們依舊是枚舉最長的那3根木棍。然後問題就變成了如何在一堆木棍中選出3根使得長度之和為一個定值。這個顯然是O(n3

)的背包啊,而我們要的是O(n2)的復雜度,怎麽辦?

看來我們枚舉最長棍的做法不太可行,那麽我們可以換個角度,枚舉3條短棍中最長的那一條。那麽我們可以用s[i]表示在之前的木棍中,選出兩根使得長度之和為i的方案數。這樣,我們在枚舉到i的時候,先枚舉i後面的所有木棍j,判斷一下j能否成為最長的木棍,也就是判斷s[j的長度-i的長度]是否為0。如果是,則更新答案;枚舉完j後,我們再用i來更新s數組,這就變成了一個背包問題。

然而考試的時候大佬們都是用容斥來處理的3+1+1+1,感覺容斥學的不好,沒太聽懂~

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long ll;
int n,m;
ll ans,sum,cnt;
int v[5010],val[5010],num[5010],bel[5010];
int s[10000010];
int main()
{
	//freopen("yist.in","r",stdin);
	//freopen("yist.out","w",stdout);
	scanf("%d",&n);
	int i,j,l,r;
	for(i=1;i<=n;i++)	scanf("%d",&v[i]);
	sort(v+1,v+n+1);
	for(i=1;i<=n;i++)
	{
		if(v[i]>v[i-1])	val[++m]=v[i];
		num[m]++,bel[i]=m;
	}
	for(i=1;i<=m;i++)
	{
		if(num[i]>=2)
		{
			sum=cnt=0;
			for(l=1,r=i-1;l<=r;l++)
			{
				while(l<=r&&val[l]+val[r]>val[i])	r--;
				if(val[l]+val[r]!=val[i]||l>r)	continue;
				if(l==r)
				{
					if(num[l]>=4)	cnt+=(ll)num[l]*(num[l]-1)*(num[l]-2)*(num[l]-3)/2/3/4;
					cnt+=(ll)num[l]*(num[l]-1)/2*sum;
				}
				else
				{
					if(num[l]>=2&&num[r]>=2)	cnt+=(ll)num[l]*(num[l]-1)/2*num[r]*(num[r]-1)/2;
					cnt+=(ll)num[l]*num[r]*sum;
					sum+=(ll)num[l]*num[r];
				}
			}
			ans+=cnt*num[i]*(num[i]-1)/2;
		}
	}
	for(i=1;i<=n;i++)
	{
		for(j=bel[i]+1;j<=m;j++)	if(num[j]>=3)	ans+=(ll)num[j]*(num[j]-1)*(num[j]-2)/2/3*(s[val[j]-v[i]]);
		for(j=1;j<i;j++)	if(v[j]+v[i]<=v[n])	s[v[j]+v[i]]++;
	}
	printf("%lld",ans);
	return 0;
}

【BZOJ4927】第一題 雙指針+DP(容斥?)