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;
}
posted @ 2026-09-09 15:19  TNT_THT  阅读(8)  评论(0)    收藏  举报