【BZOJ4927】第一题 双指针+DP(容斥?)
【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
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; }
| 欢迎来原网站坐坐! >原文链接<