@TryMyEdge
2017-04-22T21:23:45.000000Z
字数 6860
阅读 870
题解
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);
}
else
tre[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);
else
add(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);
else
return 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);
else
add(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);
else
return 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));
else
return 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);
else
return 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;
}