@TryMyEdge
2017-04-22T13:23:45.000000Z
字数 6860
阅读 998
题解
A Binary Simulation
题目大意:
有一个长度为n(1<=n<=100000)的二进制数,每一位的数字为0或者1,位编号从1到n。有q(1<=q<=50000)个操作,操作分两种:(1)给定i和j,从第i位到第j位数字取逻辑反(1变0,0变1),(2)给定i,询问第i位上的数字当前状态是0还是1。
T(T<=10)组数据。
解题思路:
暴力模拟的最坏情况是每次都是修改所有的数的状态,那么复杂度是o(T*n*q),boom!
实际上我们会发现,只要知道这个位子上的数的初始状态和被修改了多少次,就能算出来这个数的当前状态是什么。于是问题就转化成了在有区间加法的情况下求某个点的值,可以用线段树也可以用树状数组,这里给出用线段树在区间加法下维护每个点值的解法。
ps:输入长度n的二进制数要用字符串。初始建树的时候稍作修改,查询的时候就不需要再去看这个位置的初始状态是什么了,可以观察下我的build函数。
时间复杂度o(T*q*log(n)),空间复杂度o(n)。
AC代码:
#include<iostream>#include<cstdio>#include<cstring>using namespace std;struct Node{int l,r,mid,val;}tre[400005];char s[100005],c[6];void build(int x,int l,int r){tre[x].l=l;tre[x].r=r;tre[x].mid=(l+r)>>1;if(tre[x].l!=tre[x].r){tre[x].val=0;build(x*2,l,tre[x].mid);build(x*2+1,tre[x].mid+1,r);}elsetre[x].val=(s[l]=='1');}void add(int x,int l,int r){if(tre[x].l==l && tre[x].r==r)tre[x].val++;else{if(l<=tre[x].mid && r>tre[x].mid){add(x*2,l,tre[x].mid);add(x*2+1,tre[x].mid+1,r);}else{if(r<=tre[x].mid)add(x*2,l,r);elseadd(x*2+1,l,r);}}}int ask(int x,int now){if(tre[x].l==tre[x].r)return tre[x].val;else{if(now<=tre[x].mid)return tre[x].val+ask(x*2,now);elsereturn tre[x].val+ask(x*2+1,now);}}int main(){int T,n,m,a,b;scanf("%d",&T);for(int t=1;t<=T;t++){scanf("%s",s+1);build(1,1,strlen(s+1));scanf("%d",&m);printf("Case %d:\n",t);while(m--){scanf("%s",c);if(c[0]=='I'){scanf("%d%d",&a,&b);add(1,a,b);}else{scanf("%d",&a);printf("%d\n",ask(1,a)%2);}}}return 0;}
B Points in Segments
题目大意:
给出n(1<=n<=100000)个点的坐标,坐标范围是[0,10^8],然后有q(1<=q<=50000)个询问,对于每个询问输出询问的区间内(包括区间左右端点)出现了多少个点。
T(T<=5)组数据。
解题思路:
用线段树和树状数组很容易解决的一道单点更新、区间求和的题目。问题在于坐标的范围太大,不仅10^8*log(10^8)的时间复杂度会超,甚至10^8的空间复杂度也是超的。
我们发现,一个坐标如果既不出现在询问中也不是某个点的坐标,那么这个坐标存不存在实际上没区别。n个点加上每个询问两个端点,最多会出现n+q*2<=200000个坐标(其中还可能有重复的)。我们把那些没用的点删去,只留下这些有用的点,就可以把点的坐标范围缩小到200000。
我们用到的方法就是离散化。在这道题里面,实际上就是建立一个双射,把每个坐标值和它是第几个有用的坐标值联系起来。
ps:记得去重。
时间复杂度o(T*q*log(n+q*2)),空间复杂度o(n+q*2)。
AC代码:
#include<iostream>#include<cstdio>#include<cstring>#include<algorithm>using namespace std;struct Node{int l,r,mid,val;}tre[800005];int addnum[100005];int ql[50005],qr[50005];int nums[200005],nownums;int num[200005],nownum;void build(int x,int l,int r){tre[x].l=l;tre[x].r=r;tre[x].mid=(l+r)>>1;tre[x].val=0;if(tre[x].l!=tre[x].r){build(x*2,l,tre[x].mid);build(x*2+1,tre[x].mid+1,r);}}void add(int x,int y){tre[x].val++;if(tre[x].l!=tre[x].r){if(y<=num[tre[x].mid])add(x*2,y);elseadd(x*2+1,y);}}int ask(int x,int l,int r){if(num[tre[x].l]==l && num[tre[x].r]==r)return tre[x].val;else{if(l<=num[tre[x].mid] && r>num[tre[x].mid])return ask(x*2,l,num[tre[x].mid])+ask(x*2+1,num[tre[x].mid+1],r);else{if(r<=num[tre[x].mid])return ask(x*2,l,r);elsereturn ask(x*2+1,l,r);}}}int main(){int T,n,m;scanf("%d",&T);for(int t=1;t<=T;t++){nownums=0;scanf("%d%d",&n,&m);for(int i=0;i<n;i++){scanf("%d",addnum+i);nums[nownums]=addnum[i];nownums++;}for(int i=0;i<m;i++){scanf("%d%d",ql+i,qr+i);nums[nownums]=ql[i];nownums++;nums[nownums]=qr[i];nownums++;}sort(nums,nums+nownums);nownum=1;num[1]=nums[0];for(int i=1;i<nownums;i++){if(nums[i]!=nums[i-1]){nownum++;num[nownum]=nums[i];}}build(1,1,nownum);for(int i=0;i<n;i++)add(1,addnum[i]);printf("Case %d:\n",t);for(int i=0;i<m;i++)printf("%d\n",ask(1,ql[i],qr[i]));}return 0;}
C Calm Down
题目大意:
看图猜题意.jpg
把n(2<=n<=100)个一模一样的小圆(?)按照图里的方式放在半径为R(0<R<1000)的大圆里,问小圆的半径应该是多少。
T(T<=125)组数据。
解题思路:
二分什么的都是异端,这题推个公式就好了。
sin(2π/2n)=r/(R-r),解出来r=R*sin(2π/2n)/(1+sin(2π/2n))。
时间复杂度o(T),空间复杂度o(1)。
AC代码:
#include<iostream>#include<cstdio>#include<cstring>#include<algorithm>#include<cmath>using namespace std;#define Pi acos(-1)int main(){int T,n;double R,r;scanf("%d",&T);for(int t=1;t<=T;t++){scanf("%lf%d",&R,&n);r=R*sin(Pi/n)/(1+sin(Pi/n));printf("Case %d: %.8f\n",t,r);}return 0;}
D Neighbor House
题目大意:
有n(1<=n<=20)间房子排成一排,第一间房子和最后一间房子不相邻。每间房子刷成红、绿、蓝三种颜色有各自的代价。相邻两间房子不能刷成一个颜色,问最少要花多少钱。
T(T<=100)组数据。
解题思路:
因为相邻两间房子颜色不能一样,所以我们刷某一间房子的时候,只需要考虑他前面一间房子是什么颜色的,更之前的房子都不影响当前这一间的颜色。所以我们可以很容易采用dp的解法来做这道题。
dp[i][j]表示到第i个房子为止全部合法粉刷,并且第i间房子的颜色为j的最小代价。num[i][j]表示把第i间房子刷成颜色j的代价。
那么状态转移方程为:
dp[i][0]=min(dp[i-1][1],dp[i-1][2])+num[i][0]
dp[i][1]=min(dp[i-1][0],dp[i-1][2])+num[i][1]
dp[i][2]=min(dp[i-1][1],dp[i-1][0])+num[i][2]
ps:我的代码里没出现num数组~这道题其实n完全可以大点= =。
时间复杂度o(T*n),空间复杂度o(n)。
AC代码:
#include<iostream>#include<cstdio>#include<cstring>#include<algorithm>#include<cmath>using namespace std;#define Pi acos(-1)int dp[25][3];int main(){int T,n;int r,g,b;scanf("%d",&T);for(int t=1;t<=T;t++){scanf("%d",&n);for(int i=1;i<=n;i++){scanf("%d%d%d",&r,&g,&b);dp[i][0]=min(dp[i-1][1],dp[i-1][2])+r;dp[i][1]=min(dp[i-1][0],dp[i-1][2])+g;dp[i][2]=min(dp[i-1][1],dp[i-1][0])+b;}printf("Case %d: %d\n",t,min(dp[n][0],min(dp[n][1],dp[n][2])));}return 0;}
E Array Queries
题目大意:
n(1<=n<=100000)个数,每个数的范围是[0,100000]。q(1<=q<=50000)个询问,询问某个区间内最小值是多少。
T(T<=5)组数据。
解题思路:
裸的求区间最值,这很线段树.jpg
时间复杂度o(T*q*log(n)),空间复杂度o(n)。
AC代码:
#include<iostream>#include<cstdio>#include<cstring>#include<algorithm>using namespace std;struct Node{int l,r,mid,val;}tre[400005];int num[100005];int build(int x,int l,int r){tre[x].l=l;tre[x].r=r;tre[x].mid=(l+r)>>1;if(tre[x].l!=tre[x].r)return tre[x].val=min(build(x*2,l,tre[x].mid),build(x*2+1,tre[x].mid+1,r));elsereturn tre[x].val=num[tre[x].mid];}int ask(int x,int l,int r){if(tre[x].l==l && tre[x].r==r)return tre[x].val;else{if(l<=tre[x].mid && r>tre[x].mid)return min(ask(x*2,l,tre[x].mid),ask(x*2+1,tre[x].mid+1,r));else{if(r<=tre[x].mid)return ask(x*2,l,r);elsereturn ask(x*2+1,l,r);}}}int main(){int T,n,m,a,b;scanf("%d",&T);for(int t=1;t<=T;t++){scanf("%d%d",&n,&m);for(int i=1;i<=n;i++)scanf("%d",num+i);build(1,1,n);printf("Case %d:\n",t);while(m--){scanf("%d%d",&a,&b);printf("%d\n",ask(1,a,b));}}return 0;}
F Farthest Nodes in a Tree
题目大意:
给一个有n(2<=n<=30000)个点的无根树,问树上最远两点的距离。
T(T<=10)组数据。
解题思路:
有一个神奇的结论:从树上任意一点A出发,到达离A最远的点(可能有多个)中的一个记为B,那么从B出发,到达离B最远的点(可能有多个)中的一个记为C,那么B到C的距离就是树上最远两点的距离。
很神奇吧~以下是证明:
我们定义|P1P2|为点A到点B的距离,因为这是棵树,所以任意两点是连通的,并且距离是唯一的。
树的一个性质:P1、P2、P3为树上任意三点,那么|P1P2|<=|P1P3|+|P3P2|。至于原理,自己去证~
假设树上最远的距离是从点X到点Y,并且|XY|>|BC|,又因为A是距离B最远的点之一,所以|AX|<=|AB|。M是A到X和A到B这两条路径上的分叉点(这个点一定存在,因为至少A是这两条路径的共同点),那么|AX|=|AM|+|MX|,|AB|=|AM|+|MB|,所以|MX|<=|MB|。根据树的性质,|XY|<=|XM|+|MY|<=|XM|+|MB|=|XB|。又因为C是距离B最远的距离之一,所以|BX|<=|BC|,所以|XY|<=|BC|,和假设矛盾,所以假设不成立,证明结束。
根据这个神奇的结论,我们以任意一个点为起点A,找到最远点中的一个B,在从B出发,找到最远点中的一个C,那么BC的距离就是答案了。只需要两次DFS即可。
时间复杂度o(T*n),空间复杂度o(n)。
AC代码:
#include<iostream>#include<cstdio>#include<cstring>#include<algorithm>using namespace std;struct Edge{int v,dis;}e[60005];int nums;int root[30005],nex[60005];bool life[30005];int maxdis,flag;void dfs(int dis,int x){life[x]=1;int temp=root[x];if(dis>maxdis){maxdis=dis;flag=x;}while(temp){if(!life[e[temp].v])dfs(dis+e[temp].dis,e[temp].v);temp=nex[temp];}}int main(){int T,n,a,b,w;scanf("%d",&T);for(int t=1;t<=T;t++){scanf("%d",&n);nums=0;memset(root,0,sizeof(root));for(int i=1;i<n;i++){scanf("%d%d%d",&a,&b,&w);nums++;e[nums].v=b;e[nums].dis=w;nex[nums]=root[a];root[a]=nums;nums++;e[nums].v=a;e[nums].dis=w;nex[nums]=root[b];root[b]=nums;}maxdis=0;memset(life,0,sizeof(life));dfs(0,0);maxdis=0;memset(life,0,sizeof(life));dfs(0,flag);printf("Case %d: %d\n",t,maxdis);}return 0;}