8.15 ——小测
8.15 8.15 ——小测
- 做题,先拆出母题,然后再对其加上扩展,最后方为正解。
- 当你写出暴力时,就向正解迈出了一大步
T1——Coprime
题面
思路
- 首先,我们可以想到纯暴力的写法,就是枚举每对可能进行判断,但一看数据范围就知道暴力出不了奇迹了……
- 但是,写出暴力就迈出了第一步,可以发现,对于“setwise coprime”情况暴力只需$ O(n)$的时间复杂度即可判断 ,而“not coprime”的判断只需判断是否都不满足其他条件即可,只需优化“pairwise coprime”的情况。
- 但我们该如何优化呢?发现两个数或一组数互不互质,和最大公因数有关。
- 通过对最大公因数的了解,发现最大公因数和质因数有密不可分的关系,只要两个数哪怕只有一个质因数相同就一定不互质。
- 扩展!判断断一组数是否互质只需判断有没有拥有同个质因数的一对的数。
- 但怎么统计呢,可以用下标计数和质因数拆分进行预处理。
- 那么“pairwise coprime”的情况就OK了。
code
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e6+5;
int n,m,a,mxa;
bool vis[N],vis2[N],s[N];
vector<int>d[N];
signed main(){
//freopen("gcd.in","r",stdin);
//freopen("gcd.out","w",stdout);
scanf("%lld",&n);
for(int i=1;i<=n;i++){
scanf("%lld",&a);
mxa=max(mxa,a);
s[a]=1;
}
int ans=0;
for(int i=2;i<=mxa;i++){
if(vis[i]==0){
for(int j=i*2;j<=mxa;j+=i){
vis[j]=1;
d[j].push_back(i);
}
}
}
int t=mxa,bj=0;
for(int i=1;i<=mxa;i++){
if(s[i]==1){
t=__gcd(t,i);
if(vis[i]==0){
if(vis2[i]==1){
bj=1;
break;
}
vis2[i]=1;
}else{
for(int j=0;j<d[i].size();j++){
if(vis2[d[i][j]]==1){
bj=1;
break;
}
vis2[d[i][j]]=1;
}
}
}
}
if(t!=1){
printf("not coprime");
return 0;
}
if(bj==0)printf("pairwise coprime");
else printf("setwise coprime");
return 0;
}
T2——旅游
题面
思路
- 首先,在想这题时可以先把传送门给去掉,那这题就简化了许多。
- 第一时间反应的是最短路,但是看见数据范围以及多次提问后,这个想法就可以放弃了。
- 但是用什么方法呢?观察题面可以发现整张图其实就是一颗树。
- 可这有什么用呢?联想到LCA(最近公共祖先),我们发现,在不考虑传送门的情况下,u点到v点的距离就是:$ u点到lca的距离+v点到lca的距离$。
- 这下只需对整棵树进行预处理即可快速求出两点之间的距离。
- 但是,加上了传送门呢?发现只要寻找出两点之间的最近传送门,并将到达两者的时间相加,和不用传送门的方法取最小值即可。
- 至于寻找最近传送门,用bfs预处理即可。
code
#include<bits/stdc++.h>
using namespace std;
const int N=2e5+5;
int n,m,q,fa[N];
bool vis[N];
struct edge{
int n,v,w;
}s[N*2];
struct edge2{
int x,y,z;
}d[N];
int cur,h[N];
void Add(int x,int y,int z){
cur++;
s[cur].n=h[x];
s[cur].v=y;
s[cur].w=z;
h[x]=cur;
}
int far[N];
void bfs(){
queue<int>q;
for(int i=1;i<=n;i++){
far[i]=INT_MAX>>1;
if(vis[i]==1){
q.push(i);
far[i]=0;
}
}
while(!q.empty()){
int t=q.front();
q.pop();
for(int i=h[t];i;i=s[i].n){
if(far[s[i].v]==INT_MAX>>1){
far[s[i].v]=far[t]+1;
q.push(s[i].v);
}
}
}
}
int deep[N],ma[N][21];
void dfs(int x,int y,int c){
deep[x]=c;
for(int i=h[x];i;i=s[i].n){
if(s[i].v==y)continue;
ma[s[i].v][0]=x;
dfs(s[i].v,x,c+1);
}
}
int lca(int x,int y){
if(deep[x]<deep[y])swap(x,y);
int ans=deep[x]+deep[y],w1=far[x],w2=far[y];
for(int i=20;i>=0;i--){
if(deep[ma[x][i]]>=deep[y]){
x=ma[x][i];
}
}
if(x==y){
ans=min(ans-deep[x]*2,w1+w2);
return ans;
}
for(int i=20;i>=0;i--){
if(ma[x][i]!=ma[y][i]){
x=ma[x][i];
y=ma[y][i];
}
}
ans=min(ans-deep[ma[x][0]]*2,w1+w2);
return ans;
}
signed main(){
scanf("%d%d%d",&n,&m,&q);
for(int i=1;i<n;i++){
scanf("%d%d",&d[i].x,&d[i].y);
Add(d[i].x,d[i].y,1);
Add(d[i].y,d[i].x,1);
}
for(int i=1;i<=m;i++){
int x;
scanf("%d",&x);
vis[x]=1;
}
bfs();
dfs(1,1,1);
for(int i=1;i<=20;i++) {
for(int j=1;j<=n;j++){
ma[j][i]=ma[ma[j][i-1]][i-1];
}
}
for(int i=1;i<=q;i++){
int x,y;
scanf("%d%d",&x,&y);
int t=lca(x,y);
printf("%d\n",t);
}
return 0;
}/*
*/
T3——考试
题面
思路
- 首先,先想暴力该怎么做?显然,就是枚举每个可能的时间,在进行判断该时间的不愉快值,取最小值。
- 暴力想好了,就是优化,发现学生的不愉快值可以利用差分预处理:
$$student[i]=student[i-1]+期待天小于i的学生数 $$
student[i]代表第i天学生产生了多少天的不愉快值。
-
这样求第i天学生产生了多少天的愉快值就可以用$student[i]*C$
快速求出学生产出的不愉快值。
-
但是老师产生的呢?发现对于老师的两种操作,如果A的操作费用小于B,那么一定是优先使用A的,反之,优先使用B操作,而A操作只需考虑有多少天比目标天大。而B操作只需在加上统计有多少天可以用于转移即可,而这些都可以预处理,简化时间复杂度,而打出正解。
code
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e6+5;
long long A,B,C;
int n,m;
int a[N],b[N],c[N];
long long suma[N],sumb[N],sumc[N];
int pb[N],pa[N],pc[N];
long long ansb=0,ansa=0,an=0;
long long check(long long i){
an=sumc[i]*C;
if(A<B){
if(suma[i]>sumb[i])an+=sumb[i]*A;
else an+=suma[i]*A+(sumb[i]-suma[i])*B;
}else{
an+=sumb[i]*B;
}
return an;
}
signed main(){
//freopen("exam.in","r",stdin);
//freopen("exam.out","w",stdout);
scanf("%lld%lld%lld",&A,&B,&C);
scanf("%lld%lld",&m,&n);
for(int i=1;i<=m;i++){
scanf("%lld",&b[i]);
c[n+i]=b[i];
pc[b[i]]++;
}
for(int i=1;i<=n;i++){
scanf("%lld",&a[i]);
c[i]=a[i];
pa[a[i]]++;
pb[a[i]]++;
}
sort(a+1,a+1+n);
sort(b+1,b+1+m);
sort(c+1,c+1+n+m);
for(int i=1;i<=c[n+m];i++){
sumc[i]=sumc[i-1]+pc[i-1];
pc[i]+=pc[i-1];
}
for(int i=c[n+m];i>=1;i--){
sumb[i]=sumb[i+1]+pb[i+1];
pb[i]+=pb[i+1];
}
for(int i=1;i<=c[n+m];i++){
suma[i]=pa[i-1]+suma[i-1];
pa[i]+=pa[i-1];
}
long long ans=LLONG_MAX;
for(int i=1;i<=c[n+m];i++){
if(check(i)<0)continue;
ans=min(ans,check(i));
}
printf("%lld",ans);
return 0;
}
T4——寻宝
题面
思路
- 还是老套路,先想暴力!直接用bfs跑一遍,每次选择时判断一下能否用魔法代替,而代替肯定用字典序最小的‘a’代替。
- 但这一定会超时,怎么优化呢?发现我们判断一个字母是否需用魔法代替取决于是否为‘a’,是‘a’就不用,不是‘a’则判断是否还有魔法使用次数,可以用动态规划实现,再用bfs搜一遍即可。
- (实在想不到怎么解释,代码中有注释)
code
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=1005;
int n,k;
int dp[N][N];
char a[N][N];
int qsx,qsy,qsm=INT_MIN;
struct edge{
int x,y;
}p[N][N];
bool vis[N][N];
char ans[N*2];
int zx[2]={0,1},zy[2]={1,0};
queue<pair<int,int> >q;
void bfs(){
int x,y,z;
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
vis[i][j]=0;
}
}
while(!q.empty()){
x=q.front().first;
y=q.front().second;
z=x+y-qsm;
q.pop();
if(a[x][y]>ans[z]||x>n||y>n){
continue;
}//如果目前路线的字符比答案数组的字符大,就不需考虑该路线后面的情况
for(int i=0;i<2;i++){
int nx=x+zx[i],ny=y+zy[i];
if(vis[nx][ny]==0&&nx<=n&&ny<=n&&ans[z+1]>=a[nx][ny]){
vis[nx][ny]=1;
ans[z+1]=a[nx][ny];
q.push({nx,ny});
}
}
}
}
signed main(){
scanf("%lld%lld",&n,&k);
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
cin>>a[i][j];
if(a[i][j]!='a'){
dp[i][j]=1;
}else{
dp[i][j]=0;
}//差分数组初始化
}
}
for(int i=1;i<=n;i++){
dp[i][0]=INT_MAX>>1;
dp[0][i]=INT_MAX>>1;
}
dp[1][0]=0;
dp[0][1]=0;
qsm=1;
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
dp[i][j]+=min(dp[i-1][j],dp[i][j-1]);
if(dp[i][j]<=k&&i+j-1>=qsm){
qsm=i+j-1;//记录可到达最远的点的距离
}
}
}
for(int i=1;i<=n;i++){
a[i][n+1]='z'+1;
a[n+1][i]='z'+1;//边界初始化
}
for(int i=1;i<=n*2;i++)ans[i]='z'+1;//答案数组初始化
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
if(dp[i][j]<=k&&i+j-1==qsm){
qsx=i;
qsy=j;
q.push({qsx,qsy});//将次数用玩可以到达的最远的点开是bfs,但最远的点可能有多个,都要压入
}
}
}
if(k==0){
qsx=1,qsy=1;
q.push({qsx,qsy});//如果没法使用魔法,就从起始点开始
}
bfs();
if(k!=0){
for(int i=1;i<=qsm;i++)printf("a");
}else{
cout<<a[1][1];
}
for(int i=2;i<=n+n-qsm;i++)cout<<ans[i];
return 0;
}

浙公网安备 33010602011771号