HHU ACM 综合训练2
2016-09-22 21:02
387 查看
本次综合训练主要是2013年的大连online的题目,有难有易,其中题意不太清楚和要求非常高的题目就直接剔除了。这样题数少了,于是又加了2题2016年大连online的我觉得挺不错的题目。后来发现知识点有点重叠,不过一套比赛本来就是这样。
题解:做这题首先要知道 φ(n) 的计算式(不作证明):
φ(n)=n(1−1p1)(1−1p2)...(1−1pk)
∴n/φ(n)=1(1−1p1)(1−1p2)...(1−1pk)
=1p1−1p1p2−1p2...pk−1pk
=p1p1−1p2p2−1...pkpk−1
=(1+1p1−1)(1+1p2−1)...(1+1pk−1)
其中 p1,p2,...pk 为 n 的质因子。
【考虑到大家提交的很多代码都是打表,这里我写的比较详细
明显地:
1. p1,p2,...pk 越小,n 越小,而且 n/φ(n) 越大;
2. p1,p2,...pk 的次方数没有贡献。
所以,只需要把最小的那些质数乘起来,就能得到满足条件的 n
为了增加(编程)难度,这题又加入了高精度,套用模板,代码如下:
题解:可以发现,最后的答案具有单调性,即,若跳跃能力 k 可以跳过这条河,那么跳跃能力 x(x≥k) 一定能跳过这条河。
由此,使用二分答案可以解决,代码如下:
题解:做这题首先要知道一个结论,值为 X 的数连续地跟其它数进行 GCD 运算,最多出现 ⌊log2X⌋ 个不同的数。这个结论刚听到可能很新鲜,但其实很好理解,GCD 运算之后出现了新的 X ,说明新的 X 至少少了一个质因子。而质因子最多有 ⌊log2X⌋ 个。所以 GCD 个数最多也就 NlogMAX 个,可以用 map 来保存。
其次还需要知道一个技巧,如何统计一个区间内不同的数。对于一个右端点 R ,我们保存能使得值 X 出现的最大的 L ,并在 L 这一点加1,那么每次询问的答案就是 [L,R] 的区间和。
这样把所有询问离线并排序以后,就可以从 1 开始枚举右端点,代码如下:
题解:一定要认真读题!这道题目的 k 是一开始就给定的,是不会变的!
理解了这一点这就是一道水题,代码如下:
题解:因为这一题 1≤N≤1000 ,所以只要枚举每个点作为左下角的情况就行了(为什么?)
如果 1≤N≤10000 ,那么可以用扫描线+线段树的解法。先离散化,并将所有点按照 x y 两个关键字从小到大排序,我们想象垂直于 x 轴有两条间距为 R 的直线,这两条线不断的向右移动,右边的一些点会进入,左边的一些点会退出。那要怎么统计呢?这里要用到一个奇妙的处理,把每一个点向上延伸 R 产生的这条线段加到线段树,也就是区间加1,最后只要每次统计从最低到最高的每个点的最大值就行了(为什么?)
这个做法听起来很简单,不过由于离散化等原因,很容易写错(WA3 = =),代码如下:
题解:由于树中任意一个节点的祖先都在同一条链上,所以只需要考虑如何快速的求出一条链上的”Weak Pair”。这题有很多解法,我想到的是将祖先都插入到树状数组,那么在 u 这一点,答案就加上 x≤kau 的个数。
考虑到权值范围很大,要用离散化,并且要注意 au=0 的情况,代码如下:
【还有一点就是,题目给的是明确的父子关系,但是没有给根,一定要自己找,WA3 = =
Find the maximum
题意:给出 N 求出最小的 2≤n≤N 使得 n/φ(n) 最大。题解:做这题首先要知道 φ(n) 的计算式(不作证明):
φ(n)=n(1−1p1)(1−1p2)...(1−1pk)
∴n/φ(n)=1(1−1p1)(1−1p2)...(1−1pk)
=1p1−1p1p2−1p2...pk−1pk
=p1p1−1p2p2−1...pkpk−1
=(1+1p1−1)(1+1p2−1)...(1+1pk−1)
其中 p1,p2,...pk 为 n 的质因子。
【考虑到大家提交的很多代码都是打表,这里我写的比较详细
明显地:
1. p1,p2,...pk 越小,n 越小,而且 n/φ(n) 越大;
2. p1,p2,...pk 的次方数没有贡献。
所以,只需要把最小的那些质数乘起来,就能得到满足条件的 n
为了增加(编程)难度,这题又加入了高精度,套用模板,代码如下:
#include <iostream> #include <cstdio> #include <cstring> using namespace std; const int MAX=1000000; int pr[100000],pt=0; bool f[MAX]; char s[110]; struct BigInt { const static int mod = 10000; const static int DLEN = 4; int a[100],len; BigInt() { memset(a,0,sizeof(a)); len = 1; } BigInt(int v) { memset(a,0,sizeof(a)); len = 0; do { a[len++] = v%mod; v /= mod; } while(v); } BigInt(const char s[]) { memset(a,0,sizeof(a)); int L = strlen(s); len = L/DLEN; if(L%DLEN)len++; int index = 0; for(int i = L-1; i >= 0; i -= DLEN) { int t = 0; int k = i - DLEN + 1; if(k < 0)k = 0; for(int j = k; j <= i; j++) t = t*10 + s[j] - '0'; a[index++] = t; } } BigInt operator +(const BigInt &b)const { BigInt res; res.len = max(len,b.len); for(int i = 0; i <= res.len; i++) res.a[i] = 0; for(int i = 0; i < res.len; i++) { res.a[i] += ((i < len)?a[i]:0)+((i < b.len)?b.a[i]:0); res.a[i+1] += res.a[i]/mod; res.a[i] %= mod; } if(res.a[res.len] > 0)res.len++; return res; } BigInt operator *(const BigInt &b)const { BigInt res; for(int i = 0; i < len; i++) { int up = 0; for(int j = 0; j < b.len; j++) { int temp = a[i]*b.a[j] + res.a[i+j] + up; res.a[i+j] = temp%mod; up = temp/mod; } if(up != 0) res.a[i + b.len] = up; } res.len = len + b.len; while(res.a[res.len - 1] == 0 &&res.len > 1)res.len--; return res; } bool operator >(const BigInt &b)const { if (len==b.len) { int k=len-1; while (k&&a[k]==b.a[k]) k--; if (k<0) return false; return a[k]>b.a[k]; } return len>b.len; } void output() { printf("%d",a[len-1]); for(int i = len-2; i >=0 ; i--) printf("%04d",a[i]); printf("\n"); } }; void init_pr() { for (int i=2;i<MAX;i++) { if (!f[i]) pr[++pt]=i; for (int j=1;j<=pt;j++) { if (i*pr[j]>=MAX) break; f[i*pr[j]]=1; if (i%pr[j]==0) break; } } } int main() { init_pr(); int T; scanf("%d",&T); while (T--) { scanf("%s",s); BigInt n(s); BigInt a(1),b(1); for (int i=1;i<=pt;i++) { BigInt tmp(pr[i]); b=a*tmp; if (b>n) break; a=b; } a.output(); } return 0; }
The Frog’s Games
题意:青蛙要跳过一条长为 L ,两岸之间有 n 块石头的河,最多只能跳 m 次,求青蛙至少需要的跳跃能力(跳一次的最大长度)。题解:可以发现,最后的答案具有单调性,即,若跳跃能力 k 可以跳过这条河,那么跳跃能力 x(x≥k) 一定能跳过这条河。
由此,使用二分答案可以解决,代码如下:
#include <iostream> #include <cstdio> #include <cstring> #include <algorithm> using namespace std; const int N=500010; int a ; int L,n,m; bool check(int l) { int cnt=0,pre=0; for (int i=1;i<=n+1;) { while (pre+l>=a[i]&&i<=n+1) i++; pre=a[i-1]; if (++cnt>m) return false; } return true; } int bs(int l,int r) { while (l<r) { int m=(l+r)>>1; if (check(m)) r=m; else l=m+1; } return l; } int main() { while (scanf("%d%d%d",&L,&n,&m)!=EOF) { for (int i=1;i<=n;i++) scanf("%d",&a[i]); sort(a+1,a+1+n); int l=L,r=L;a[n+1]=L; for (int i=1;i<=n+1;i++) l=min(a[i]-a[i-1],l); printf("%d\n",bs(l,r)); } return 0; }
Different GCD Subarray Query
题意:给出 N 个数,每次询问 [L,R] 的子数组(类似子串)的 GCD 有多少不同的值。题解:做这题首先要知道一个结论,值为 X 的数连续地跟其它数进行 GCD 运算,最多出现 ⌊log2X⌋ 个不同的数。这个结论刚听到可能很新鲜,但其实很好理解,GCD 运算之后出现了新的 X ,说明新的 X 至少少了一个质因子。而质因子最多有 ⌊log2X⌋ 个。所以 GCD 个数最多也就 NlogMAX 个,可以用 map 来保存。
其次还需要知道一个技巧,如何统计一个区间内不同的数。对于一个右端点 R ,我们保存能使得值 X 出现的最大的 L ,并在 L 这一点加1,那么每次询问的答案就是 [L,R] 的区间和。
这样把所有询问离线并排序以后,就可以从 1 开始枚举右端点,代码如下:
#include <iostream> #include <cstdio> #include <cstring> #include <algorithm> #include <map> using namespace std; typedef pair<int,int> PII; const int N=100010; const int MAX=1000010; map<int,int> d[2],cur; struct query { int l,r,id; operator < (const query& b) const { return r<b.r; } } q ; int a ,ans ; int c[MAX]; int gcd(int a,int b) { if (!b) return a; return gcd(b,a%b); } void ins(int x,int d) { for (;x<MAX;x+=x&(-x)) { c[x]+=d; } } int query(int x) { if (!x) return 0; int res=0; for (;x>0;x-=x&(-x)) { res+=c[x]; } return res; } int main() { int n,m; while (scanf("%d%d",&n,&m)!=EOF) { d[0].clear(); d[1].clear(); cur.clear(); memset(c,0,sizeof(c)); for (int i=1;i<=n;i++) scanf("%d",&a[i]); for (int i=1;i<=m;i++) { scanf("%d%d",&q[i].l,&q[i].r); q[i].id=i; } sort(q+1,q+1+m); int now=1,pre=0,k=1; for (int i=1;i<=n;i++) { for (map<int,int>::iterator it=d[pre].begin();it!=d[pre].end();it++) { int x=gcd(a[i],it->first); d[now][x]=max(d[now][x],it->second); } d[now][a[i]]=i; for (map<int,int>::iterator it=d[now].begin();it!=d[now].end();it++) { int x=it->first; if (cur.count(x)&&cur[x]<(it->second)) { ins(cur[x],-1); ins(it->second,1); cur[x]=it->second; } else if (!cur.count(x)) { ins(it->second,1); cur[x]=it->second; } } d[pre].clear(); swap(pre,now); for (;k<=m&&q[k].r==i;k++) { ans[q[k].id]=query(q[k].r)-query(q[k].l-1); } } for (int i=1;i<=m;i++) printf("%d\n",ans[i]); } return 0; }
The kth great number
题意:给 n 个操作,支持插入和求第 k 大。题解:一定要认真读题!这道题目的 k 是一开始就给定的,是不会变的!
理解了这一点这就是一道水题,代码如下:
#include <iostream> #include <cstdio> #include <cstring> #include <algorithm> #include <queue> using namespace std; int main() { int n,k; while (scanf("%d%d",&n,&k)!=EOF) { priority_queue< int,vector<int>,greater<int> > Q; while (n--) { char s[5]; scanf("%s",s); if (s[0]=='I') { int x; scanf("%d",&x); Q.push(x); if (Q.size()>k) Q.pop(); } else { printf("%d\n",Q.top()); } } } return 0; }
Dave
题意:在二维平面上给 N 个不重合的点,问边长为 R 的正方形最多能包含多少个点(包含边界)题解:因为这一题 1≤N≤1000 ,所以只要枚举每个点作为左下角的情况就行了(为什么?)
如果 1≤N≤10000 ,那么可以用扫描线+线段树的解法。先离散化,并将所有点按照 x y 两个关键字从小到大排序,我们想象垂直于 x 轴有两条间距为 R 的直线,这两条线不断的向右移动,右边的一些点会进入,左边的一些点会退出。那要怎么统计呢?这里要用到一个奇妙的处理,把每一个点向上延伸 R 产生的这条线段加到线段树,也就是区间加1,最后只要每次统计从最低到最高的每个点的最大值就行了(为什么?)
这个做法听起来很简单,不过由于离散化等原因,很容易写错(WA3 = =),代码如下:
#include <iostream> #include <cstdio> #include <cstring> #include <algorithm> using namespace std; #define lson l,m,rt<<1 #define rson m+1,r,rt<<1|1 const int N=1010; typedef long long ll; struct point { int x,y,ix,iy; bool operator < (const point& b) { return ix<b.ix; } } p ; int qx ,qy ; ll add[N<<2],sum[N<<2]; int n,r,cntx,cnty; void push_up(int rt) { sum[rt]=max(sum[rt<<1],sum[rt<<1|1]); } void push_down(int rt) { if (add[rt]) { add[rt<<1] += add[rt]; add[rt<<1|1] += add[rt]; sum[rt<<1] += add[rt]; sum[rt<<1|1] += add[rt]; add[rt] = 0; } } void update(int L,int R,int d,int l,int r,int rt) { if (L<=l&&r<=R) { add[rt]+=d; sum[rt]+=d; return; } push_down(rt); int m=(l+r)>>1; if (L<=m) update(L,R,d,lson); if (m<R) update(L,R,d,rson); push_up(rt); } ll query(int L,int R,int l,int r,int rt) { if (L<=l&&r<=R) { return sum[rt]; } push_down(rt); int m=(l+r)>>1; ll res=0; if (L<=m) res=max(res,query(L,R,lson)); if (m<R) res=max(res,query(L,R,rson)); return res; } int ins(int k,int d) { int i; for (i=k;i<=n&&p[i].ix==p[k].ix;i++) { int pos=upper_bound(qy+1,qy+cnty+1,p[i].y+r)-qy-1; update(p[i].iy,pos,d,1,cnty,1); } return i; } int main() { while (scanf("%d%d",&n,&r)!=EOF) { cntx=cnty=0; for (int i=1;i<=n;i++) { scanf("%d%d",&p[i].x,&p[i].y); qx[++cntx]=p[i].x; qy[++cnty]=p[i].y; } sort(qx+1,qx+cntx+1); sort(qy+1,qy+cnty+1); cntx=unique(qx+1,qx+cntx+1)-qx-1; cnty=unique(qy+1,qy+cnty+1)-qy-1; for (int i=1;i<=n;i++) { p[i].ix=lower_bound(qx+1,qx+cntx+1,p[i].x)-qx; p[i].iy=lower_bound(qy+1,qy+cnty+1,p[i].y)-qy; } sort(p+1,p+n+1); memset(sum,0,sizeof(sum)); memset(add,0,sizeof(add)); int ans=0; for (int i=1,k=1;i<=n;) { while (k<=n&&p[k].x-p[i].x<=r) { k=ins(k,1); } ans=max((ll)ans,sum[1]); i=ins(i,-1); } printf("%d\n",ans); } return 0; }
Weak Pair
题意:一棵树的每个节点有一个非负权值,如果 u 是 v 的祖先(u≠v),并且两点的权值的积 au∗av≤k ,则称它们为”Weak Pair”。求”Weak Pair”的数目。题解:由于树中任意一个节点的祖先都在同一条链上,所以只需要考虑如何快速的求出一条链上的”Weak Pair”。这题有很多解法,我想到的是将祖先都插入到树状数组,那么在 u 这一点,答案就加上 x≤kau 的个数。
考虑到权值范围很大,要用离散化,并且要注意 au=0 的情况,代码如下:
【还有一点就是,题目给的是明确的父子关系,但是没有给根,一定要自己找,WA3 = =
#include <iostream> #include <cstdio> #include <cstring> #include <algorithm> using namespace std; const int N=1e5+5; const int MAX=2*N; const long long INF=1LL<<33; long long a ,b ,A[2*N]; int c[MAX]; struct edge { int go,next; } eg[2*N]; int last ; bool vis ; int tot; long long ans; void adde(int x,int y) { eg[++tot].go=y; eg[tot].next=last[x]; last[x]=tot; } void ins(int x,int d) { for (;x<MAX;x+=x&(-x)) { c[x]+=d; } } long long query(int x) { long long res=0; for (;x;x-=x&(-x)) { res+=c[x]; } return res; } void dfs(int x) { ans+=query(b[x]); ins(a[x],1); // printf("%d %d\n",x,ans); for (int i=last[x];i;i=eg[i].next) { dfs(eg[i].go); } ins(a[x],-1); } int main() { int T,n; long long k; scanf("%d",&T); while (T--) { tot=0; memset(last,0,sizeof(last)); scanf("%d%lld",&n,&k); for (int i=1;i<=n;i++) { scanf("%lld",&a[i]); A[i]=a[i]; if (a[i]==0) { A[n+i]=b[i]=INF; } else { A[n+i]=b[i]=k/a[i]; } } sort(A+1,A+1+n*2); for (int i=1;i<=n;i++) { a[i]=lower_bound(A+1,A+1+2*n,a[i])-A; b[i]=lower_bound(A+1,A+1+2*n,b[i])-A; } memset(vis,0,sizeof(vis)); for (int i=1;i<n;i++) { int x,y; scanf("%d%d",&x,&y); adde(x,y); vis[y]=1; } int i=1; while (i<=n&&vis[i]) i++; ans=0; dfs(i); printf("%lld\n",ans); } return 0; }
相关文章推荐
- 【数据结构】线段树总结(一)
- 线段树题集
- 2011ACM福州网络预选赛B题 HDU4062 Abalone
- 线段树
- HDU5446(2015年长春网络赛1010)改
- 【日常练习 线段树】HDU1754 I hate it
- hdu1754
- HDU1394
- 敌兵布阵 (1)
- I Hate It (1)
- LCIS (2)
- A Simple Problem with Integers (2)
- Mayor's posters (3)
- Buy Tickets (3)
- 线段树
- UVA - 12532 Interval Product
- bzoj 4499
- POJ 3264 Balanced Lineup
- hdu 1542 求矩形并的面积
- 关于数据结构之线段树