2025重庆市赛补题

第十三届重庆市大学生程序设计竞赛

Problem F. 赢麻了

问题

给三个数a, b, c,若:

1. a > b ,输出 “Win”

2. c > b ,输出 “WIN”

3. 其他情况,输出 “nowin”

注意大小写即可

代码
#include<bits/stdc++.h>
using namespace std;
int T,n;
int a,b,c;
int main()
{
    scanf("%d",&T);
    while(T--) {
        scanf("%d%d%d",&a,&b,&c);
        if(a>b) printf("Win\n");
        else if(c>b) printf("WIN\n");
        else printf("nowin\n");
    }
    return 0;
}

Problem A. 题目背景是 GPT 给的

问题

给n个数,进行若干次操作,第 i 次操作令 \(a_j=a_j|a_{(i+j)\%n}\) ,问最少几次操作使得这n个数全部相等

思路

乍一看模拟是\(O(n^2)\),但仔细分析

当两个数进行或运算时,二进制下,新的数继承了两个数的所有1,因此每次操作发散的速度是很快的,模拟也能过

代码
#include<bits/stdc++.h>
using namespace std;
int T,n;
int a[200100];
int b[200100];
bool check() {
	for(int i=2;i<=n;i++) {
		if(a[i]!=a[1]) return 1;
	}
	return 0;
}
int main()
{
	scanf("%d",&T);
	while(T--) {
		scanf("%d",&n);
		for(int i=1;i<=n;i++) {
			scanf("%d",&a[i]);
		}
		int ans=1;
		for(ans;check();ans++) {
			for(int j=1;j<=n;j++) b[j]=a[j];
			for(int j=1;j<=n;j++) {
				a[j]=(b[j] | b[(j+ans-1)%n+1]);
				//printf("%d %d %d %d\n",b[j],b[(j+ans-1)%n+1],(b[j] | b[(j+ans-1)%n+1]),a[j]);
			}
			//for(int j=1;j<=n;j++) printf("%d ",a[j]);
			//printf("\n");
		}
		printf("%d\n",ans-1);
	}
	return 0;
}

Problem H. 连接

问题

有2n个球,其中n个球紧贴下边界,标号为1-n(无序),还有n个球分布在上边界,标号1-n(无序且不一定紧贴上边界),现对标号相同的球进行连线,问是否会出现交叉的情况

![](file:///C:/Users/Sonatto/Pictures/Screenshots/%E5%B1%8F%E5%B9%95%E6%88%AA%E5%9B%BE%202025-05-19%20233422.png?msec=1747668879834)

思路

既然下边界都是紧贴的,那我们的思考重心应该放在上边界上。

一开始的想法是从两边往中间找,不难发现分布在左右最外面的如果是不紧贴的,则可以绕过去并转换为最外面是紧贴的的情况;再看如果最外面的是紧贴的,又好像找不出什么性质,不能和内部紧贴的逆序,而和内部不紧贴的逆序与否都无所谓----进一步思考内部不紧贴的和内部紧贴的球之间的关系,发现逆序与否也都不影响,故得到一项关键性质:不紧贴的球可以直接忽略

那剩下的球中,唯一的要求便是不能逆序,否则便会交叉,故找出上下紧贴的球中是否存在逆序即可

代码
#include<bits/stdc++.h>
using namespace std;
int T,n;
int a[200100];
int b[200100];
int c[200100];
map<int,int>Map;
int main()
{
	scanf("%d",&T);
	while(T--) {
		scanf("%d",&n);
		for(int i=1;i<=n;i++) {
			scanf("%d",&a[i]);
		}
		for(int i=1;i<=n;i++) {
			scanf("%d",&b[i]);
		}
		for(int i=1;i<=n;i++) {
			scanf("%d",&c[i]);
			if(c[i]==0) Map[a[i]]=1;
		}
		/*for(int i=1;i<=n;i++) {
			if(c[i]==0) {
				Map[b[i]]=1;
			}
		}*/
		int cnt=0,flag=0;
		for(int i=1;i<=n;i++) {
			if(c[i]==0) continue;
			cnt++;
			while(Map.find(b[cnt])!=Map.end()) cnt++;
			if(a[i]!=b[cnt]) {
				flag=1;
				break;
			}
		}
		if(flag) printf("No\n");
		else printf("Yes\n");
		Map.clear();
	}
	return 0;
}

Problem C. 区域划分

问题

将一个n*n的正方形格子分成n个区块并进行涂色,要求:

• 相同区域的格子的颜色必须相同。

• 相邻区域的格子颜色必须不同。(区域 i 与区域 j 相邻,当且仅当存在一个区域 i 中的格子与一个区域 j 中的格子共享一条边)

求如何划分能使得最少的涂色数最大

思路

最后几十分钟看了一眼这个题,这不一眼四色定理吗直接n>3时输出4种颜色的情况秒了,结果wa,还以为数学不存在了继续死磕L去了

赛后细看,和四色定理的涂色不同的是这道题的相同区块可以不相邻,放地图上就是飞地,所以别脑子一抽题都没读懂就开写

没看懂题解他在说什么,于是考虑让每个区块和尽可能多的方块相邻,这里我就找个规律,发现这么排能让每个区块的每个方块都能和四个不同区块相邻(当然可能会重复),如下,当n=6时:

png

即1-n按顺序排列,接下来每一行往右移i*(i+1)/2格,这样做剩下两行是多余的,随便排都行

代码
#include<bits/stdc++.h>
using namespace std;
int T,n;
int a[2000][2000];
int b[20][20];
int main()
{
	scanf("%d",&T);
	while(T--) {
		scanf("%d",&n);
		for(int i=1;i<=n;i++) {
			for(int j=1;j<=n;j++) {
				int add=max(i*(i+1)/2-1,0);
				a[i][(j+add-1) % n+1]=j;
			}
		}
		for(int i=1;i<=n;i++) {
			for(int j=1;j<=n;j++) {
				printf("%d ",a[i][j]);
			}
			printf("\n");
		}
	}
	return 0;
}

Problem L. 栈与重复

问题

给你一个栈 S ,初始为空,接下来有 n 个操作,每个操作是如下操作之一:

• Push x :x为非负整数。

• Pop :将 S 中栈顶元素弹出,题目保证执行该语句时栈 S 始终非空。

• Repeat :重复执行一遍此前的所有语句(不包含本语句)。

思路

一来就看到这道题,第一眼觉得可能是签到,于是开始死磕了(恼)

显然因为repeat的存在,我们不能模拟,但出现repeat我们只用将ans*2即可,push就加,pop就得减栈顶元素。因为pop的存在,我们还需要想办法把栈给存起来,于是想到了一种奇妙的方法:

push和pop都正常进行(在一个vector中),当遇到了repeat,我们就新开一层vector(初始为空),接下来每次push都在最新的一层里push,如果该层pop完了,我们记录一个pop值,代表在这一层已经往前pop了cnt_i 个,然后往上一层递归进行查找要pop的数(但不进行更改,在最新一层的pop值里记录即可),每一一层都代表了一次repeat。但实现起来太过臃肿,dfs写的依托,码力有所下降(而且为了应对repeat过多的情况还得进行删层的优化,更繁琐了)

看完题解感觉豁然开朗,当栈内元素大于n时忽略repeat即可,居然如此简洁

代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
int n;
char s[200];
deque<int>a;
int MOD=998244353;
signed main()
{
	int x=0;
	int sum=0;
	int flag=0;
	scanf("%lld",&n);
	for(int i=1;i<=n;i++) {
		scanf("%s",&s);
		if(s[1]=='u') {//Push
			scanf("%lld",&x);
			a.push_back(x);
			sum+=x;
			sum%=MOD;
			//if(a.size()>n)a.pop_front();
		} else if(s[1]=='o') {//Pop
			sum-=a.back();
			if(sum<0)sum+=MOD;
			a.pop_back();
		} else if(s[1]=='e') {//Repeat
			sum*=2;
			sum%=MOD;
			if(a.size()<n&&!flag) {
				int size=a.size();
				auto it=a.begin();
				for(int j=1;j<=size;j++,it++) {
					a.push_back(*it);
				}
			} else {
				flag=1;
			}
		}
		/*for(auto var : a) {
			printf("%lld ",var);
		}
		printf("\n");*/
		printf("%lld\n",sum);
	}
	return 0;
}
/*
999
push 1
push 2
re
re
pop
pop
*/

Problem B. 列车

问题

有n个站,m辆列车,给出每辆列车的起点l、终点r、最大容量c,每辆车只能在起点上车,但可以在l-r任意一站下车,1站台有无限个人,问最多能载多少人到n站台

思路

看题解说是最大流最小割,于是粗略学习了下网络流,的确很像,重点在于怎么建图。

从 l -> r 建立一条边,然后若 l, r 中间有新的 l' 则插入并变成下面的形状:l -> l' -> r ->r' 或 l -> l' -> r' -> r(不同的情况取决于r'的大小)

不妨取第一种,则从左往右每条边的权重变为: c, c'+c ,c', 建完图找最大流即可

然后发现这样建图最后会变成一条线,诶这一个个的插入不变成链表了吗,再看权值的计算诶这不变成区间加法了吗,诶这tm线段树或者差分秒了啊

不过注意n的取值范围为1e9,所以要离散化一下,把每个区间加完后找最小割(这里就最小的一条边)即可

在看题解之前还想到了一种贪心的方法,对每辆车按起点进行排序,然后枚举每辆车:

如果其左端点为1,则按 r 升序存进一个map里(map值为其容量,这里map等效于优先队列)。

否则,将其 r 与map里的r进行比较:

若 r 大于 map_begin的r,代表新列车的终点更远,比map_begin更优,于是尽可能抢map_begin的乘客,若抢完了还没满,则重复上一层判定

若 r 等于 map_begin的r,代表终点相同,将map_begin的 c 加上 train[i] 的 c ,进入下一个i

否则,将train[i]插进map里,进入下一个i

但是WA on test 3,再找找是bug还是贪心是错的

代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
int T,n,m;
struct node{
	int l;
	int r;
	int c;
	int people;
}a[200100];
int comp(node X,node Y) {
	if(X.l==Y.l) return X.r<Y.r;
	return X.l<Y.l;
}
map<int,int>f;
int pre[600100];
signed main()
{
	scanf("%lld",&T);
	while(T--) {
		scanf("%lld%lld",&n,&m);
		for(int i=1;i<=m;i++) {
			scanf("%lld%lld%lld",&a[i].l,&a[i].r,&a[i].c);
			f.insert({a[i].l,1});
			f.insert({a[i].r,1});
			//f.insert({a[i].r+1,1});
		}
		sort(a+1,a+m+1,comp);
		int L=a[1].l,R=a[1].r;
		for(int i=2;i<=m;i++) {
			if(a[i].l<=R) {
				R=max(R,a[i].r);
			}
		}
		if(R!=n||L!=1) {
			printf("0\n");
			f.clear();
			continue;
		}
		int cnt=1;
		for(auto &it : f) {
			it.second=cnt;
			cnt++;
		}
		for(int i=1;i<=m;i++) {
			pre[f[a[i].l]]+=a[i].c;
			pre[f[a[i].r]]-=a[i].c;
			//printf("%lld %lld\n",pre[f[a[i].l]],pre[f[a[i].r]+1]);
		}
		int ans=1e18,res=0;
		for(int i=1;i<=f.size()-1;i++) {
			//printf("i=%lld %lld\n",i,pre[i]);
			res=res+pre[i];
			ans=min(ans,res);
		}
		printf("%lld\n",ans);
		for(int i=0;i<=m*3+10;i++) {
			pre[i]=0;
		}
		f.clear();
	}
	return 0;
}
/*
#include<bits/stdc++.h>
#define int long long
using namespace std;
int T,n,m;
struct node{
	int l;
	int r;
	int c;
	int people;
}train[200100];
map<int,int>Map;
int comp(node X,node Y) {
	return X.l<Y.l;
}
signed main()
{
	scanf("%lld",&T);
	while(T--) {
		scanf("%lld%lld",&n,&m);
		for(int i=1;i<=m;i++) {
			scanf("%lld%lld%lld",&train[i].l,&train[i].r,&train[i].c);
		}
		sort(train+1,train+1+m,comp);
		for(int i=1;i<=m;i++) {
			if(train[i].l==1) {
				train[i].people=train[i].c;
				Map[train[i].r]+=train[i].c;
			} else {
				while(!Map.empty()) {
					auto it = Map.begin();
					if((*it).first<train[i].l) {
						Map.erase(it);
						continue;
					}
					if(train[i].r==(*it).first) {
						(*it).second+=train[i].people;
						break;
					} else if(train[i].r>(*it).first) {//如果r_i大于r_map,乘客转移
						if(train[i].c-train[i].people>=(*it).second) {
							train[i].people+=(*it).second;
							Map.erase(it);
						} else {
							(*it).second-=train[i].c-train[i].people;
							train[i].people=train[i].c;
							Map[train[i].r]+=train[i].people;
							break;
						}
					} else {
						if(train[i].people) Map[train[i].r]+=train[i].people;
						break;
					}
				}
			}
		}
		if(Map.find(n)!=Map.end()) printf("%lld\n",Map[n]);
		else printf("%lld\n",0);
		Map.clear();
	}
	return 0;
}*/

Problem J. RGB 树

前面的区域等下周再探索吧~

posted @ 2025-05-20 01:04  Sonatto  阅读(242)  评论(0)    收藏  举报