2016 Multi-University Training Contest 1
官方题解:http://bestcoder.hdu.edu.cn/blog/2016-multi-university-training-contest-1-solutions-by-hit/
题目链接:
A http://acm.hdu.edu.cn/showproblem.php?pid=5723
第一步求最小生成树,因为题目限制边权各不相同,所以最小生成树唯一。第二步求任意树上任意两点距离的均值,通过计算每条边对总和的贡献得到。枚举每条边,经过次数等于该边左边点的个数乘右边点的个数。dfs求有根树每个点子树节点个数的办法。
1 #include<bits/stdc++.h> 2 using namespace std; 3 const int M=1e6+10; 4 struct E { 5 int u,v; 6 double w; 7 } e[M]; 8 int n,m; 9 double cost; 10 double average; 11 struct G{ 12 struct E{ 13 int v,next; 14 double w; 15 }e[M]; 16 int le,head[M]; 17 void init(int n){ 18 le=0; 19 for(int i=0;i<=n;i++) head[i]=-1; 20 } 21 void add(int u,int v,double w){ 22 e[le].v=v; 23 e[le].w=w; 24 e[le].next=head[u]; 25 head[u]=le++; 26 } 27 }g; 28 class Kruskal { ///最小生成树(无向图) O(E*log(E)) 29 typedef double typec; ///边权的类型 30 static const int ME=1e6+10; ///边的个数 31 static const int MV=1e5+10; ///点的个数 32 class UnionFindSet { ///并查集 33 int par[MV]; 34 void add(int son,int fa) { 35 par[fa]+=par[son]; 36 par[son]=fa; 37 } 38 public: 39 void init(int n) { 40 for(int i=0; i<=n; i++) par[i]=-1; 41 } 42 int getroot(int x) { 43 int i=x,j=x,temp; 44 while(par[i]>=0) i=par[i]; 45 while(j!=i) { 46 temp=par[j]; 47 par[j]=i; 48 j=temp; 49 } 50 return i; 51 } 52 bool unite(int x,int y) { 53 int p=getroot(x); 54 int q=getroot(y); 55 if(p==q) return false; 56 if(par[p]>par[q]) { 57 add(p,q); 58 } 59 else { 60 add(q,p); 61 } 62 return true; 63 } 64 } ufs; 65 struct E { 66 int u,v; 67 typec w; 68 friend bool operator < (const E &a,const E &b) { 69 return a.w<b.w; 70 } 71 } e[ME]; 72 int le,num,n; 73 typec res; 74 public: 75 void init(int tn) { ///传入点的个数 76 n=tn; 77 le=0; 78 } 79 void add(int u,int v,typec w) { 80 e[le].u=u; 81 e[le].v=v; 82 e[le].w=w; 83 le++; 84 } 85 typec solve() { ///返回-1 不连通 86 res=0; 87 num=1; 88 ufs.init(n); 89 sort(e,e+le); 90 for(int i=0; i<le&&num<n; i++) { 91 if(ufs.unite(e[i].u,e[i].v)) { 92 num++; 93 res+=e[i].w; 94 g.add(e[i].u,e[i].v,e[i].w); 95 g.add(e[i].v,e[i].u,e[i].w); 96 } 97 } 98 if(num<n) res=-1; 99 return res; 100 } 101 } mst; 102 int dfs(int u,int fa){ 103 int sum=1; 104 for(int i=g.head[u];~i;i=g.e[i].next){ 105 int v=g.e[i].v; 106 if(v==fa) continue; 107 double w=g.e[i].w; 108 int son=dfs(v,u); 109 sum+=son; 110 average+=w*son*(n-son); 111 } 112 return sum; 113 } 114 void solve() { 115 g.init(n); 116 mst.init(n); 117 for(int i=0;i<m;i++){ 118 mst.add(e[i].u,e[i].v,e[i].w); 119 } 120 cost=mst.solve(); 121 average=0; 122 dfs(1,-1); 123 average*=2; 124 average/=n; 125 average/=(n-1); 126 } 127 int main() { 128 int t; 129 while(~scanf("%d",&t)) { 130 while(t--) { 131 scanf("%d%d",&n,&m); 132 for(int i=0; i<m; i++) { 133 scanf("%d%d%lf",&e[i].u,&e[i].v,&e[i].w); 134 } 135 solve(); 136 printf("%.0f %.2f\n",cost,average); 137 } 138 } 139 return 0; 140 }
B http://acm.hdu.edu.cn/showproblem.php?pid=5724
博弈,nim,
标程是递推的,会比我快一点,我写的是记忆化搜素,更符合正向思维。sg函数=所有后继状态sg值中,最小的不出现的非负整数。定理。多个游戏组合的和,等于多个游戏sg值的异或。
1 #include<bits/stdc++.h> 2 #define mt(a,b) memset(a,b,sizeof(a)) 3 using namespace std; 4 const int M=1e3+10; 5 int n,m[M]; 6 int a[M][M]; 7 char answer[2][8]={"NO","YES"}; 8 int sg[1<<20]; 9 void init(){ 10 mt(sg,-1); 11 } 12 int dfs(int sta){ 13 int &g=sg[sta]; 14 if(~g) return g; 15 bool s[20]; 16 mt(s,0); 17 int zero=-1; 18 for(int i=0;i<20;i++){ 19 if(!((sta>>i)&1)){ 20 zero=i; 21 continue; 22 } 23 if(zero==-1) continue; 24 s[dfs(sta^(1<<i)^(1<<zero))]=true; 25 } 26 for(int i=0;i<32;i++){ 27 if(s[i]) continue; 28 g=i; 29 break; 30 } 31 return g; 32 } 33 int solve(){ 34 int nim=0; 35 for(int i=0;i<n;i++){ 36 int sta=0; 37 for(int j=0;j<m[i];j++){ 38 sta|=(1<<(20-a[i][j])); 39 } 40 nim^=dfs(sta); 41 } 42 return nim!=0; 43 } 44 int main(){ 45 init(); 46 int t; 47 while(~scanf("%d",&t)){ 48 while(t--){ 49 scanf("%d",&n); 50 for(int i=0;i<n;i++){ 51 scanf("%d",&m[i]); 52 for(int j=0;j<m[i];j++){ 53 scanf("%d",&a[i][j]); 54 } 55 } 56 puts(answer[solve()]); 57 } 58 } 59 return 0; 60 }
递推预处理写法
1 #include<bits/stdc++.h> 2 #define mt(a,b) memset(a,b,sizeof(a)) 3 using namespace std; 4 const int M=1e3+10; 5 const int total=1<<20; 6 int n,m[M]; 7 int a[M][M]; 8 char answer[2][8]={"NO","YES"}; 9 int sg[total]; 10 bool had[20]; 11 void init(){ 12 for(int i=0;i<total;i++){ 13 mt(had,0); 14 int zero=-1; 15 for(int j=0;j<20;j++){ 16 if(!((i>>j)&1)){ 17 zero=j; 18 continue; 19 } 20 if(zero==-1) continue; 21 had[sg[i^(1<<j)^(1<<zero)]]=true; 22 } 23 for(int j=0;;j++){ 24 if(had[j]) continue; 25 sg[i]=j; 26 break; 27 } 28 } 29 } 30 int solve(){ 31 int nim=0; 32 for(int i=0;i<n;i++){ 33 int sta=0; 34 for(int j=0;j<m[i];j++){ 35 sta|=(1<<(20-a[i][j])); 36 } 37 nim^=sg[sta]; 38 } 39 return nim!=0; 40 } 41 int main(){ 42 init(); 43 int t; 44 while(~scanf("%d",&t)){ 45 while(t--){ 46 scanf("%d",&n); 47 for(int i=0;i<n;i++){ 48 scanf("%d",&m[i]); 49 for(int j=0;j<m[i];j++){ 50 scanf("%d",&a[i][j]); 51 } 52 } 53 puts(answer[solve()]); 54 } 55 } 56 return 0; 57 }
D http://acm.hdu.edu.cn/showproblem.php?pid=5726
第一问求一个区间的gcd,第二问求等于这个值的有多少个区间。比赛时的做法,第一问通过rmq预处理,复杂度n*logn*gcd,查询gcd。 第二问枚举起点,对每个起点,gcd分段递减,对每一段二分出段的尾部,然后这段的gcd值都相同,用map记录,每个起点最多logai段。复杂度n*logn*loga*gcd。查询log。
1 #include<bits/stdc++.h> 2 using namespace std; 3 typedef long long LL; 4 const int M=1e5+10; 5 int n,m; 6 int a[M]; 7 struct Q { 8 int x,y,gcd; 9 LL sum; 10 } q[M]; 11 int gcd(int a,int b){ 12 return b?gcd(b,a%b):a; 13 } 14 class RMQ { ///区间最值查询(ST)离线算法 init O(n*logn) query O(1) 15 typedef int typec; ///点权类型 16 static const int M=1e5+10; ///点的个数 17 int LOG[M]; 18 typec dpmax[M][20]; 19 public: 20 RMQ() { 21 LOG[0]=-1; 22 for(int i=1; i<M; i++) { 23 LOG[i]=LOG[i>>1]+1; 24 } 25 } 26 void init(int n,typec a[]) { ///传入点的个数,下标 1 开始 27 for(int i=1; i<=n; i++) { 28 dpmax[i][0]=a[i]; 29 } 30 for(int j=1; j<=LOG[n]; j++) { 31 for(int i=1; i+(1<<j)-1<=n; i++) { 32 int k=i+(1<<(j-1)); 33 dpmax[i][j]=gcd(dpmax[i][j-1],dpmax[k][j-1]); 34 } 35 } 36 } 37 typec get(int a,int b) { ///传入 1 返回 max,传入 0 返回 min 38 int k=LOG[b-a+1]; 39 b=b-(1<<k)+1; 40 return gcd(dpmax[a][k],dpmax[b][k]); 41 } 42 } rmq; 43 map<int,LL> mp; 44 int binary(int s,int head,int g){ 45 int L=head,R=n,result=L; 46 while(L<=R){ 47 int mid=(L+R)>>1; 48 if(rmq.get(s,mid)==g){ 49 result=mid; 50 L=mid+1; 51 } 52 else{ 53 R=mid-1; 54 } 55 } 56 return result; 57 } 58 void init() { 59 mp.clear(); 60 for(int i=1;i<=n;i++){ 61 int head=i; 62 while(head<=n){ 63 int g=rmq.get(i,head); 64 int tail=binary(i,head,g); 65 mp[g]+=tail-head+1; 66 head=tail+1; 67 } 68 } 69 } 70 void solve() { 71 rmq.init(n,a); 72 init(); 73 for(int i=0;i<m;i++){ 74 q[i].gcd=rmq.get(q[i].x,q[i].y); 75 q[i].sum=mp[q[i].gcd]; 76 } 77 } 78 int main() { 79 int t; 80 while(~scanf("%d",&t)) { 81 int cas=1; 82 while(t--) { 83 scanf("%d",&n); 84 for(int i=1; i<=n; i++) { 85 scanf("%d",&a[i]); 86 } 87 scanf("%d",&m); 88 for(int i=0; i<m; i++) { 89 scanf("%d%d",&q[i].x,&q[i].y); 90 } 91 solve(); 92 printf("Case #%d:\n",cas++); 93 for(int i=0; i<m; i++) { 94 printf("%d %I64d\n",q[i].gcd,q[i].sum); 95 } 96 } 97 } 98 return 0; 99 }
D 标程的解法会少一个logn,因为找不同的gcd段时不二分,而是把每一个点作为终点的情况存下,存某个终点各个段的gcd值和起点。当处理 x 为终点的时, x-1的段的结果可以利用上,类似单调队列。也就是说,增加了a【x】,前面的段断点都可能是新的断点,实现起来不太好理解。
1 #include<bits/stdc++.h> 2 using namespace std; 3 typedef long long LL; 4 const int M=1e5+10; 5 int n,m; 6 int a[M]; 7 struct Q { 8 int x,y,gcd; 9 LL sum; 10 } q[M]; 11 int gcd(int a,int b){ 12 return b?gcd(b,a%b):a; 13 } 14 map<int,LL> mp; 15 typedef pair<int,int> pii; 16 vector<pii> block[M]; 17 void solve() { 18 mp.clear(); 19 for(int i=0;i<=n;i++){ 20 block[i].clear(); 21 } 22 for(int i=1;i<=n;i++){ 23 int last=0; 24 for(int j=0;j<block[i-1].size();j++){ 25 int head=block[i-1][j].first; 26 int g=block[i-1][j].second; 27 int nowgcd=gcd(g,a[i]); 28 if(nowgcd==last) continue; 29 last=nowgcd; 30 block[i].push_back(make_pair(head,nowgcd)); 31 } 32 if(a[i]!=last){ 33 block[i].push_back(make_pair(i,a[i])); 34 } 35 for(int j=0;j<block[i].size();j++){ 36 int head=block[i][j].first; 37 int g=block[i][j].second; 38 int tail=i; 39 if(j+1<block[i].size()){ 40 tail=block[i][j+1].first-1; 41 } 42 mp[g]+=tail-head+1; 43 } 44 } 45 for(int i=0;i<m;i++){ 46 int L=0,R=block[q[i].y].size()-1,result=L; 47 while(L<=R){ 48 int mid=(L+R)>>1; 49 if(block[q[i].y][mid].first<=q[i].x){ 50 result=mid; 51 L=mid+1; 52 } 53 else{ 54 R=mid-1; 55 } 56 } 57 q[i].gcd=block[q[i].y][result].second; 58 q[i].sum=mp[q[i].gcd]; 59 } 60 } 61 int main() { 62 int t; 63 while(~scanf("%d",&t)) { 64 int cas=1; 65 while(t--) { 66 scanf("%d",&n); 67 for(int i=1; i<=n; i++) { 68 scanf("%d",&a[i]); 69 } 70 scanf("%d",&m); 71 for(int i=0; i<m; i++) { 72 scanf("%d%d",&q[i].x,&q[i].y); 73 } 74 solve(); 75 printf("Case #%d:\n",cas++); 76 for(int i=0; i<m; i++) { 77 printf("%d %I64d\n",q[i].gcd,q[i].sum); 78 } 79 } 80 } 81 return 0; 82 }
end