BUPT 2022 Spring Training #5
A - Approach
题意
两条线段AB,CD。甲从A走向B,乙从C走向D。两人同时开走且速度相同,先到达目的地的人会停下来等待另一个人走完。问两人之间的最短距离是多少。
思路
三分。
分两段,两人同时在走的为一段,只有一个人走另一个人停下来等的为另一段。两段分别三分取最大值。
这题卡精度真卡得丧心病狂,必须用三角函数做否则过不了。我也不知道什么原理。
代码
#include<cstdio>
#include<cmath>
#include<algorithm>
using namespace std;
void swap(double &a,double &b)
{
double k=a; a=b; b=k;
return;
}
double dis(double x1,double y1,double x2,double y2)
{
return sqrt((x1-x2)*(x1-x2)+(y1-y2)*(y1-y2));
}
double ax,ay,bx,by,cx,cy,dx,dy;
double angle1,angle2;
double d_x,d_y;
double f(double t)
{
double x1,y1,x2,y2;
x1=ax+t*cos(angle1);
y1=ay+t*sin(angle1);
x2=cx+t*cos(angle2);
y2=cy+t*sin(angle2);
return dis(x1,y1,x2,y2);
}
double f2(double t)
{
double x1,y1,x2,y2;
x1=bx;
y1=by;
x2=cx+t*cos(angle2);
y2=cy+t*sin(angle2);
return dis(x1,y1,x2,y2);
}
int main()
{
int k,i,j;
double dis1,dis2;
double l,r,mid_l,mid_r;
double ans1,ans2;
scanf("%lf%lf%lf%lf%lf%lf%lf%lf",&ax,&ay,&bx,&by,&cx,&cy,&dx,&dy);
dis1=dis(ax,ay,bx,by); dis2=dis(cx,cy,dx,dy);
if(dis1>dis2)
{
swap(ax,cx); swap(ay,cy);
swap(bx,dx); swap(by,dy);
swap(dis1,dis2);
}
angle1=atan2(by-ay,bx-ax);
angle2=atan2(dy-cy,dx-cx);
l=0; r=dis1; k=0;
while(k<10000)
{
mid_l=l+(r-l)/3;
mid_r=r-(r-l)/3;
if(f(mid_l)>f(mid_r))l=mid_l;
else r=mid_r;
k++;
}
ans1=f(l);
l=dis1; r=dis2; k=0;
while(k<10000)
{
mid_l=l+(r-l)/3;
mid_r=r-(r-l)/3;
if(f2(mid_l)>f2(mid_r))l=mid_l;
else r=mid_r;
k++;
}
ans2=f2(l);
printf("%0.12lf\n",min(ans1,ans2));
return 0;
}
B - Baby name
题意
两个字符串A,B。要从A,B中分别挑选一个子串按顺序连接起来,使这个新串的字典序最大。
思路
首先,B串的子串必须是极大子串,用SAM求一波。
然后在用SAM求A串的的极大字串时,每走一步判断一下当前最后一个字符是不是小于B极大字串的第一个字符。如果是就停止,后面直接和B并起来。
代码
#include<cstdio>
#include<cstring>
#define ll long long
const int N=1e6+10;
char in1[200010];
char in2[200010];
int ans2[200010];
ll max(ll a,ll b)
{
if(a>b)return a;
return b;
}
struct samnode{
int son[26];
int fa,len;
int count;
};
struct SAM{
samnode t[N];
int last,tot;
void init()
{
last=tot=1;
int k,i;
for(k=0;k<N;k++)
{
for(i=0;i<26;i++)t[k].son[i]=0;
t[k].fa=t[k].len=0;
}
t[1].count=0;
return;
}
void insert(int x)
{
int k,i,j;
int p,np,q,nq;
p=last; np=++tot; last=np;
t[np].len=t[p].len+1;
t[np].count=1;
while(!t[p].son[x] && p)
{
t[p].son[x]=np;
p=t[p].fa;
}
if(!p)t[np].fa=1;
else
{
q=t[p].son[x];
if(t[q].len==t[p].len+1)t[np].fa=q;
else
{
nq=++tot;
t[nq]=t[q]; t[nq].len=t[p].len+1;
t[np].fa=t[q].fa=nq;
t[nq].count=0;
while(p && t[p].son[x]==q)
{
t[p].son[x]=nq;
p=t[p].fa;
}
}
}
}
}sam;
int main()
{
int k,i,j;
int len1,len2;
/*scanf("%s",&ch);
for(k=0;ch[k];k++)sam.insert(ch[k]-'a');
for(k=1;k<=sam.tot;k++)add(sam.t[k].fa,k,k);
dfs(1);
printf("%d\n",ans);*/
sam.init();
scanf("%s%s",&in1,&in2);
for(k=0;in2[k];k++)sam.insert(in2[k]-'a');
len2=0; k=1;
while(true)
{
for(i=25;i>=0;i--)if(sam.t[k].son[i])break;
if(i<0)break;
k=sam.t[k].son[i];
ans2[len2++]=i;
}
sam.init();
for(k=0;in1[k];k++)sam.insert(in1[k]-'a');
k=1;
for(i=25;i>=0;i--)if(sam.t[k].son[i])break;
printf("%c",'a'+i);
k=sam.t[k].son[i];
while(true)
{
for(i=25;i>=0;i--)if(sam.t[k].son[i])break;
if(i<0 || i<ans2[0])break;
k=sam.t[k].son[i];
printf("%c",'a'+i);
}
for(k=0;k<len2;k++)printf("%c",'a'+ans2[k]);
return 0;
}
C - Cipher count
题意
字符集大小为a,求长度不超过k的本质不同的维吉尼亚密码密钥个数。(两个密钥分别不断循环得到的字符串相同就是本质相同)
思路
将能够被记入答案的密钥称为有效密钥。
将一个有效密钥循环n次得到的密钥是无效密钥,反之不能由一个有效密钥循环n次得到的密钥就是有效密钥。用\(f_n\)表示长度为\(n\)的有效密钥个数,可以得到
依次枚举倍数即可完成,复杂度\(O(n\log n)\)
代码
#include <bits/stdc++.h>
typedef long long ll;
const ll mod = 1000000007;
ll f[5000010];
int main()
{
ll a, k;
scanf("%lld%lld", &a, &k);
ll powa = 1, sum = 0;
for (int i = 1; i <= k; i++)
{
powa = (powa * a) % mod;
f[i] = (f[i] + powa) % mod;
sum = (sum + f[i]) % mod;
for (int j = i * 2; j <= k; j += i)
{
f[j] = (f[j] - f[i] + mod) % mod;
}
}
printf("%lld\n", sum);
return 0;
}
D - Dice
题意
n个k面的有猫腻的骰子,每个骰子掷到数值为m的倍数的可能性都是0,其余面的可能性均等。n个骰子一起抛,问最后得到的所有骰子数值之和是m的倍数的概率是多少。
思路
我们先处理一个骰子,可以很容易地处理出一个骰子掷到模m为0到m-1的情况数是多少。
然后把这玩意看成一个对应0到m-1次的多项式,显然n个骰子掷出来每个模数的情况数是一个骰子的情况序列在多项式卷积意义下的n次幂。
借用快速幂的原理搞一下,再算一下多项式卷积即可。
由于\(m\leq 200\),并不需要FFT,暴力模拟即可。
代码
#include<cstdio>
#include<cstring>
#define int long long
const int N=2010;
const int mod=1e9+7;
int n,t,m;
int ans[N];
int x[N];
int tmp[N];
int pow(int x,int y)
{
int ans=1;
while(y)
{
if(y&1)ans=ans*x%mod;
x=x*x%mod;
y>>=1;
}
return ans;
}
void juanji(int *a,int *b)
{
int k,i,j;
for(k=0;k<m*2-1;k++)
{
tmp[k]=0;
for(i=0;i<=k;i++)tmp[k]=(tmp[k]+a[i]*b[k-i]%mod)%mod;
}
for(k=0;k<m;k++)a[k]=(tmp[k]+tmp[k+m])%mod;
return;
}
void find_ans(int y)
{
int k,i,j;
for(k=1;k<m;k++)ans[k]=0;
ans[0]=1;
while(y)
{
if(y&1)juanji(ans,x);
juanji(x,x);
y>>=1;
}
return;
}
signed main()
{
int k,i,j;
int p,q;
memset(ans,0,sizeof(ans));
memset(x,0,sizeof(x));
memset(tmp,0,sizeof(tmp));
scanf("%lld%lld%lld",&n,&t,&m);
for(k=1;k<=t;k++)x[k%m]++;
x[0]=0;
find_ans(n);
p=ans[0];
q=pow(pow(t-t/m,n),mod-2);
printf("%lld\n",p*q%mod);
return 0;
}
E - Enter to the best problem of this contest!
(虽然是交互题)水题略
F - Free restricted flights
题意
有一张单向图,两个人分别在两地,两人需要异于两人初始位置的点见面并返回初始位置,每个人有k次机会免费走一条边,问最小费用。
思路
分层图最短路,将图分成k+1层。分别建立正边和反边求去程和回程最短路,再枚举见面点和两人分别在去程和回程用多少次免费机会。
代码
#include <bits/stdc++.h>
typedef long long ll;
struct Edge
{
int v, w, next;
bool operator<(const Edge &o) const
{
return w > o.w;
}
} e[1000010];
int head[200010], cnt;
int dis1[200010], dis2[200010], dis3[200010], dis4[200010];
int u[10010], v[10010], w[10010];
void addedge(int u, int v, int w)
{
cnt++;
e[cnt].v = v;
e[cnt].w = w;
e[cnt].next = head[u];
head[u] = cnt;
}
void dijkstra(int s, int n, int dis[])
{
for (int i = 0; i <= n; i++) dis[i] = 0x3f3f3f3f;
dis[s] = 0;
std::priority_queue<Edge> pq;
pq.push({s, 0, 0});
while (!pq.empty())
{
int u = pq.top().v;
int udis = pq.top().w;
pq.pop();
if (udis != dis[u]) continue;
for (int i = head[u]; i; i = e[i].next)
{
int v = e[i].v, w = e[i].w;
if (dis[u] + w < dis[v])
{
dis[v] = dis[u] + w;
pq.push({v, dis[v], 0});
}
}
}
}
int main()
{
int n, m, a, b, k;
scanf("%d%d%d%d%d", &n, &m, &a, &b, &k);
for (int i = 1; i <= m; i++) scanf("%d%d%d", &u[i], &v[i], &w[i]);
for (int i = 1; i <= m; i++)
{
for (int j = 0; j <= k; j++)
{
addedge(j * n + u[i], j * n + v[i], w[i]);
if (j) addedge(j * n + u[i], (j - 1) * n + v[i], 0);
}
}
for (int i = 0; i < n; i++)
{
for (int j = 1; j <= k; j++) addedge(j * n + i, (j - 1) * n + i, 0);
}
dijkstra(k * n + a, (k + 1) * n, dis1);
dijkstra(k * n + b, (k + 1) * n, dis2);
cnt = 0;
for (int i = 0; i <= (k + 1) * n; i++) head[i] = 0;
for (int i = 1; i <= m; i++)
{
for (int j = 0; j <= k; j++)
{
addedge(j * n + v[i], j * n + u[i], w[i]);
if (j) addedge(j * n + v[i], (j - 1) * n + u[i], 0);
}
}
for (int i = 0; i < n; i++)
{
for (int j = 1; j <= k; j++) addedge(j * n + i, (j - 1) * n + i, 0);
}
dijkstra(k * n + a, (k + 1) * n, dis3);
dijkstra(k * n + b, (k + 1) * n, dis4);
int ans1, ans2 = 0x3f3f3f3f;
for (int i = 0; i < n; i++)
{
if (i == a || i == b) continue;
for (int k1 = 0; k1 <= k; k1++)
{
for (int k2 = 0; k2 <= k; k2++)
{
ll sum = (ll)dis1[k1 * n + i] + dis2[k2 * n + i] + dis3[(k - k1) * n + i] + dis4[(k - k2) * n + i];
if (sum < ans2)
{
ans2 = sum;
ans1 = i;
}
}
}
}
if (ans2 == 0x3f3f3f3f)
printf(">:(\n");
else
printf("%d %d\n", ans1, ans2);
return 0;
}
G - Great dinner
题意
n个人排成一队,有m对两两不交的限制关系\(2m\leq n\).其中\((a_i,b_i)\)表示\(a_i\)必须站在\(b_i\)的前头。问有多少排法。
思路
简单组合数学题。
先把有限制的人放进去,每次插空放。在有t个空的时候乘\((t^2+t)/2\),最后再把剩下的人随便放就行了。
代码
#include<cstdio>
#define int long long
const int mod=1e9+7;
int n,m;
int pow(int x,int y)
{
int ans=1;
while(y)
{
if(y&1)ans=ans*x%mod;
x=x*x%mod;
y>>=1;
}
return ans;
}
signed main()
{
int k,i,j;
int a,b,c;
int ans=1;
scanf("%lld%lld",&n,&m);
for(k=1;k<=m;k++)
{
scanf("%lld%lld",&a,&b);
ans*=(k*2-1)*k;
ans%=mod;
}
a=1;
for(k=n;k>=n-m*2+1;k--)a=a*k%mod;
b=1;
for(k=m*2;k>=1;k--)b=b*k%mod;
a=a*pow(b,mod-2)%mod;
ans=ans*a%mod;
for(k=n-2*m;k>=1;k--)ans=ans*k%mod;
printf("%lld\n",ans);
return 0;
}
H - Happy game
题意
求长度为奇数的内容不同的回文子串个数。
思路
使用manacher算法。在manacher过程中,从之前转移来的回文半径内的回文都是之前出现过的。只有转移后再延长的回文半径中才可能有之前没出现过的,总共只有\(O(n)\)个,对这些回文字串哈希判重即可。
注意由于生日悖论哈希极可能冲突,需要使用挂链法或者双哈希。
代码
#include <bits/stdc++.h>
typedef long long ll;
const ll mod1 = 1000000007;
const ll mod2 = 1000000009;
char s[100010];
int p[100010];
ll hash1[100010], hash2[100010];
std::set<std::pair<ll, ll>> set;
ll qpow(ll a, ll b, ll mod)
{
ll ret = 1 % mod;
while (b)
{
if (b & 1) ret = ret * a % mod;
b >>= 1;
a = a * a % mod;
}
return ret;
}
std::pair<ll, ll> gethash(int l, int r)
{
ll a = (hash1[r] - hash1[l - 1] * qpow(19, r - l + 1, mod1) % mod1 + mod1) % mod1;
ll b = (hash2[r] - hash2[l - 1] * qpow(17, r - l + 1, mod2) % mod2 + mod2) % mod2;
return {a, b};
}
int main()
{
int len;
scanf("%d%s", &len, s + 1);
for (int i = 1; i <= len; i++)
{
hash1[i] = (hash1[i - 1] * 19 + s[i]) % mod1;
hash2[i] = (hash2[i - 1] * 17 + s[i]) % mod2;
}
s[0] = '~';
int mid = 0, maxright = 0;
for (int i = 1; s[i]; i++)
{
if (i < maxright)
{
p[i] = std::min(p[2 * mid - i], maxright - i);
}
while (s[i + p[i] + 1] == s[i - p[i] - 1])
{
p[i]++;
set.insert(gethash(i, i + p[i]));
}
if (i + p[i] > maxright)
{
maxright = i + p[i];
mid = i;
}
}
printf("%d\n", (int)set.size());
return 0;
}
K - Katastrophic sort
水题略
L - Lonely day
题意
一张网格图,有的格是空的,有的格有障碍,没障碍的格只能相邻走,有障碍的格只能跳过去。问从S到T最少多少步。
思路
裸bfs。跑一遍直接完事了。细节有点麻烦。
代码
#include<cstdio>
#include<queue>
using namespace std;
const int N=2010;
char in[N];
int dis[N][N];
int mat[N][N];
int up[N][N];
int down[N][N];
int left[N][N];
int right[N][N];
char totans[N*N];
struct st1{
int x;
int y;
};
st1 pre[N][N];
queue<st1> q;
st1 s,t;
int n,m;
void bfs()
{
int k,i,j;
int x[4],y[4];
st1 a;
for(k=1;k<=n;k++)
for(i=1;i<=m;i++)
{
dis[k][i]=-1;
}
dis[s.x][s.y]=0;
while(!q.empty())q.pop();
q.push(s);
while(!q.empty())
{
a=q.front(); q.pop();
x[3]=a.x-up[a.x][a.y]; y[3]=a.y;
x[0]=a.x+down[a.x][a.y]; y[0]=a.y;
x[1]=a.x; y[1]=a.y-left[a.x][a.y];
x[2]=a.x; y[2]=a.y+right[a.x][a.y];
for(k=0;k<4;k++)
{
if(dis[x[k]][y[k]]!=-1)continue;
if(x[k]<1 || x[k]>n || y[k]<1 || y[k]>m)continue;
dis[x[k]][y[k]]=dis[a.x][a.y]+1;
q.push((st1){x[k],y[k]});
pre[x[k]][y[k]]=a;
}
if(dis[t.x][t.y]!=-1)return;
}
return;
}
int main()
{
int k,i,j;
int a,b,c;
int x,y,x1,y1;
int cnt;
scanf("%d%d",&n,&m);
for(k=1;k<=n;k++)
{
scanf("%s",&in);
for(i=0;i<m;i++)
{
if(in[i]=='.')mat[k][i+1]=0;
else if(in[i]=='X')mat[k][i+1]=1;
else if(in[i]=='S')
{
mat[k][i+1]=0;
s=(st1){k,i+1};
}
else
{
mat[k][i+1]=0;
t=(st1){k,i+1};
}
}
}
//----------------------------------------
for(k=1;k<=m;k++)up[1][k]=down[n][k]=1;
for(k=1;k<=n;k++)left[k][1]=right[k][m]=1;
for(k=2;k<=n;k++)
for(i=1;i<=m;i++)
{
if(!mat[k-1][i])up[k][i]=1;
else up[k][i]=up[k-1][i]+1;
}
for(k=n-1;k>=1;k--)
for(i=1;i<=m;i++)
{
if(!mat[k+1][i])down[k][i]=1;
else down[k][i]=down[k+1][i]+1;
}
for(k=1;k<=n;k++)
for(i=2;i<=m;i++)
{
if(!mat[k][i-1])left[k][i]=1;
else left[k][i]=left[k][i-1]+1;
}
for(k=1;k<=n;k++)
for(i=m-1;i>=0;i--)
{
if(!mat[k][i+1])right[k][i]=1;
else right[k][i]=right[k][i+1]+1;
}
//-----------------------------------------
bfs();
if(dis[t.x][t.y]==-1)
{
printf("-1\n");
return 0;
}
printf("%d\n",dis[t.x][t.y]);
/*for(k=1;k<=n;k++)
{
for(i=1;i<=m;i++)
{
printf("%d ",mat[k][i]);
}
printf("\n");
}
printf("\n");
for(k=1;k<=n;k++)
{
for(i=1;i<=m;i++)
{
printf("%d ",dis[k][i]);
}
printf("\n");
}
printf("\n");*/
cnt=0;
x=t.x; y=t.y;
while(!(x==s.x && y==s.y))
{
x1=pre[x][y].x; y1=pre[x][y].y;
if(y==y1 && x<x1)totans[cnt++]='U';
else if(y==y1 && x>x1)totans[cnt++]='D';
else if(x==x1 && y<y1)totans[cnt++]='L';
else totans[cnt++]='R';
x=x1; y=y1;
}
for(k=cnt-1;k>=0;k--)printf("%c",totans[k]);
printf("\n");
return 0;
}
M - Magic spells
题意
给一个字符串s,n次询问,每次询问给一个字符串,问其最长的是s子序列的前缀是什么。
思路
s中每个位置记录一下它后面最早的a~z的位置,每次匹配贪心地跳就行了。
代码
#include<cstdio>
#include<cstring>
const int N=2e5+10;
char s[N];
char in[N];
int n;
int next_[N][26];
int main()
{
int k,i,j;
int len;
scanf("%s",&s);
len=strlen(s);
for(k=0;k<26;k++)next_[len][k]=-1;
for(k=len-1;k>=0;k--)
{
for(i=0;i<26;i++)next_[k][i]=next_[k+1][i];
next_[k][s[k]-'a']=k+1;
}
scanf("%d",&n);
for(k=0;k<n;k++)
{
scanf("%s",&in);
if(next_[0][in[0]-'a']==-1)
{
printf("IMPOSSIBLE\n");
continue;
}
j=0;
for(i=0;in[i];i++)
{
if(next_[j][in[i]-'a']==-1)break;
printf("%c",in[i]);
j=next_[j][in[i]-'a'];
}
printf("\n");
}
return 0;
}

浙公网安备 33010602011771号