5.31模拟赛
第一题:yist
题目大意:
给定n根直的木棍,要从中选出6根木棍,满足:能用这6 根木棍拼出一个正方形。
注意木棍不能弯折。问方案数。
数据规模:
60% n<=1000
100% n<=5000
题解:
60分:
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#define MP make_pair
#define D(x) cout<<#x<<" = "<<x<<" "
#define E cout<<endl
using namespace std;
typedef pair<int,int> pii;
const int N = 1005;
typedef long long LL;
int n,m,a[N],b[N*N/2];
pii tp[N*N/2]; int tpcnt;
int C[N][5];
int read(int &num){
num=0; char c;
while(!isdigit(c=getchar()));
num=c-'0';
while(isdigit(c=getchar())) num=num*10+c-'0';
}
void init(int n){
C[1][0]=C[1][1]=1;
for(int i=2;i<=n;i++){
C[i][0]=1;
for(int j=1;j<=3;j++){
C[i][j]=C[i-1][j]+C[i-1][j-1];
}
}
}
struct Stack{
int num[N*N/2],cnt[N*N/2]; int top;
Stack(){ top=0; }
void push(int x,int c){
num[++top]=x; cnt[top]=c;
}
int find(int x){
int pos=lower_bound(num+1,num+1+top,x)-num;
return num[pos]==x ? cnt[pos] : 0;
}
void output(){
D(top); E;
for(int i=1;i<=top;i++) D(num[i]), D(cnt[i]), E;
}
} c,d,e;
int main(){
freopen("yist.in","r",stdin);
freopen("yist.out","w",stdout);
init(1000);
read(n); m=0;
for(int i=1;i<=n;i++){ read(a[i]); }
for(int i=1;i<=n;i++){
for(int j=i+1;j<=n;j++){
b[++m]=a[i]+a[j];
}
}
sort(a+1,a+1+n); sort(b+1,b+1+m);
for(int i=1;i<=n;i++){
int cnt=1;
while(a[i]==a[i+1]) i++,cnt++;
c.push(a[i],cnt);
}
for(int i=1;i<=m;i++){
if(b[i]>a[n]) break;
int cnt=1;
while(b[i]==b[i+1]) i++,cnt++;
d.push(b[i],cnt);
}
for(int i=1;i<=c.top;i++){
for(int j=i+1;j<=c.top;j++){
int cnt=C[c.cnt[i]][2]*C[c.cnt[j]][2];
if(cnt) tp[++tpcnt]=MP(c.num[i]+c.num[j],cnt);
}
if(c.cnt[i]>=2) tp[++tpcnt]=MP(c.num[i]*2,C[c.cnt[i]][2]);
}
sort(tp+1,tp+1+tpcnt);
for(int i=1;i<tpcnt;i++){
int cnt=tp[i].second;
while(tp[i].first==tp[i+1].first) cnt+=tp[i+1].second,i++;
e.push(tp[i].first,cnt);
}
// c.output(); d.output(); e.output();
LL ans=0;
for(int i=1;i<=c.top;i++){
if(c.cnt[i]>=2){
// D(c.num[i]);
int p1=C[c.cnt[i]][2];
int p2=e.find(c.num[i]);
ans+=p1*p2;
// D(ans); E;
}
}
// D(ans); E;
LL tp=0;
for(int i=1;i<=c.top;i++){
if(c.cnt[i]>=3){
for(int j=1;j<i;j++){
int p1=C[c.cnt[i]][3];
int p2=c.cnt[j];
int p3=d.find(c.num[i]-c.num[j])-c.find(c.num[i]-c.num[j]*2);
// D(c.num[i]); D(c.num[j]); D(p1); D(p2); D(p3); E;
tp+=p1*p2*p3;
}
}
}
ans+=tp/3;
printf("%d\n",ans);
}
第二题:ernd
题目大意:
一棵有根树,定义一个节点u的k-子树为u子树中距离u不超过k的部分。(当没有距离为k的节点时,不存在k-子树)
定义两棵树是相同的,当且仅当 考虑节点的标号时,它们的形态相同。给定一棵有根树,问最大的k,使得存在两棵相同的k-子树。
数据规模:
40% n<=2000
100% n<=10000
题解:
40分:
二分k,hash每个点k-子树的括号序,用hash比较即可。
100分:
预处理整棵树括号序的hash,构造前缀和,这样我们可以快速得到任意一段的hash。
二分k,我们已经知道了任意点u整个子树的hash,如果能把距离为k+1的那些点的hash抠掉就好了。可惜hash不支持中间抠去的操作。。。
把抠去改为合并!每个点一个vector,标记需要抠去的段的左右端点,我们只需要把没有标记的区间的hash依次取出来连起来即可。
dfs一下,当前在u,则把u及其子树对应的区间放进u的k级父亲的vector里面。当返回到u时,计算hash即可。
复杂度分析:每个点代表的区间只会被扔进一个点的vector,因此要抠去的区间个数是O(n)的,因此合并的个数也是O(n)的。整个算法复杂度O(nlogn)
Code:
//NULL