目录
B - Sum of Consecutive Prime Numbers
E - Exam in BerSU (hard version)
A - 敌兵布阵
题意:
C国的死对头A国这段时间正在进行军事演习,所以C国间谍头子Derek和他手下Tidy又开始忙乎了。A国在海岸线沿直线布置了N个工兵营地,Derek和Tidy的任务就是要监视这些工兵营地的活动情况。由于采取了某种先进的监测手段,所以每个工兵营地的人数C国都掌握的一清二楚,每个工兵营地的人数都有可能发生变动,可能增加或减少若干人手,但这些都逃不过C国的监视。
中央情报局要研究敌人究竟演习什么战术,所以Tidy要随时向Derek汇报某一段连续的工兵营地一共有多少人,例如Derek问:“Tidy,马上汇报第3个营地到第10个营地共有多少人!”Tidy就要马上开始计算这一段的总人数并汇报。但敌兵营地的人数经常变动,而Derek每次询问的段都不一样,所以Tidy不得不每次都一个一个营地的去数,很快就精疲力尽了,Derek对Tidy的计算速度越来越不满:"你个死肥仔,算得这么慢,我炒你鱿鱼!”Tidy想:“你自己来算算看,这可真是一项累人的工作!我恨不得你炒我鱿鱼呢!”无奈之下,Tidy只好打电话向计算机专家Windbreaker求救,Windbreaker说:“死肥仔,叫你平时做多点acm题和看多点算法书,现在尝到苦果了吧!”Tidy说:"我知错了。。。"但Windbreaker已经挂掉电话了。Tidy很苦恼,这么算他真的会崩溃的,聪明的读者,你能写个程序帮他完成这项工作吗?不过如果你的程序效率不够高的话,Tidy还是会受到Derek的责骂的.
思路:
线段树,百度是个好东西
线段树详解 - Xenny - 博客园 (cnblogs.com)
代码:
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstdlib>
#include<cstring>
using namespace std;
struct node
{
int l,r,num;
}s[500000];
int h[500000];
int creat(int t,int l,int r)//建树
{
if(l==r)
{
s[t].l=l;
s[t].r=r;
return s[t].num=h[l];
}
s[t].l=l;
s[t].r=r;
return s[t].num=creat(t<<1,l,(l+r)>>1)+creat((t*2)+1,((l+r)/2)+1,r);
}
void addre(int t,int a,int b)//单点更新
{
if(a==s[t].l&&a==s[t].r)
{
s[t].num=s[t].num+b;
return ;
}
if(((s[t].l+s[t].r)>>1)>=a)
{
addre(t*2,a,b);
}
else
{
addre(t*2+1,a,b);
}
s[t].num=s[t<<1].num+s[t*2+1].num;
}
int query(int t,int l,int r)//区间查询
{
int mid=(s[t].l+s[t].r)>>1;
if(s[t].l==l&&s[t].r==r)
{
return s[t].num;
}
if(mid>=r)
{
return query(t*2,l,r);
}
else if(mid<l)
{
return query(t*2+1,l,r);
}
else
{
return query(t*2,l,mid)+query(t*2+1,mid+1,r);
}
}
int main()
{
int T;
int n;
int a,b,mm=0;
char m[20];
scanf("%d",&T);
while(T--)
{
++mm;
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
scanf("%d",&h[i]);
}
creat(1,1,n);
printf("Case %d:\n",mm);
while(~scanf("%s",m))
{
if(strcmp(m,"End")==0) break;
if(strcmp(m,"Add")==0)
{
scanf("%d %d",&a,&b);
addre(1,a,b);
}
if(strcmp(m,"Sub")==0)
{
scanf("%d %d",&a,&b);
addre(1,a,-b);
}
if(strcmp(m,"Query")==0)
{
scanf("%d %d",&a,&b);
printf("%d\n",query(1,a,b));
}
}
}
return 0;
}
B - Sum of Consecutive Prime Numbers
题意:
一些正整数可以用一个或多个连续素数的和表示。一个给定的正整数有多少这样的表示?例如,整数53有两种表示形式5+7+11+13+17和53。整数41有三种表示形式2+3+5+7+11+13、11+13+17和41。整数3只有一种表示形式,即3。整数20没有这种表示形式。请注意,总和必须是连续素数
数字,因此7+13和3+5+5+7都不是整数20的有效表示形式。
您的任务是编写一个程序,报告给定正整数的表示数。
思路:
如题意,都应是素数,所以先做一次素数筛,然后再尺取答案
代码:
#include<iostream>
#include<cstdio>
#include<set>
#include<map>
#include<algorithm>
using namespace std;
#define maxn 10000000
bool vis[maxn];
int prime[maxn],x;
void oulashai(int n) //欧拉筛
{
for(int i=2; i<=n; i++)
{
if(!vis[i])
prime[x++]=i;
for(int j=0; j<x; j++)
{
if(i*prime[j]>n)
break;
vis[i*prime[j]]=true;
if(i%prime[j]==0)
break;
}
}
}
int main()
{
oulashai(100005);
int n;
while(cin>>n&&n)
{
int l=0,r=0,sum=0,num=0;
while(true)
{
while(prime[r]<=n&&sum<n)
sum+=prime[r++];
if(sum==n)
num++;
sum-=prime[l++];
if(sum<0)
break;
}
cout<<num<<endl;
}
return 0;
}
C - 天上的星星
题意:
在一个星光摧残的夜晚,蒜头君一颗一颗的数这天上的星星。
蒜头君给在天上巧妙的画了一个直角坐标系,让所有的星星都分布在第一象。天上有 n$颗星星,他能知道每一颗星星的坐标和亮度。
现在,蒜头君问自己 q次,每次他问自己每个矩形区域的星星的亮度和是多少(包含边界上的星星)。
思路:
求平面区域内和,二维前缀和和差分
代码:
#include<iostream>
#include<algorithm>
#include<cstring>
#include<cstdio>
using namespace std;
int w[2005][2005];//w[i][j]表示i,j位置处星星的亮度(差分数组)
int sum[2005][2005];//sum[i][j]表示从1,1到i,j矩形区域内w数组的和(二维前缀和数组)
int main()
{
int n;
cin >> n;
for(int i = 0; i < n; i++)
{
int x, y;
scanf("%d %d", &x, &y);//星星的坐标为x,y
int L;
scanf("%d", &L);
y++;//将区域(0,0)-(200000,200000)转移为(1,1)-(200001,200001),更易于求前缀和数组sum
x++;
w[y][x] += L;//x为列坐标,y为横坐标
}
for(int i = 1; i < 2002; i++)//求w的前缀和数组sum
{
for(int j = 1; j < 2002; j++)//都从1开始,w[0][x]与w[x][0]都为零,这样求的时候不用考虑数组越界
{
sum[i][j] = w[i][j] + sum[i - 1][j] + sum[i][j - 1] - sum[i - 1][j - 1];//即i,j>=1 i-1,j-1>=0
}
}
int q;
cin >> q;
for(int i = 0; i < q; i++)
{
int x1, y1, x2, y2;
scanf("%d %d %d %d", &x1, &y1, &x2, &y2);//询问q次
x1++;//转换为(1,1)到(200001,200001)的坐标
y1++;
x2++;
y2++;
int ans = sum[y2][x2]-sum[y2][x1-1]-sum[y1-1][x2]+sum[y1-1][x1-1];//通过二维前缀和求二维区间内的和
cout << ans << endl;
}
return 0;
}
D - Graveyard Design
题意:
乔治国王最近决定为皇家墓地设计一个新的设计。墓地必须由几个部分组成,每个部分必须是一个正方形的坟墓。所有路段必须有不同数量的坟墓。
在咨询占星家后,乔治国王决定截面边的长度必须是连续的正整数序列。边长为s的剖面包含s2个坟墓。乔治估计了墓地上的坟墓总数,现在他想知道所有可能满足条件的墓地设计。你被要求找到他们。
思路:
在所有墓地中查找符合条件的墓地,查找问题,尺取法
代码:
#include<cstdio>
#include<cmath>
#include<algorithm>
#include<iostream>
#define INF 10000005
using namespace std;
typedef long long ll;
struct node
{
ll answer,l,r;//这里的answer是区间长度,l是左端点,r是右端点
} ans[INF];
bool cmp(node n1,node n2)
{
return n1.answer>n2.answer;
}
int main()
{
ll n;
while(~scanf("%lld",&n))
{
ll left=1,right=1,sum=0,x=0;
ll s=sqrt(n);
while(left<=s)//左端点在s范围内
{
while(sum<n&&right-1<=s)//这里是右端点在s范围内
{
sum+=right*right;
right++;//因为这里每次都要跑一次right++,所以实际sum的区间长度是[left,right-1]
}
if(sum==n)
{
ans[x].answer=right-1-left+1;//右端点-左端点-1是区间长度
ans[x].l=left;//左端点
ans[x].r=right-1;//右端点
x++;
sum+=right*right;//这儿呢必须要有这个,就是说sum=n之后,判断之后的还有没有序列满足条件,
//就直接再加下一个数,然后sum就会大于n了,再接着跑下边那一个if
//(再说一下就是这儿的right已经是区间右端点的下一个数了,所以直接加right就ok)
right++;//保证right一直是区间右端点的右边一个数
}
if(sum>n)
{
sum-=left*left;
left++;
}
}
sort(ans,ans+x,cmp);
printf("%lld\n",x);
for(ll i=0; i<x; i++)
{
printf("%lld ",ans[i].answer);
for(ll j=ans[i].l; j<=ans[i].r; j++)
printf("%lld%c",j," \n"[j==ans[i].r]);
}
}
return 0;
}
// for(long long i=1;i<=7;i++)
// printf("%lld%c",i," \n"[i==7]); \n换成.看看
// cout<<1;
E - Exam in BerSU (hard version)
题意:
Beland州立大学已经开始了一个课程。许多学生正在参加考试。
Poligrafovich测谎仪将对一群n名学生进行检查。学生将从第1次到第n次依次参加考试。考试规则如下:
第i个学生随机选择一张票。
如果这张罚单对学生来说太难了,他不会回答,而是立即回家(这个过程太快了,以至于没有时间流逝)。这个学生考试不及格。
如果学生觉得这张票容易,他会花整整一分钟通过考试。比赛结束后,他立即得到分数并回家。
学生们按照固定的顺序,一个接一个地参加考试,没有任何中断。在任何时候,Poligrafovich测谎仪都会从一个学生那里得到答案。
对于所有学生来说,整个考试的持续时间为M分钟(最大),因此列表末尾的学生更有可能没有时间通过考试。
对于每个i学生,你应该计算需要考试不及格的学生的最小可能数量,以便第i个学生有足够的时间通过考试。
对于每个学生,我都会独立地找到答案。也就是说,如果在为学生i_1找到答案时,一些学生j应该离开,那么在为i_2找到答案时(i_2>i_1),学生j不必回家。
思路:
记录每个数字出现的次数,对于每个数字只需要从左往右扫一遍这个数组,
将较小的数字先取了,直到不能取为止。
代码:
#include<iostream>
using namespace std;
int a[105];
//记录每个数字出现的次数,对于每个数字只需要从左往右扫一遍这个数组,
//将较小的数字先取了,直到不能取为止。
int main()
{
int n,m;
cin>>n>>m;
for(int i=1;i<=n;i++)
{
int t;
cin>>t;
int man=0; //人
int remain=m-t; //剩下可以让他之前的人考试的时间
for(int j=1;j<=100;j++) //从时间少的开始选
{
int x=min(remain/j,a[j]); //总时间和每个时间的次数限制下的最多可考数
man+=x;
remain-=x*j;
}
a[t]++;
cout<<i-1-man<<" ";
}
}
F - Maximum Sum
题意:
给定一个正整数和负整数的二维数组,找到具有最大值的子矩形
总和矩形的和是该矩形中所有元素的和。在这个问题中,和最大的子矩形被称为最大子矩形。
子矩形是位于整个数组中大小为1×1或更大的任何连续子数组。
思路:
给出n*n的矩阵,求出里面子矩阵的和的最大值。
序列是一维的,矩阵是二维的,所以我们可以把矩阵转换为一维的来算。
也就是枚举矩阵的连续几行的合并,这样就转换为一维的了,再找其最大子序列,更新最大值就可以了。
代码:
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
int num[110][110];
int main()
{
int n;
while(~scanf("%d",&n)&&n)
{
for(int i=0; i<n; i++)
{
for(int j=0; j<n; j++)
{
scanf("%d",&num[i][j]);
}
}
int maxx=num[0][0];
for(int i=1; i<n; i++)
{
for(int j=0; j<n; j++)
{
num[i][j]+=num[i-1][j];//把上下两个数之和当成一个数
}
}
for(int i=0; i<n; i++)
{
for(int j=i; j<n; j++)
{
int sum=0;
for(int k=0; k<n; k++)
{
if(sum<0)//当出现小于0的和直接放弃
sum=0;
else if(i!=j)
sum+=num[j][k]-num[i][k];//矩阵的正数之和
if(sum>maxx&&sum>0)//比较正数中比较大的那个
maxx=sum;
}
}
}
printf("%d\n",maxx);
}
}
G - Pie
题意:
我的生日快到了,传统上我要做馅饼。不只是一个馅饼,不,我有N个馅饼,各种口味,各种尺寸。我的几个朋友要来参加我的聚会,他们每人都得到一块馅饼。这应该是一个馅饼的一块,而不是几个小块,因为看起来很乱。不过这一块可以是一整块馅饼。
我的朋友们很烦人,如果他们中的一个比其他人得到更大的一块,他们就会开始抱怨。因此,所有的馅饼都应该得到同样大小(但不一定形状相同)的馅饼,即使这会导致一些馅饼变质(这比破坏聚会要好)。当然,我自己也想要一块馅饼,而且那块馅饼的大小也应该一样。
我们所有人能得到的最大尺寸是多少?所有的馅饼都是圆柱形的,它们都有相同的高度1,但是馅饼的半径可以不同。
思路:
求出每个馅饼体积,求和除以人数求出最大半径,之后不断二分找出合适的馅饼半径
注意,人数要+1(自己)
代码:
#include<iostream>
#include<algorithm>
#include<cmath>
#include<cstdio>
using namespace std;
const double pi=acos(-1.0);
double a[100005];
int main()
{
int t;
scanf("%d",&t);
while(t--)
{
int n,f,r,sum1;
double sum=0,minn,maxx,mid;
scanf("%d%d",&n,&f);
for(int i=0;i<n;i++)
{
scanf("%d",&r);
a[i]=r*r*pi*1.0; //每个馅饼体积
sum+=a[i];
}
minn=0; //下界
maxx=sum/(f+1); //上界
while(maxx-minn>1e-5) //二分
{
mid=minn+(maxx-minn)/2; //每个人分到的馅饼半径
sum1=0;
for(int i=0;i<n;i++)
sum1+=(int)(a[i]/mid); //每个馅饼按mid半径分后的总人数
if(sum1>=(f+1)) //更新边界
minn=mid;
else
maxx=mid;
}
printf("%.4lf\n",mid);
}
}