2023山东省赛部分题解

A.订单

考察签到能力

直接按照时间排序然后看是否会出现负数的情况即可

int n,m;
struct code{
	int a,b; 
	bool operator<(const code&t)const{
		return a<t.a;
	}
}e[N];

void solve(){
    cin>>n>>m;
    for(int i=1;i<=n;i++){
    	int a,b; cin>>a>>b;
    	e[i]={a,b};
    }
    sort(e+1,e+1+n);
    int last=0,now=0;
    bool ok=true;
    for(int i=1;i<=n;i++){
    	auto [a,b]=e[i];
    	now+=(a-last)*m-b;
    	if(now<0){
    		ok=false;
    		break;
    	}
    	last=a;
    }
    cout<<(ok ? "Yes" : "No")<<endl;
    
    return ;
}

 B.建筑公司

考察抽象图论topsort,银牌题

我们发现只有当一个公司的所有条件都满足的时候该公式的值可以加入到我的值当中,这一点类似于所有的边的入度都是0的时候的所以我们考虑和图论有关系,进而

如果一个工程是满足条件的我们就可以取出这个工程的所有奖励,进而判断其他边的状况是不是还有限制,抽象是topsort十分的巧妙

#include <bits/stdc++.h>
using namespace std;
#define int long long
#define enl "\n"
const int N = 1000010,mod=1e9+7;
typedef pair<int,int> PII;
int g,n;
int m[N];
int A[N][2];
vector<PII> B[N];

unordered_map<int,priority_queue<PII,vector<PII>,greater<PII>>> mp; 
queue<int> q;
unordered_map<int,int> have;

void add(int a,int b){
	int&val=have[a];
	val+=b;
	priority_queue<PII,vector<PII>,greater<PII>> &pq=mp[a];
	while(!pq.empty()){
		auto p=pq.top();
		if(p.first>val) break;
		pq.pop();
		if(--m[p.second]==0) q.push(p.second); 
	}
}
void solve(){
	cin>>g;
	for(int i=1;i<=g;i++)  cin>>A[i][0]>>A[i][1];
	
	cin>>n;
	for(int i=1;i<=n;i++){
		cin>>m[i];// 表示有m个限制条件类似与topsort中的边数的限制
		for(int j=1;j<=m[i];j++){
			int a,b; cin>>a>>b;
			mp[a].push({b,i});
		}
		int k; cin>>k;
		for(int j=1;j<=k;j++){
			int c,d; cin>>c>>d;
			B[i].push_back({c,d});
		}
	}
	
	// 加入入度为0的点
	for(int i=1;i<=n;i++) if(!m[i]) q.push(i);
	
	for(int i=1;i<=g;i++) add(A[i][0],A[i][1]);
	
	int ans=0;
	while(!q.empty()){
		int t=q.front(); q.pop();
		for(auto&[a,b]:B[t]) add(a,b);
		ans++;
	}
	cout<<ans<<endl;
    
    return ;
}

D.负重越野

考察二分,签到题

题目明显的有暗示使用二分的迹象,我们观察到队给定式子变形之后的速度就是当前这个人的速度+他自己的体重-不满足速度的人,所以我们可以直接二分速度然后对于满足的按照v+w排序,不满足的按照w排序,这样我们按照最厉害的背最重的简单贪心即可

int t,n,m;
PII a[N];
bool check(int mid){
	vector<int> x,y;
	for(int i=1;i<=n;i++){
		auto [v,w]=a[i];
		if(v>=mid) x.push_back(v+w);
		else y.push_back(w);
	}
	if(x.size()<y.size()) return false;
	sort(x.begin(),x.end(),greater<int>());
	sort(y.begin(),y.end(),greater<int>());
	for(int i=0;i<y.size();i++){
		if(x[i]-y[i]<mid) return false;
	}
	return true;
}

void solve()
{
    cin>>n;
	for(int i=1;i<=n;i++){
		int v,w; cin>>v>>w;
		a[i]={v,w};
	}    
	int l=1,r=1e9;
	while(l<r){
		int mid=l+r+1>>1;
		if(check(mid)) l=mid;
		else r=mid-1;
	}
	cout<<l<<endl;
    return ;
}

E.数学问题

考察数字变换,银牌题

一开始看到这个题目的框架我们可能会感觉似曾相识,但是我们发现加的数x不是固定的我们发现有点棘手,我们可以先找找性质,明显可以发现进行操作1之后进行操作2是无效的,无论位置,所以我们可以发现结果一定是先进行操作1,然后接着进行操作2,问题就在于进行多少次,怎么判断,我们发现一个这个数最多进行logn次除法,logn次乘法所以我们时间复杂度是可以的,接着我们如何判断是不是到达了有解的位置,我们先不考虑操作2,我们发现多次进行操作1之后的得到一个多项式也就是k进制的式子,由此我们可以得知再进行g次操作之后这个数所在的是一个范围由此答案就浮现出来了

void solve()
{
	int n,k,m,a,b;
	cin>>n>>k>>m>>a>>b;
	if(n%m==0){
		cout<<0<<endl;
		return ;
	}        
	if(k==1){
		cout<<-1<<endl;
		return ;
	}
	int ans=3e18,cost=0;
	while(true){
		__int128 p=1;
		int base=n%m;
		for(int i=0;;i++){
			int need=(m-base)%m;//要的是差值
			if(need<p){
				ans=min(ans,cost+i*a);
				break;
			}
			p*=k;
			base=base*k%m;
		}
		if(!n) break;
		n/=k;
		cost+=b;
	}
	cout<<ans<<endl;
    return ;
}

G.匹配

考察数学公式变形,签到题

对于题目给定的式子稍微变形之后就是i-a_{i}是可以连接再一起的,考虑到数很大我们用map嵌套vector,然后对vector排序按照大到小两两组合是否大于0加起来即可

map<int,vector<int>> mp;
int n;
int a[N];

void solve(){
    mp.clear();
	cin>>n;
	for(int i=1;i<=n;i++){
		cin>>a[i];
		mp[i-a[i]].push_back(a[i]);
	}   
	
	int sum=0; 
	for(auto&[v,w]:mp){
		sort(w.begin(),w.end(),greater<int>());
		for(int i=0;i<w.size();i+=2){
			if(i+1<w.size()){
			  int x=w[i]+w[i+1];
			  if(x>0) sum+=x;
			  else break;
			}
		}
	}
	cout<<sum<<endl;
    
    return ;
}

I.三个筛子

签到题,语言暴力题

按照要求,枚举三只骰子的点数即可

#include <bits/stdc++.h>
using namespace std;

// 红色点数
int X[7] = {0, 1, 0, 0, 4, 0, 0};
// 黑色点数
int Y[7] = {0, 0, 2, 3, 0, 5, 6};

int main() {
    int x, y; scanf("%d%d", &x, &y);
    for (int i = 1; i <= 6; i++) for (int j = 1; j <= 6; j++) for (int k = 1; k <= 6; k++)
        if (X[i] + X[j] + X[k] == x && Y[i] + Y[j] + Y[k] == y) {
            printf("Yes\n");
            return 0;
        }
    printf("No\n");
    return 0;
}

J.不是一道路径查询问题

二进制连通性问题,金牌题

题目数据范围特别的到,然后告诉我们要&,考虑是不是需要使用二进制的方式来解决,我们

看看怎么拆位解决,我们试着来拆位置,考虑对于V如果这个位置是1那么对面的这个数的位置应该也是1才嫩满足,如果V的这个位置是0的话我们发现只要前缀和V的一样或者比V大同时这个位置都是1的话就很好,所以我们就就此看V为0的位置然后看那些是满足的建图,看是不是在一个联通块里面,然后来判断就好了

int n,m,q,V;
TUP a[N];
PII b[N];
vector<int> g[N];
bitset<N> st;
bool ans[N];
int id[N];

void add(int a,int b){
	g[a].push_back(b);
	g[b].push_back(a);
}

void bfs(int St){
	queue<int> q;
	q.push(St);
	while(!q.empty()){
		int t=q.front(); q.pop();
		if(st[t]) continue;
		st[t]=true;
		id[t]=St;
		for(auto&v:g[t]){
			if(st[v]) continue;
			q.push(v);
		}
	}
}

void check(int i){
	for(int i=1;i<=n;i++) g[i].clear(),st[i]=false;	
	int S=V;
	if(i>=0) S=(V>>i|1)<<i;// 表示除以这个位置再按位与1然后乘回去
	for(int i=1;i<=m;i++){
		auto [u,v,w]=a[i];
		if((w&S)>=S){
			add(u,v);
			add(v,u);
		}
	}
	for(int i=1;i<=n;i++) if(!st[i]) bfs(i);
	
	for(int i=1;i<=q;i++){
		auto [u,v]=b[i];
		if(ans[i]) continue;
		if(id[u]==id[v]) ans[i]=true;
	}
	
}
void intn(){
	for(int i=59;i>=0;i--){
		if(!(V>>i&1)){
			check(i);
		}
	}
	check(-1);
}
void solve()
{
	cin>>n>>m>>q>>V;
	for(int i=1;i<=m;i++){
		int u,v,w; cin>>u>>v>>w;
		a[i]={u,v,w};
	}        
	
	for(int i=1;i<=q;i++){
		int u,v; cin>>u>>v;
		b[i]={u,v};
	}
	
	intn();
	
	for(int i=1;i<=q;i++){
		cout<<(ans[i] ? "Yes" : "No")<<endl;
	}
    return ;
}

L.曲尺

模拟,铜牌题?

我们考虑构造正方形,如何构造一个正方形呢考虑不断往四个方向扩散的方式哪里可以扩散就扩散到哪里去这样我们就可以构造出来一个满足题目条件的包围的图像

void solve(){
    cin>>n>>x>>y;
	cout<<"Yes\n"<<n-1<<endl;
	int U=x,D=x,L=y,R=y,k=1;
	while(k<n){
		if(U>1 and L>1){
			U--,L--;
			cout<<U<<' '<<L<<' '<<k<<' '<<k<<endl;
		}
		else if(U>1 and R<n){
			U--,R++;
			cout<<U<<' '<<R<<' '<<k<<' '<<-k<<endl;
		}
		else if(L>1 and D<n){
			L--,D++;
			cout<<D<<' '<<L<<' '<<-k<<' '<<k<<endl;
		}
		else{
			R++,D++;
			cout<<D<<' '<<R<<' '<<-k<<' '<<-k<<endl;
		}
		k++;
	}   
    return ;
}

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值