北京信息科技大学第十三届程序设计竞赛暨ACM选拔赛
A.lzh的蹦床
根据贪心思想可以知道每一趟选择的出发点肯定是第一个不为1的数,但是如果一次次的模拟跳跃的话时间复杂度极高,那怎么处理呢?
我们可以来计算每一个蹦床对答案的贡献,然后相加即可。令sum[i]为左边跳到i的位置的次数,则在计算i位置的贡献时可分情况考虑:
如果 a [ i ] > 1 a[i]>1 a[i]>1时,它只会对[i+2,i+a[i]]这个区间产生一个+1的影响。
如果 s u m [ i ] ≥ a [ i ] sum[i]\ge a[i] sum[i]≥a[i]时,说明i位置已经变为1了,所以这个位置对答案也就没有贡献,但是对[i+1,i+a[i]]这个区间产生了影响,对[i+2,i+a[i]]这个区间产生了+1的影响,对i+1这个位置也产生了sum[i]-a[i]+1的影响。
如果 s u n m [ i ] < a [ i ] sunm[i]<a[i] sunm[i]<a[i]时,有部分是左边传来的,这部分不能加入到答案的贡献中,只有剩下的a[i]-sum[i]-1才是i位置对答案的贡献。
那就最后就相当于一个区间修改操作和单点查询操作了,那什么可以解决勒?线段树、树状数组、差分都能解决。(线段树实测会T)
参考代码:
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int maxn=5e3+10;
ll a[maxn],sum[maxn],n;
int main()
{
int t;
scanf("%d",&t);
while(t--)
{
scanf("%lld",&n);
memset(sum,0,sizeof(sum));
for(int i=1;i<=n;i++)
scanf("%lld",&a[i]);
ll ans=0;
for(int i=1;i<=n;i++)
{
sum[i]+=sum[i-1];
if(sum[i]>=a[i])
{
if(i+1<=n)
{
sum[i+1]+=sum[i]-a[i];
sum[i+2]-=sum[i]-a[i];
sum[i+1]++;
sum[min(n,i+a[i])+1]--;
}
}
else
{
if(sum[i]==0)
{
ans+=a[i]-1;
if(i+2<=n)
{
sum[i+2]++;
sum[min(n,i+a[i])+1]--;
}
}
else
{
ll l=i+a[i]-sum[i]+1,r=i+a[i];
if(l<=n)
{
sum[l]++;
sum[min(n,r)+1]--;
}
ans+=a[i]-sum[i]-1;
if(i+2<=n)
{
sum[i+2]++;
sum[min(n,l-1)+1]--;
}
}
}
}
printf("%lld\n",ans);
}
system("pause");
return 0;
}
B.所谓过河
总感觉我的时间复杂度会炸,但是学出来提交上去竟然只有7ms,绝了!!!
我想这的是将两两相交的圆用并查集维护起来,然后判断与y=0相交的圆是否和y=H相加的圆在一个集合里面。
参考代码:
#include<bits/stdc++.h>
using namespace std;
const double PI = acos(-1.0);
const double eps = 1e-8;
const int maxn=1e4+10;
int s[maxn],n;
double h;
int sgn(double x)
{
if (fabs(x) < eps)return 0;
else return x < 0 ? -1 : 1;
}
int cmp(double x, double y)
{
if (fabs(x - y) < eps||x < y) return 0;
return 1;
}
struct Point {
double x, y;
Point() {}
Point(double x, double y) :x(x), y(y) {}
Point operator + (Point B) { return Point(x + B.x, y + B.y); }
Point operator - (Point B) { return Point(x - B.x, y - B.y); }
Point operator * (double k) { return Point(x * k, y * k); }
Point operator / (double k) { return Point(x / k, y / k); }
};
struct Circle {
Point c;
double r;
Circle() {}
Circle(Point c, double r) :c(c), r(r) {}
Circle(double x, double y, double _r) { c = Point(x, y), r = _r; }
}a[maxn];
double dis(Point A, Point B)
{
return sqrt((A.x - B.x) * (A.x - B.x) + (A.y - B.y) * (A.y - B.y));
}
void init()
{
for(int i=1;i<=n;i++)
s[i]=i;
}
int find(int x)
{
if(x!=s[x])s[x]=find(s[x]);
return s[x];
}
void lh(int x,int y)
{
x=find(x),y=find(y);
if(x!=y)s[x]=s[y];
}
int main()
{
scanf("%d%lf",&n,&h);
init();
for(int i=1;i<=n;i++)
{
double x,y,r;
scanf("%lf%lf%lf",&x,&y,&r);
a[i]={x,y,r};
}
for(int i=1;i<=n;i++)
{
for(int j=i+1;j<=n;j++)
{
double d=dis(a[i].c,a[j].c);
if(cmp(d,a[i].r+a[j].r)==0)lh(i,j);
}
}
vector<int>v1,v2;
for(int i=1;i<=n;i++)
{
if(sgn(a[i].c.y-a[i].r)<=0)v1.push_back(i);
if(a[i].c.y+a[i].r>=h)v2.push_back(i);
}
int flag=0;
for(int i=0;i<v1.size();i++)
{
for(int j=0;j<v2.size();j++)
{
int x,y;
x=find(v1[i]);
y=find(v2[j]);
if(x==y)
{
flag=1;
break;
}
}
if(flag==1)break;
}
if(flag==0)printf("No\n");
else printf("Yes\n");
system("pause");
return 0;
}
C.旅行家问题1
没得思路,就判断起始点的位置,然后分情况求最小就行。
参考代码:
#include<bits/stdc++.h>
using namespace std;
#define ll long long
int main()
{
int n,x;
scanf("%d%d",&n,&x);
ll minn=LLONG_MAX,maxx=LLONG_MIN;
for(int i=1;i<=n;i++)
{
ll xx;
scanf("%lld",&xx);
minn=min(minn,xx);
maxx=max(maxx,xx);
}
if(x<=minn)printf("%lld\n",maxx-x);
else if(x>=maxx)printf("%lld\n",x-minn);
else printf("%lld\n",min(2*(x-minn)+maxx-x,2*(maxx-x)+x-minn));
system("pause");
return 0;
}
D.旅行家问题2
贪心思想,可以知道对于每一座山只会到达一次和离开一次,所以n座山可以形成一个环。并且到达最高的山之后返回过程一定都是靠梯子来完成的,在到达最高山这个过程可能采用梯子,也有可能坐飞机。
为了方便计算,我们可以假设全部采用梯子,所以总和就是 ∑ x i \sum x_i ∑xi,对于一些 h [ j ] > h [ i ] + x [ i ] h[j]>h[i]+x[i] h[j]>h[i]+x[i]的山只能采用坐飞机的方式,所以对答案另外的贡献就是 h [ j ] − h [ i ] − x [ i ] h[j]-h[i]-x[i] h[j]−h[i]−x[i],所以答案就是 ∑ x i \sum x_i ∑xi+ ∑ h [ j ] − h [ i ] − x [ i ] \sum h[j]-h[i]-x[i] ∑h[j]−h[i]−x[i],因为前面是固定的,要让答案最小则需要后面最小。在计算后面时只需要维护一个 h [ i ] + x [ i ] h[i]+x[i] h[i]+x[i]的最大值即可。
参考代码:
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int maxn=1e5+10;
int n;
struct node{
ll h,x,maxh;
}a[maxn];
bool cmp(node xx,node yy)
{
return xx.h<yy.h;
}
int main()
{
scanf("%d",&n);
ll ans=0;
for(int i=1;i<=n;i++)
{
scanf("%lld%lld",&a[i].h,&a[i].x);
ans+=a[i].x;
a[i].maxh=a[i].h+a[i].x;
}
sort(a+1,a+1+n,cmp);
ll maxx=a[1].maxh;
for(int i=2;i<=n;i++)
{
if(a[i].h>maxx)ans+=(a[i].h-maxx);
maxx=max(maxx,a[i].maxh);
}
printf("%lld\n",ans);
system("pause");
return 0;
}
E.小菲和Fib数列
对于 ( x i × x j + 1 ) (x_i\times x_j +1)%2 (xi×xj+1)这个式子可以知道,要想对答案有1的贡献就是 x i 、 x j x_i、x_j xi、xj全为偶数还在一个奇数一个偶数。
那这不就很简单了嘛,求出前n项的斐波拉契数,统计奇数和偶数,答案就是 e v e n × ( e v e n − 1 ) ÷ 2 + o d d × e v e n even\times (even-1)\div 2+odd\times even even×(even−1)÷2+odd×even
参考代码:
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int maxn=5e4+10;
ll a[maxn],sum1,sum2;
int n;
void init()
{
a[1]=1,a[2]=1;
sum1=2;
for(int i=3;i<=n;i++)
{
a[i]=a[i-1]+a[i-2];
a[i]%=2;
if(a[i]==1)sum1++;
else sum2++;
}
}
int main()
{
scanf("%d",&n);
if(n==1)printf("0\n");
else
{
init();
//cout<<sum1<<" "<<sum2<<endl;
ll x,y;
if(sum2<2)y=0;
else y=(sum2-1)*sum2/2;
x=sum1*sum2;
printf("%lld\n",x+y);
}
system("pause");
return 0;
}
F.好玩的音乐游戏
没得什么思路,就模拟一下就行。
参考代码:
#include<bits/stdc++.h>
using namespace std;
const int maxn=2e3+10;
char a[maxn][10],op[10];
vector<int>v[maxn];
int n,m;
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++)
{
for(int j=1;j<=7;j++)
a[i][j]=' ';
}
for(int i=1;i<=n;i++)
{
int x,y;
scanf("%s%d%d",op,&x,&y);
if(strcmp(op,"tap")==0)a[x][y]='O';
else a[x][y]='X';
v[x].push_back(y);
}
for(int i=1;i<=m;i++)
{
if(v[i].size()<=1)continue;
sort(v[i].begin(),v[i].end());
for(int j=v[i][0];j<=v[i][v[i].size()-1];j++)
{
if(a[i][j]==' ')a[i][j]='-';
}
}
for(int i=m;i>=1;i--)
{
printf("|");
for(int j=1;j<=7;j++)
printf("%c",a[i][j]);
printf("|\n");
}
printf("+-------+\n");
system("pause");
return 0;
}
G.ranko的手表
就直接求出两个时间的所有取值,在可行的范围内求个最小值和最大值就行,纯模拟,没得技术含量
参考代码:
#include<bits/stdc++.h>
using namespace std;
char a[10],b[10];
vector<int>v1,v2;
void dfs_a(int st,int num,int sum)
{
if(st==5)
{
v1.push_back(sum+num);
return ;
}
if(a[st]==':')
{
sum=num*60;
dfs_a(st+1,0,sum);
}
else if(a[st]!='?')dfs_a(st+1,num*10+(a[st]-'0'),sum);
else
{
if(st==0)
{
for(int i=0;i<=2;i++)dfs_a(st+1,i,sum);
}
else if(st==1)
{
for(int i=0;i<=9;i++)
dfs_a(st+1,min(23,num*10+i),sum);
}
else if(st==3)
{
for(int i=0;i<=5;i++)
dfs_a(st+1,i,sum);
}
else if(st==4)
{
for(int i=0;i<=9;i++)
dfs_a(st+1,min(59,num*10+i),sum);
}
}
}
void dfs_b(int st,int num,int sum)
{
if(st==5)
{
v2.push_back(sum+num);
return ;
}
if(b[st]==':')
{
sum=num*60;
dfs_b(st+1,0,sum);
}
else if(b[st]!='?')dfs_b(st+1,num*10+(b[st]-'0'),sum);
else
{
if(st==0)
{
for(int i=0;i<=2;i++)dfs_b(st+1,i,sum);
}
else if(st==1)
{
for(int i=0;i<=9;i++)
dfs_b(st+1,min(23,num*10+i),sum);
}
else if(st==3)
{
for(int i=0;i<=5;i++)
dfs_b(st+1,i,sum);
}
else if(st==4)
{
for(int i=0;i<=9;i++)
dfs_b(st+1,min(59,num*10+i),sum);
}
}
}
int main()
{
scanf("%s%s",a,b);
dfs_a(0,0,0);
dfs_b(0,0,0);
sort(v1.begin(),v1.end());
sort(v2.begin(),v2.end());
int maxx=INT_MIN,minn=INT_MAX;
for(int i=0;i<v1.size();i++)
{
for(int j=0;j<v2.size();j++)
{
if(v1[i]<v2[j])
{
maxx=max(v2[j]-v1[i],maxx);
minn=min(minn,v2[j]-v1[i]);
}
}
}
printf("%d %d\n",minn,maxx);
system("pause");
return 0;
}
H.字母收集
简单dp问题,状态方程也很容易推出来
d
p
[
i
]
[
j
]
=
m
a
x
(
d
p
[
i
−
1
]
[
j
]
,
d
p
[
i
]
[
j
−
1
]
)
+
a
[
i
]
[
j
]
dp[i][j]=max(dp[i-1][j],dp[i][j-1])+a[i][j]
dp[i][j]=max(dp[i−1][j],dp[i][j−1])+a[i][j],只需处理l、o、v、e这四个字母的值就行。
参考代码:
#include<bits/stdc++.h>
using namespace std;
const int maxn=510;
int a[maxn][maxn],n,m;
int dp[maxn][maxn];
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++)
{
char x;
scanf(" %c",&x);
if(x=='l')a[i][j]=4;
else if(x=='o')a[i][j]=3;
else if(x=='v')a[i][j]=2;
else if(x=='e')a[i][j]=1;
}
}
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++)
dp[i][j]=max(dp[i-1][j],dp[i][j-1])+a[i][j];
}
printf("%d\n",dp[n][m]);
system("pause");
return 0;
}
I.数字染色
还没研究出来,老菜鸡了…
J.小红的心愿
参考代码:
10
K.小红的树
直接用sz[i]表示以i为根节点的子树的大小,则 s z [ i ] = ∑ s z [ j ] sz[i]=\sum sz[j] sz[i]=∑sz[j](j为i的儿子),先预处理所有的sz,询问时O(1)就能解决。
参考代码:
#include<bits/stdc++.h>
using namespace std;
const int maxn=1e5+10;
char a[maxn];
int h[maxn],n,cnt,sz[maxn];
struct Edge{
int to;
int next;
}edge[maxn];
void add(int u,int v)
{
edge[++cnt].to=v;
edge[cnt].next=h[u];
h[u]=cnt;
}
void dfs(int u)
{
if(a[u]=='R')sz[u]=1;
for(int i=h[u];~i;i=edge[i].next)
{
int j=edge[i].to;
dfs(j);
sz[u]+=sz[j];
}
}
int main()
{
scanf("%d",&n);
memset(h,-1,sizeof(h));
for(int i=2;i<=n;i++)
{
int x;
scanf("%d",&x);
add(x,i);
}
scanf("%s",a+1);
dfs(1);
int q;
scanf("%d",&q);
while(q--)
{
int u;
scanf("%d",&u);
printf("%d\n",sz[u]);
}
system("pause");
return 0;
}
L.重排字符串
简单构造题。先判断什么时候要输出no呢?输出no的情况应该是最多的出现次数大于 ( n + 1 ) / 2 (n+1)/2 (n+1)/2,其余的情况全是yes。
那yes的情况要怎样构造呢?因为要相邻字符不同,则可以先将出现次数最多的字符从1好位置开始放置,然后每一次都放置在+2的位置上。由于每一个位置都可以由 1 + 2 x 或 者 2 + 2 x 1+2x或者2+2x 1+2x或者2+2x表示,所以在放完1+2x的情况后,可将剩下的字符全部放在2+2x这些位置上,这就保证了相邻字符不同的情况。
参考代码:
#include<bits/stdc++.h>
using namespace std;
const int maxn=1e5+10;
#define PI pair<int,char>
int vis[maxn];
PI p[30];
int n;
bool cmp(PI x,PI y)
{
return x.first>y.first;
}
int main()
{
scanf("%d",&n);
for(int i=0;i<26;i++)
p[i]={0,'a'+i};
for(int i=1;i<=n;i++)
{
char op;
scanf(" %c",&op);
p[op-'a'].first++;
}
sort(p,p+26,cmp);
if(p[0].first>(n+1)/2)printf("no\n");
else
{
int idx=1;
memset(vis,-1,sizeof(vis));
for(int i=0;i<26;i++)
{
if(p[i].first==0)break;
while(p[i].first!=0)
{
if(vis[idx]==-1)
{
vis[idx]=p[i].second-'a';
p[i].first--;
}
idx+=2;
if(idx>n)idx=2;
}
}
printf("yes\n");
for(int i=1;i<=n;i++)
{
char x='a'+vis[i];
printf("%c",x);
}
printf("\n");
}
system("pause");
return 0;
}
如有问题,欢迎大佬私信!!!!!!!!