2014ACM/ICPC亚洲区牡丹江站 浙大命题
A Average Score http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemId=5373
a班有n个人,b班有m个人,bob在a班,现在知道除了bob以外的所有人的成绩,还知道bob如果从a班转到b班,两个班的平均成绩都会提高,问bob合法的成绩区间。
解法,求一下两个班的平均成绩,bob的成绩肯定要比a班的小,比b班的大。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 #include<cstdio> 2 int main(){ 3 int t,n,m,x,sa,sb; 4 while(~scanf("%d",&t)){ 5 while(t--){ 6 scanf("%d%d",&n,&m); 7 sa=sb=0; 8 n--; 9 for(int i=0;i<n;i++){ 10 scanf("%d",&x); 11 sa+=x; 12 } 13 for(int i=0;i<m;i++){ 14 scanf("%d",&x); 15 sb+=x; 16 } 17 sb/=m; 18 sb++; 19 if(sa%n==0){ 20 sa/=n; 21 sa--; 22 } 23 else{ 24 sa/=n; 25 } 26 printf("%d %d\n",sb,sa); 27 } 28 } 29 return 0; 30 }
B Building Fire Stations http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemId=5374
输入n个点,n-1条边,构成一颗树,选树上两个点作为消防队,使得n个点中距离消防队最远的值最小。
解法,如果是选一个点,那必然是选树的直径(树上最长链,距离最远的两个点之间的路径)的中点,这样到达树上所有点最远的距离肯定最小。当选两个点,我猜想两个点都在直径上选,然后二分答案,判断合法的时候一个消防队从直径的头部加上mid,一个从直径的尾部减去mid,然后判断能否覆盖所有点,二分答案logn,判断on,总复杂度nlogn。
网上对于猜想的证明:
先给出这个问题的数学描述:
给定一棵无根树 T 。定义以 T 上一对点 (a, b) 为“复根”的树的高度 h(a, b) = max min(dist(x, a), dist(x, b)), for all x on T 。现求 H(T) = min h(a, b), for all (a, b) available 。
我们先讨论一下 H(T) 的上界。
引理 1 对任意的一棵树 T ,不妨设其直径长为 d ,有 H(T) <= [d / 2] ([x] 意为对 x 向下取整)。
证明 当 d 为偶数时,T 有且只有一个中心点 x 。取中心点为“复根”,显然 h(x, x) = d / 2 = [d / 2] 。当 d 为奇数时, T 有且只有一条中心边,不妨设其端点分别为 x 和 y 。则显然 h(x, y) = (d - 1) / 2 = [d / 2] 。证毕。
根据引理 1 ,我们可知 H(T) 的可达上界是 [d / 2] 。所以我们接下来的目标就是找是否存在比 [d / 2] 更小的方案。下面我们证明另一个结论。
引理 2 对树 T 上任一对点 (a, b) ,若 h(a, b) < [d / 2] ,则 a 到 b 的路径上一定经过 T 的中心点(边)。
证明 反证法可证。此证明不难,留给读者。
引理 3 对树 T 上任一对点 (a, b) ,若 h(a, b) < [d / 2] ,则对 T 的任一条直径 D ,不妨设距离 a 较近的点为 l ,有 dist(b, l) > [d / 2] 且 dist(a, l) < [d / 2];另一端点 r 距离 b 较近,有 dist(b, r) < [d / 2] 且 dist(a, r) > [d / 2] 。
证明 由引理 2 可以直接推出该引理。此证明不难,留给读者。
引理 4 对任意的 (a, b) ,若其中存在至少一个点不在树 T 的某一条直径上,且 h(a, b) < [d / 2] 。则存在一对点 (a', b') ,其中 a' 和 b' 都在树 T 的任一条直径上,使得 h(a', b') <= h(a, b) 。
证明 对任意满足条件的 (a, b) ,不妨设其中 b 不在 T 的一条直径 D 上。记:
x: 直径 D 上距离 b 最近的点为 x 。
T': 将 T 上 D 包含的边都从边集中去掉得到一个森林 G ,其中 x 所在的树记为 T' 。
y: 在 T' 上 x 的最远点记为 y 。
l, r: 由于 h(a, b) < [d / 2] ,故 D 的两个端点中记距离 a 较近的点为 l ,另一端点记为 r 。接下来证明 h(a, x) <= h(a, b) :
根 据以上条件,显然有:dist(x, r) < dist(b, r) < [d / 2] 。进而可得 x 到 r 的路径上不经过 T 的中心点(边)。于是可知 dist(x, r) >= dist(x, y) (否则 r 就不可能在直径上)。
1) 因为 h(a, b) >= min(dist(a, r), dist(b, r)) = dist(b, r) > dist(x, r) >= dist(x, y) ,且 y 是 T' 中 x 的最远点。所以对 T' 中的任一点 k ,都有 dist(x, k) <= dist(x, y) <= h(a, b) ,故 min(dist(a, k), dist(x, k)) <= h(a, b) 。
2) 而对于所有不在 T' 中的点 k ,都有 dist(x, k) <= dist(b, k) ,故 min(dist(a, k), dist(x, k)) <= min(dist(a, k), dist(b, k)) <= h(a, b) 。
结合 1) 和 2) 可得:对 T 中任一点 k , 都有 min(dist(a, k), dist(x, k)) <= h(a, b) 。故 h(a, x) <= h(a, b) 。
所以我们找到了一对点 (a, x) ,满足 h(a, x) <= h(a, b) 。证毕。
于是我们得到了下面这个最关键的结论:
定理 对树 T 上任一对点 (a, b) ,若 h(a, b) = H(T) ,则对 T 的任一条直径 D , a 和 b 都在 D 上。
证明 由引理 3 可知,不存在一种有一个点不在直径上的最优解。故该定理得证。
所以我们只要枚举所有两个点都在直径上的方案就可以找到一组最优解。于是我们就转化为一个一维的问题。可以二分答案,然后 O(N) 的判定可行性。总体复杂度是 O(NlogN) 。
spfa版本
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 //#define debug 2 //#define txtout 3 #include<cstdio> 4 #include<cstdlib> 5 #include<cstring> 6 #include<cmath> 7 #include<cctype> 8 #include<ctime> 9 #include<iostream> 10 #include<algorithm> 11 #include<vector> 12 #include<queue> 13 #include<stack> 14 #include<map> 15 #include<set> 16 #define mt(a,b) memset(a,b,sizeof(a)) 17 using namespace std; 18 typedef long long LL; 19 const double eps=1e-8; 20 const double pi=acos(-1.0); 21 const int inf=0x3f3f3f3f; 22 const int M=2e5+10; 23 24 class Spfa { ///最短路快速算法 O(E*k)(k~=2) 25 typedef int typec;///边权的类型 26 static const int ME=4e5+10;///边的个数 27 static const int MV=2e5+10;///点的个数 28 struct E { 29 int v,next; 30 typec w; 31 } e[ME]; 32 int n,le,head[MV],inque[MV],pre[MV],i,u,v; 33 typec dist[MV]; 34 bool used[MV]; 35 queue<int> q; 36 public: 37 void init(int tn) { ///传入点的个数 38 n=tn; 39 le=0; 40 for(i=0; i<=n; i++) head[i]=-1; 41 } 42 void add(int u,int v,typec w) { 43 e[le].v=v; 44 e[le].w=w; 45 e[le].next=head[u]; 46 head[u]=le++; 47 } 48 bool solve(int s) { ///传入起点,存在负环返回false 49 for(i=0; i<=n; i++) { 50 dist[i]=inf; 51 used[i]=true; 52 inque[i]=0; 53 pre[i]=-1; 54 } 55 used[s]=false; 56 dist[s]=0; 57 inque[s]++; 58 while(!q.empty()) q.pop(); 59 q.push(s); 60 while(!q.empty()) { 61 u=q.front(); 62 q.pop(); 63 used[u]=true; 64 for(i=head[u]; ~i; i=e[i].next) { 65 v=e[i].v; 66 if(dist[v]>dist[u]+e[i].w) { 67 dist[v]=dist[u]+e[i].w; 68 pre[v]=u; 69 if(!used[v]) continue; 70 used[v]=false; 71 q.push(v); 72 inque[v]++; 73 if(inque[v]>n) return false; 74 } 75 } 76 } 77 return true; 78 } 79 typec getdist(int id) { 80 return dist[id]; 81 } 82 int getpre(int id) { 83 return pre[id]; 84 } 85 } g; 86 int getfar(int n){ 87 int res=1; 88 for(int i=1;i<=n;i++){ 89 if(g.getdist(i)>g.getdist(res)){ 90 res=i; 91 } 92 } 93 return res; 94 } 95 vector<int> zhi; 96 int lz; 97 bool cover[M]; 98 bool judge(int n,int d,int &x,int &y){ 99 int lid=min(d,lz-2); 100 int rid=max(lid+1,lz-1-d); 101 x=zhi[lid]; 102 y=zhi[rid]; 103 g.solve(x); 104 for(int i=1;i<=n;i++){ 105 cover[i]=false; 106 } 107 bool ok=true; 108 for(int i=1;i<=n;i++){ 109 if(g.getdist(i)<=d){ 110 cover[i]=true; 111 } 112 else{ 113 ok=false; 114 } 115 } 116 if(ok) return true; 117 g.solve(y); 118 for(int i=1;i<=n;i++){ 119 if(cover[i]) continue; 120 if(g.getdist(i)>d) return false; 121 } 122 return true; 123 } 124 int main() { 125 #ifdef txtout 126 freopen("in.txt","r",stdin); 127 freopen("out.txt","w",stdout); 128 #endif 129 int t,n,u,v; 130 while(~scanf("%d",&t)) { 131 while(t--){ 132 scanf("%d",&n); 133 g.init(n); 134 for(int i=1;i<n;i++){ 135 scanf("%d%d",&u,&v); 136 g.add(u,v,1); 137 g.add(v,u,1); 138 } 139 g.solve(1); 140 int z1=getfar(n); 141 g.solve(z1); 142 int z2=getfar(n); 143 zhi.clear(); 144 int now=z2; 145 while(true){ 146 zhi.push_back(now); 147 if(now==z1) break; 148 now=g.getpre(now); 149 } 150 lz=zhi.size(); 151 int L=0,R=lz,ans=lz,x,y,ax,ay; 152 while(L<=R){ 153 int mid=(L+R)>>1; 154 if(judge(n,mid,x,y)){ 155 R=mid-1; 156 if(ans>mid){ 157 ans=mid; 158 ax=x; 159 ay=y; 160 } 161 } 162 else{ 163 L=mid+1; 164 } 165 } 166 printf("%d %d %d\n",ans,ax,ay); 167 } 168 } 169 return 0; 170 }
C Card Game http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemCode=3821
放放放,跳过。
D Domination http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemId=5376
输入n,m表示棋盘有n行m列,人每天会随机找一个空的格子放棋子,问覆盖棋盘的天数的期望。覆盖的定义是棋盘的每一行至少有一个棋子,每一列至少有一个棋子。
解法:E=x1*p1+x2*p2+...
也就是我们需要求出1天覆盖棋盘的概率p1,2天覆盖棋盘的概率p2,。。。最后1*p1+2*p2+3*p3...
如何求p1到pn。如果暴力,第一天有n*m种选法,每种的概率是1/(n*m),以此类推,级别是n!
不难发现第一天选11,第二天选22,和第一天选22,第二天选11都是两天覆盖了2*2的棋盘,也就是状态是重复的。
定义dp【n】【m】【k】表示覆盖n*m的棋盘花费k天的概率。
当前状态是dp【i】【j】【k】,则下一步可以推向几个状态
定义sum=n*m,即棋盘的总点数
一是
选的点不在未覆盖的行上,也不在未覆盖的列上,此时下一个状态就是dp【i】【j】【k+1】,因为是随机放,概率就等于满足条件的个数除以可选的总数(i*j-k)/(sum-k)
同理,选的点还有3种情况,只贡献一行,只贡献一列,贡献一行一列,分别转移。
加入dp==0 continue可以减掉许多达不到的状态优化近10倍
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 #include<cstdio> 2 const double eps=1e-13; 3 const int M=55; 4 double dp[M][M][M*M]; 5 int main(){ 6 int t,n,m; 7 while(~scanf("%d",&t)){ 8 while(t--){ 9 scanf("%d%d",&n,&m); 10 int all=n*m; 11 for(int i=0;i<=n;i++){ 12 for(int j=0;j<=m;j++){ 13 for(int k=0;k<=all;k++){ 14 dp[i][j][k]=0; 15 } 16 } 17 } 18 dp[0][0][0]=1; 19 for(int i=0;i<=n;i++){ 20 for(int j=0;j<=m;j++){ 21 for(int k=0;k<=all;k++){ 22 if(dp[i][j][k]<eps) continue; 23 if(i!=n||j!=m) 24 dp[i][j][k+1]+=dp[i][j][k]*(i*j-k)/(all-k); 25 dp[i+1][j][k+1]+=dp[i][j][k]*(n-i)*j/(all-k); 26 dp[i][j+1][k+1]+=dp[i][j][k]*i*(m-j)/(all-k); 27 dp[i+1][j+1][k+1]+=dp[i][j][k]*(n-i)*(m-j)/(all-k); 28 } 29 } 30 } 31 double ans=0; 32 for(int i=0;i<=all;i++){ 33 ans+=i*dp[n][m][i]; 34 } 35 printf("%.12f\n",ans); 36 } 37 } 38 return 0; 39 }
E Excavator Contest http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemId=5377
某大神的构造。有空来看。
http://blog.csdn.net/u014250021/article/details/40109587
F Fiber-optic Network http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemId=5378
小岛大神的树形dp,容斥。http://www.shuizilong.com/house/archives/2014-acmicpc-asia-mudanjiang-regional-contest-onsite/
http://acm.hust.edu.cn/vjudge/problem/viewSource.action?id=2845448
某不知名大神的莫比乌斯
http://blog.csdn.net/u012848726/article/details/46874335
都是需要学习的。
G Garden and Sprinklers http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemCode=3825
放放放。跳过。
H Hierarchical Notation http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemId=5380
输入一个串,是一系列键值对,然后输入q组查询,每次查询输入键,要求将对应的值打印出来。键值对由:连接。
解法:首先枚举所有的:,然后:之前的“”就是键,存在map中,map里存pair,表示该字符串的值的头和尾的下标。都处理好后,对每个查询,就在map中拿到它的头尾,然后打印就行了。我没处理a.b.c非法的,能过,说明都是合法的查询,那就直接查最后一个.之后的字符串就可以了。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 #include<cstdio> 2 #include<cstring> 3 #include<iostream> 4 #include<map> 5 using namespace std; 6 typedef pair<int,int> pii; 7 const int M=1e6+10; 8 map<string,pii> mp; 9 map<string,pii>::iterator it; 10 string str; 11 pii p; 12 char a[M],b[M]; 13 void getstr(int l,int r,char c[]){ 14 str.resize(r-l+1); 15 int ls=0; 16 for(int i=l;i<=r;i++){ 17 str[ls++]=c[i]; 18 } 19 } 20 void init(){ 21 mp.clear(); 22 int la=strlen(a); 23 for(int i=0;i<la;i++){ 24 if(a[i]!=':') continue; 25 for(int j=i-2;j>=0;j--){ 26 if(a[j]=='"'){ 27 getstr(j,i-1,a); 28 break; 29 } 30 } 31 p.first=i+1; 32 int tmp=0; 33 for(int j=i+1;j<la;j++){ 34 if(a[j]=='{'){ 35 tmp++; 36 continue; 37 } 38 if(a[j]=='}'){ 39 if(!tmp){ 40 p.second=j-1; 41 break; 42 } 43 tmp--; 44 if(tmp) continue; 45 p.second=j; 46 break; 47 } 48 if(a[j]==','&&!tmp){ 49 p.second=j-1; 50 break; 51 } 52 } 53 mp[str]=p; 54 } 55 } 56 int main(){ 57 int t,n; 58 while(~scanf("%d",&t)){ 59 while(t--){ 60 scanf("%s%d",a,&n); 61 init(); 62 while(n--){ 63 scanf("%s",b); 64 int s=0,lb=strlen(b); 65 for(int i=0;i<lb;i++){ 66 if(b[i]=='.'){ 67 s=i+1; 68 } 69 } 70 getstr(s,lb-1,b); 71 it=mp.find(str); 72 if(it==mp.end()){ 73 puts("Error!"); 74 continue; 75 } 76 p=it->second; 77 for(int i=p.first;i<=p.second;i++){ 78 putchar(a[i]); 79 } 80 puts(""); 81 } 82 } 83 } 84 return 0; 85 }
I Information Entropy http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemId=5381
按照题目的公式求和,注意p==0时是0,最后取相反数。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 #include<cstdio> 2 #include<cmath> 3 char a[32]; 4 double f(int p,char c){ 5 double res=p*0.01; 6 if(c=='b') return res*log2(res); 7 if(c=='n') return res*log(res); 8 return res*log10(res); 9 } 10 int main(){ 11 int t,n,x; 12 while(~scanf("%d",&t)){ 13 while(t--){ 14 scanf("%d%s",&n,a); 15 double sum=0; 16 while(n--){ 17 scanf("%d",&x); 18 if(!x) continue; 19 sum+=f(x,a[0]); 20 } 21 printf("%.12f\n",-sum); 22 } 23 } 24 return 0; 25 }
J Jacobi Pattern http://wzimpha-wordpress.stor.sinaapp.com/uploads/2014/11/Jacobi-Pattern.pdf
这种题属于进final的选手思考的。我就暂时放一放。
K Known Notation http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemId=5383
输入一个带数字和*的串,问最少几次操作变成合法逆波兰表达式,操作有两种,一种是插入一个数字或星,一种是交换任意两个字符。
解法:首先12*可以消掉变成2,也就是说两个数字加一个星可以变成一个数字,那么如果一共有n个星,我们至少需要n+1个数字,并且数字尽可能在前面更好,所以第一步贪心的在前面加数字,加到星的个数加1即可。第二步遍历,记录一下前面剩了多少个数字,只要遇到星,就会和前面两个数字抵消成一个数字,相当于前面剩余的数字减1,如果前面剩余的数字不够了,则贪心的选择最后一个数字和当前的星交换位置。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 #include<cstdio> 2 #include<cstring> 3 const int M=1e3+10; 4 char a[M]; 5 int main(){ 6 int t; 7 while(~scanf("%d",&t)){ 8 while(t--){ 9 scanf("%s",a); 10 int la=strlen(a),star=0,num=0; 11 for(int i=0;i<la;i++){ 12 if(a[i]=='*'){ 13 star++; 14 } 15 } 16 num=la-star; 17 int ans=0,tmp=0; 18 if(num<star+1){ 19 ans=star+1-num; 20 tmp=ans; 21 } 22 for(int i=0;i<la;i++){ 23 if(a[i]!='*'){ 24 tmp++; 25 continue; 26 } 27 if(tmp>=2){ 28 tmp--; 29 continue; 30 } 31 for(int j=la-1;j>=0;j--){ 32 if(a[j]!='*'){ 33 ans++; 34 a[j]='*'; 35 tmp++; 36 break; 37 } 38 } 39 } 40 printf("%d\n",ans); 41 } 42 } 43 return 0; 44 }
end