2021“MINIEYE杯”中国大学生算法设计超级联赛(8)
1003-Ink on paper
题意:
给定n滴墨水的坐标,每一滴墨水以每秒0.5米的速度向四周扩散,求最后所有墨水连通的最少时间。
思路:
很简单能想到是一道最小生成树prim的板题,但是比赛过程中队友板子用错了超时了一发,然后又因为初始化时dis数组没用long long初始化导致WA了一发。
参考代码:
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define PI pair<ll,ll>
const int maxn=5e3+10;
const ll inf=0x3f3f3f3f3f3f3f3f;
ll a[maxn][maxn],dis[maxn];
PI p[maxn];
bool vis[maxn];
int n;
ll Dis(PI x,PI y)
{
return (x.first-y.first)*(x.first-y.first)+(x.second-y.second)*(x.second-y.second);
}
ll prim()
{
ll ans=0;
memset(vis,false,sizeof(vis));
memset(dis,inf,sizeof(dis));
for(int i=0;i<n;i++)
{
int t=-1;
for(int j=1;j<=n;j++)
if(vis[j]==0&&(t==-1||dis[t]>dis[j]))
t=j;
if(i&&dis[t]==inf)return inf;
if(i)ans=max(ans,dis[t]);
vis[t]=1;
for(int j=1;j<=n;j++)
dis[j]=min(dis[j],a[t][j]);
}
return ans;
}
int main()
{
int t;
scanf("%d",&t);
while(t--)
{
memset(a,inf,sizeof(a));
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
ll x,y;
scanf("%lld%lld",&x,&y);
p[i]={x,y};
}
for(int i=1;i<=n;i++)
{
for(int j=i;j<=n;j++)
{
a[i][j]=Dis(p[i],p[j]);
a[j][i]=Dis(p[i],p[j]);
}
}
ll ans=prim();
printf("%lld\n",ans);
}
// system("pause");
return 0;
}
1004-Counting Stars
题意:
对数组进行三种操作:
1.查询[l,r]区间总和
2.将[l,r]区间的每一个 a i a_i ai减去一个lowbit( a i a_i ai)
3.将[l,r]区间的每一个 a i a_i ai加上一个 2 k 2^k 2k( 2 k ≤ a i ≤ 2 k + 1 2^k\le a_i \le 2^{k+1} 2k≤ai≤2k+1)
思路:
这题很容易就想到用线段树来操作,但是加法和减法如何实现呢?可以考虑将 a i a_i ai加减看成二进制加减,把 a i a_i ai分解成最高位和后面部分(12可以分解成8和4,即1000和0100)。
加法相当于把最高位左移一位,减法先减去后面部分,当后面部分为零时再来减最高位。加法很好操作,直接开一个lazy记录移位就行,但是减怎么减呢?暴力!比赛时队友不敢暴力减,这题也是没能成功AC。但是在更新节点还得优化一下,当节点的总和为0时,无论加还是减都不能改变值了,在update的时候可以加以判断防止TLE。
参考代码:
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int maxn=1e5+10;
const ll M=998244353;
ll a[maxn],b[maxn],c[maxn];
struct node{
ll l,r;
ll val1,val2;
ll lazy;
ll sum;
}tree[maxn<<2];
ll ksm(ll x,ll y)
{
ll ans=1;
while(y)
{
if(y&1)ans=ans*x%M;
x=x*x%M;
y>>=1;
}
return ans;
}
ll lowbit(ll x)
{
return x&-x;
}
void pushup(ll root)
{
tree[root].val1=(tree[root<<1].val1+tree[root<<1|1].val1)%M;
tree[root].val2=(tree[root<<1].val2+tree[root<<1|1].val2)%M;
tree[root].sum=(tree[root<<1].sum+tree[root<<1|1].sum)%M;
}
void pushdown(ll root)
{
node &u=tree[root],&le=tree[root<<1],&ri=tree[root<<1|1];
if(u.lazy!=0)
{
le.lazy+=u.lazy;
ri.lazy+=u.lazy;
ll f=ksm(2,u.lazy);
le.val1=le.val1*f%M;
ri.val1=ri.val1*f%M;
le.sum=(le.val1+le.val2)%M;
ri.sum=(ri.val1+ri.val2)%M;
u.lazy=0;
}
}
void build(ll l,ll r,ll root)
{
if(l==r)tree[root]={l,r,b[l],c[l],0,a[l]};
else
{
tree[root]={l,r,0,0,0,0};
ll mid=l+r>>1;
build(l,mid,root<<1);
build(mid+1,r,root<<1|1);
pushup(root);
}
}
void update1(ll l,ll r,ll root)
{
if(tree[root].l==tree[root].r)
{
if(tree[root].val2!=0)
{
tree[root].val2-=lowbit(tree[root].val2);
tree[root].sum=(tree[root].val1+tree[root].val2)%M;
}
else tree[root]={tree[root].l,tree[root].r,0,0,0,0};
}
else
{
pushdown(root);
ll mid=tree[root].l+tree[root].r>>1;
if(l<=mid&&tree[root<<1].sum!=0)update1(l,r,root<<1);
if(r>mid&&tree[root<<1|1].sum!=0)update1(l,r,root<<1|1);
pushup(root);
}
}
void update2(ll l,ll r,ll root)
{
if(tree[root].l>=l&&tree[root].r<=r)
{
tree[root].lazy+=1;
tree[root].val1=(tree[root].val1<<1)%M;
tree[root].sum=(tree[root].val1+tree[root].val2)%M;
}
else
{
pushdown(root);
ll mid=tree[root].l+tree[root].r>>1;
if(l<=mid&&tree[root<<1].sum!=0)update2(l,r,root<<1);
if(r>mid&&tree[root<<1|1].sum!=0)update2(l,r,root<<1|1);
pushup(root);
}
}
ll query(ll l,ll r,ll root)
{
if(tree[root].l>=l&&tree[root].r<=r)return tree[root].sum;
//return (tree[root].val1+tree[root].val2)%M;
else
{
pushdown(root);
ll mid=tree[root].l+tree[root].r>>1;
ll ans=0;
if(l<=mid)ans=(ans+query(l,r,root<<1))%M;
if(r>mid)ans=(ans+query(l,r,root<<1|1))%M;
return ans;
}
}
int main()
{
int t;
scanf("%d",&t);
while(t--)
{
int n;
scanf("%d",&n);
for(int i=1;i<=n;i++)
scanf("%lld",&a[i]);
for(int i=1;i<=n;i++)
{
for(int j=30;j>=0;j--)
{
int x=(a[i]>>j)&1;
if(x==1)
{
b[i]=1<<j;
c[i]=a[i]-b[i];
break;
}
}
}
build(1,n,1);
int q;
scanf("%d",&q);
while(q--)
{
int op,l,r;
scanf("%d%d%d",&op,&l,&r);
if(op==1)printf("%lld\n",query(l,r,1));
else if(op==2)update1(l,r,1);
else update2(l,r,1);
}
}
//system("pause");
return 0;
}
1006-GCD Game
题意:
给定一个长度为n的数组,每一步可以选择一个x( 1 ≤ x < a i 1\le x < a_i 1≤x<ai)将 a i a_i ai的值修改为gcd(x, a i a_i ai)。Alice先操作然后Bob操作,当数组不能操作时比赛结束,求最后谁能赢
思路:
可以将每一个 a i a_i ai的质因子个数求出来,最后可以发现就是Nim游戏。(我还是太菜了,这种题竟然想了一个多小时)
参考代码:
#include<bits/stdc++.h>
using namespace std;
// const int maxn=1e6+10;
const int maxm = 1e7 + 10;
int a[maxm],n,cnt,sum[maxm];
int prime[maxm];
bool vis[maxm];
void Prime()
{
for (int i = 2; i < maxm; i++)
{
if (vis[i] == false)
{
sum[i]=1;
prime[cnt++] = i;
}
for (int j = 0; i * prime[j] < maxm; j++)
{
vis[i * prime[j]] = true;
sum[i * prime[j]]=sum[i]+1;
if (i % prime[j] == 0)
{
//sum[i]=sum[i/prime[j]]+1;
break;
}
}
}
}
int main()
{
int t;
scanf("%d",&t);
Prime();
while(t--)
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
scanf("%d",&a[i]);
int ans=0;
for(int i=1;i<=n;i++)
ans^=sum[a[i]];
if(ans)printf("Alice\n");
else printf("Bob\n");
}
//system("pause");
return 0;
}
1008-Square Card
题目:
给定两个半径为r1和r2的两个圆,和一个边长为a的正方形,正方形可以任意放置,求正方形既在第一个又在第二个圆里的可能性与完全在第一个圆里的可能性的比值。
思路:
可以在第一个圆中求出正方形完全放入圆内的面积,即一个半径为R1的圆,切圆心与第一个圆相同,同样可以求出在第二个圆内的面积,即一个半径为R2的圆,圆心与第二个圆相同。
这时答案就变成了求半径为R1和R2两个圆的相交面积和半径为R1的圆面积的比值。那么现在就是如何求R1和R2了。只需要运用余弦定理解方程便可得到R1和R2,然后就是圆面积并的板子,具体细节见代码。细节没处理好可以WA到哭,特别处理出现负数的情况。
参考代码:
#include<bits/stdc++.h>
using namespace std;
const double eps=1e-8;
const double PI=acos(-1.0);
struct Circle{
double x,y,r;
};
double dis(double x1,double y1,double x2,double y2)
{
return sqrt((x1-x2)*(x1-x2)+(y1-y2)*(y1-y2));
}
double intersectionArea(Circle A,Circle B,double d)
{
double ang1=acos((A.r*A.r+d*d-B.r*B.r)/(2*A.r*d));
double ang2=acos((B.r*B.r+d*d-A.r*A.r)/(2*B.r*d));
return ang1*A.r*A.r + ang2*B.r*B.r - A.r*d*sin(ang1);
}
int main()
{
int t;
scanf("%d",&t);
while(t--)
{
Circle A,B;
double a;
scanf("%lf%lf%lf",&A.r,&A.x,&A.y);
scanf("%lf%lf%lf",&B.r,&B.x,&B.y);
scanf("%lf",&a);
if(fabs(4*B.r*B.r-a*a)<eps||fabs(4*A.r*A.r-a*a)<eps)printf("0.000000\n");
else
{
double r1=(-a+sqrt(max(4*A.r*A.r-a*a,0.0)))*0.5;
double r2=(-a+sqrt(max(4*B.r*B.r-a*a,0.0)))*0.5;
double d=dis(A.x,A.y,B.x,B.y);
if(r1+r2<=d)printf("0.000000\n");
else
{
if(r2-r1>=d)printf("1.000000\n");
else if(r1-r2>=d)
{
double xx,yy;
xx=PI*r2*r2;
yy=PI*r1*r1;
printf("%.6lf\n",xx/yy);
}
else
{
A.r=r1,B.r=r2;
double xx,yy;
xx=intersectionArea(A,B,d);
yy=PI*r1*r1;
printf("%.6lf\n",xx/yy);
}
}
}
}
//system("pause");
return 0;
}
1009-Singing Superstar
题目:
给定一个母串,和q个子串,求每一个子串在母串中的不相交的出现次数。
思路:
因为每一个子串的长度都小于等于30,所以就可以母串中长度小于等于30的子串先处理出来,将它插入到trie树中,对询问串的时候暴力查找。
在插入母串的时候可以用一个last数组标记当前串上一次出现的位置,当上一次出现的位置与当前位置不相交时cnt[root]++,最后对询问串的查找可直接输出cnt[root]即可。
参考代码:
#include<bits/stdc++.h>
using namespace std;
const int maxn=4e6+10;
const int maxm=1e5+10;
char a[maxm];
int trie[maxn][26],cnt[maxn],last[maxn];
int idx;
void init()
{
memset(trie,0,sizeof(trie));
memset(cnt,0,sizeof(cnt));
memset(last,0,sizeof(last));
idx=0;
}
int insert(int root,int x)
{
if(trie[root][x]==0)trie[root][x]=++idx;
root=trie[root][x];
return root;
}
int query(char a[],int len)
{
int root=0;
for(int i=0;i<len;i++)
{
int x=a[i]-'a';
if(trie[root][x]==0)return -1;
root=trie[root][x];
}
return root;
}
int main()
{
int t;
scanf("%d",&t);
while(t--)
{
init();
scanf("%s",a+1);
int len=strlen(a+1);
for(int i=1;i<=len;i++)
{
int root=0;
for(int j=i;j<=min(len,i+30-1);j++)
{
root=insert(root,a[j]-'a');
if(last[root]<i)
{
last[root]=j;
cnt[root]++;
}
}
}
int q;
scanf("%d",&q);
while(q--)
{
scanf("%s",a+1);
int root=query(a+1,strlen(a+1));
if(root==-1)printf("0\n");
else printf("%d\n",cnt[root]);
}
}
// system("pause");
return 0;
}