河南萌新联赛2024第(五)场:信息工程大学
2024.8.14 13:00————17:00
过题数9/11
补题数10/11
- 日历游戏
- 学生分组
- 小美想收集
- 区间问题1
- 哥德巴赫猜想
- 小美想跑步
- 爬楼梯
- 区间问题2
- 小美想打音游
- 平方根
- 小美想游泳
A - 日历游戏
补题过的,赛事用的dfs,理论上也能过,但我思维不够严谨,最好还是找规律。
题解:
给出一个日期,可以对这个日期的天数加一或者月份加一,先到达2024.08.01的人获胜,Alice先操作,问他有没有必胜策略?
可以知道,8.1是奇数,一定是由偶数日操作一个后过来,特判11月和9月的情况,操作一次后不会改变奇偶性。
代码:
#include<bits/stdc++.h>
using namespace std;
#define int long long
void solve() {
int x,y,z;
cin >> x >> y >> z;
if(y == 9 && z == 30) cout << "YES" << endl;
else if(y == 11 && z == 30) cout << "YES" << endl;
else if((y+z)%2 == 0) cout << "YES" << endl;
else cout << "NO" << endl;
}
signed main() {
int t;
cin >> t;
while(t--) {
solve();
}
return 0;
}
B - 学生分组
题解:
n个元素,要求序列中所有元素在[l,r]之间,每次可以使其中一个数+1,另一个数-1,问最少要多少次可以满足要求。
先判断这组序列能否满足要求,如果其过小或过大均不可。在已经知道它可以满足的情况下,判断需要减小的数是多少,需要增大的数是多少,取较大值就是最少需要操作的次数。
代码:
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N = 1e6+10;
int n,l,r;
int a[N];
signed main() {
cin >> n;
int sum = 0;
for (int i = 1; i<= n; i++) {
cin >> a[i];
sum += a[i];
} cin >> l >> r;
if(l*n > sum || r*n < sum) {
cout << -1 << endl;
return 0;
}
int ans1 = 0;
int ans2 = 0;
for (int i = 1; i <= n; i++) {
if(a[i] < l) {
a[i] = l-a[i];
ans1 += a[i];
}
else if(a[i] > r) {
a[i] = a[i]-r;
ans2 += a[i];
}
else a[i] = 0;
}
cout << max(ans1,ans2) << endl;
return 0;
}
C - 小美想收集
最小生成树原来可以解决这类问题,但我还不太会最小生成树,再学一下吧。
题解:
代码:
D - 区间问题1
题解:
Alice有n个数字,她可以进行俩种操作,区间修改或单点查询。
线段树版子题。
代码:
#include<bits/stdc++.h>
#define N 500005
#define endl '\n'
#define _for(i,a,b) for(int i=a;i<b;i++)
using namespace std;
typedef long long ll;
struct Node{
int l,r,num;
}T[N*4];
int a[N]; ll res;
void build(int i,int l,int r){//µÝ¹é½¨Ê÷
T[i].l=l;T[i].r=r;
if(l==r){//Èç¹ûÕâ¸ö½ÚµãÊÇÒ¶×Ó½Úµã
T[i].num=a[r];
return ;
}
int mid=(l+r)>>1;
build(i*2,l,mid);//·Ö±ð¹¹Ôì×ó×ÓÊ÷ºÍÓÒ×ÓÊ÷
build(i*2+1,mid+1,r);
T[i].num= 0;//¸Õ²ÅÎÒÃÇ·¢ÏÖµÄÐÔÖÊreturn ;
}
void add(int i,int l,int r,int k){
if(T[i].l>=l && T[i].r<=r){//Èç¹ûÕâ¸öÇø¼ä±»ÍêÈ«°üÀ¨ÔÚÄ¿±êÇø¼äÀïÃ棬½²Õâ¸öÇø¼ä±ê¼Çk
T[i].num+=k;
return ;
}
if(T[i*2].r>=l)
add(i*2,l,r,k);
if(T[i*2+1].l<=r)
add(i*2+1,l,r,k);
}
void search(int i,int dis){
res+=T[i].num;//һ·¼ÓÆðÀ´
if(T[i].l==T[i].r)
return ;
if(dis<=T[i*2].r)
search(i*2,dis);
else
search(i*2+1,dis);
}
int main(){
ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
int n,m;
cin>>n;
_for(i,1,n+1) cin>>a[i];
build(1,1,n);
cin >> m;
while(m--){
int k; cin>>k;
if(k==1){//
int l,r,ad; cin>>l>>r>>ad;
add( 1,l,r,ad );
}
else {
int pos; cin>>pos; res=0;
search(1,pos);
cout<<res<<endl;
}
}
return 0;
}
//这是别人的一个板子,赛时交的是这个。
有lazy标记板
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N= 1e6+10;
int a[1000000];
int n;
int tree[4*N];
//这个点的区间和
void build (int p ,int l, int r) {
//编号,左右区间
if(l == r) {
tree[p] = a[l];
//只剩一个数字
return ;
}
int mid = (l+r)/2;
build(p*2,l,mid);
build(p*2+1,mid+1,r);
tree[p] = tree[p*2] + tree[p*2+1];
//节点所代表的区间和
}
int lazy[4000100];
void pushdown(int p,int l,int r) {
int mid = (r+l)/2;
lazy[p*2] += lazy[p];
lazy[p*2+1] += lazy[p];
//俩个儿子的lazy赋值
tree[p*2] += lazy[p] * (mid-l+1);
tree[p*2+1] += lazy[p] * (r-(mid+1)+1);
//更改俩个儿子区间和
lazy[p] = 0;
//俩个儿子已经知道了,我就不用了
}
void change(int p,int l,int r, int x,int y,int num) {
if(x <= l && r <= y) {
tree[p] += num*(r-l+1);
lazy[p] += num;
return ;
}
if(lazy[p] != 0) {
pushdown(p,l,r);
}
int mid = (l+r)/2;
if(x <= mid) change(p*2,l,mid,x,y,num);
if(y > mid) change(p*2+1,mid+1,r,x,y,num);
tree[p] = tree[p*2] + tree[p*2+1];
//修改后再次更新区间和的值
}
int calc(int p,int l,int r, int x) {
if(l == r) {
//完整包含在区间内,直接返回和
return tree[p];
}
if(lazy[p] != 0) pushdown(p,l,r);
int mid = (l+r)/2;
//完整的在这个区间左边
if(x <= mid) return calc(p*2,l,mid,x);
else return calc(p*2+1,mid+1,r,x);
//完整的在这个区间右边
// return calc(p*2,l,mid,x,mid) + calc(p*2+1,mid+1,r,mid+1,y);
}
signed main() {
cin >> n;
for (int i = 1; i <= n; i++) cin >> a[i];
build(1,1,n);
//1号节点代表的是1到n
int q;
cin >> q;
while(q--) {
int x;
cin >> x;
if(x == 1) {
int l,r,d;
cin >> l >> r >> d;
change(1,1,n,l,r,d);
}
else {
int p;
cin >> p;
// cout << tree[p] << endl;
cout << calc(1,1,n,p) << endl;
//查询区间y到z的和
}
}
return 0;
}
//比赛的时候我的板子好像出了一点问题,现在改过来了。
E - 哥德巴赫猜想
没懂这题的点在哪里,暴力跑过的。
题解:
t组数据,每行包含一个正奇数n,要求将它拆成三个质数,且和等于这个奇数,要求第二个质数在第一个质数尽量小的情况下尽量小。
先判断素数,然后暴力跑第一个和第二个质数即可。
代码:
#include<bits/stdc++.h>
using namespace std;
#define int long long
int t;
bool a[50010];
signed main() {
cin >> t;
for (int i= 2; i <= 50005; i++) {
for (int j = 2; j*j <= i; j++) {
if(i%j == 0) {
a[i] = 1;
break;
}
}
}a[1] = 1,a[2] = 0;
while(t--) {
bool st = true;
int n;
cin >> n;
for (int i = 1; i <= n; i++) {
if(!st) break;
if(a[i]) continue;
for (int j = 1; j <= n-i-2; j++) {
if(!st) break;
if(a[j]) continue;
if(!a[n-i-j]) {
cout << i << ' ' << j << ' ' << n-i-j << endl;
st = false;
break;
}
}
}
if(st) cout << -1 << endl;
}
return 0;
}
F - 小美想跑步
一道比较简单的Dijkstra变形题
题解:
n个打卡点,m条单行道,要求从起点1跑一个点跑回家,求跑完每一个点之后的最短总路程。
从1到其它各点的最短总路程较简单,跑一个正常的Dijkstra。然后把所有的单行道的方向反一下,在跑一次Dijkstra,相当于从它们每个点跑回家的最短路程,求和即可。
代码:
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define PII pair<int,int>
const int N=1e6+10;
int n, m;
int s,t;
vector<PII>e[N];
int dist[N];
int dp[N];
void dij(int x){
priority_queue<PII,vector<PII>,greater<PII> >q;
q.push({0,x});
dist[x]=0;
while(q.size()){
int y=q.top().second;
q.pop();
for(int i=0;i<e[y].size();i++){
if(dist[e[y][i].first]>dist[y]+e[y][i].second){
dist[e[y][i].first]=dist[y]+e[y][i].second;
q.push({dist[e[y][i].first],e[y][i].first});
}
}
}
}
void dfs(int x){
for(int i=0;i<e[x].size();i++){
if(e[x][i].first==1){
dp[x]=e[x][i].second;
continue;
}
dfs(e[x][i].first);
dp[x]=min(dp[x],dp[e[x][i].first]+e[x][i].second);
}
return ;
}
void solve(){
cin>>n>>m;
memset(dist,0x3f,sizeof dist);
memset(dp,0x3f,sizeof dp);
for(int i=0;i<m;i++){
int x,y,z;
cin>>x>>y>>z;
e[x].push_back({y,z});
}
dij(1);
for(int i=0;i<e[1].size();i++){
dfs(e[1][i].first);
}
int ans=0;
for(int i=2;i<=n;i++){
// cout<<dist[i]<<" "<<dp[i]<<endl;
ans+=(dist[i]+dp[i]);
}
cout<<ans<<endl;
}
signed main(){
ios::sync_with_stdio(false);
cin.tie(0);
int T=1;
// cin>>T;
while(T--){
solve();
}
return 0;
}
G - 爬楼梯
题解:
你在爬楼梯,每次可以爬1——3个台阶,有多少种不同的方法可以爬到第n层台阶。
dp。能跑到当前台阶的方案数是能跑到前1,2,3台阶的方案数的总和。
代码:
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int mod = 1000000007;
int a[1000010];
signed main() {
int n;
cin >> n;
a[1] = 1;
a[2] = 2;
a[3] = 4;
for (int i = 4; i <= n; i++) {
a[i] = a[i-1]+a[i-2]+a[i-3];
a[i] = a[i]%mod;
}cout << a[n];
return 0;
}
H - 区间问题2
题解:
区间查询,易超时。
RMQ算法。
代码:
#include<bits/stdc++.h>
#define MAX_N 1000100
#define MAX_M 30 // 2^25 > 100007
using namespace std;
int S[MAX_N],Log[MAX_N];
int st[MAX_N][MAX_M];
int n,m;
void init()
{
for(int i=1;i<=n;i++)
st[i][0] = S[i];
for(int j=1;(1<<j)<=n;j++)
for(int i=1;i+(1<<(j-1))<=n;i++)
st[i][j] = max(st[i][j-1],st[i+(1<<(j-1))][j-1]);
}
void getLog()
{
Log[1] = 0;
for(int i=2;i<=n+1;i++)
Log[i] = Log[i/2]+1;
}
int Query(int l,int r)
{
int k = Log[r-l+1];
return max(st[l][k],st[r-(1<<k)+1][k]);
}
//这里是快读
inline int read()
{
int x=0,f=1;char ch=getchar();
while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
return x*f;
}
int main()
{
int l,r;
n = read();
for(int i=1;i<=n;i++)
S[i] = read();
getLog();
init();
m = read();
for(int i=1;i<=m;i++){
l = read();
r = read();
printf("%d\n",Query(l,r));
}
return 0;
}
I - 小美想打音游
这题也很无趣其实,一个三分板子题。
题解:
n个数字,需要将所有数字变成同一个,所需要的魔力是这个数字与每个数字的差值和,最后再用一个魔力结尾。输出所需消耗的魔力总值最小是多少。
三分枚举答案。
代码:
#include<bits/stdc++.h>
using namespace std;
#define int long long
int n;
const int N = 1e6+10;
int c[N];
int dis(int x) {
int res = 0;
for (int i = 1; i <= n; i++) {
res += abs(c[i]-x);
}return res;
}
signed main() {
cin >> n;
int mi = 1e7,ma = 0;
for (int i = 1;i <= n; i++){
cin >> c[i];
mi = min(mi,c[i]);
ma = max(ma,c[i]);
}
int l = mi,r = ma;
int f1=1e9,f2=1e9;
while(l +10 < r) {
int lm = l + (r-l)/3;
int rm = r - (r-l)/3;
f1 = dis(lm),f2 = dis(rm);
if(dis(lm) <= dis(rm)) r = rm-1;
else l = lm+1;
}
int res = min(f1,f2);
for (int i = l; i <= r;i++) {
res = min(res,dis(i));
}cout << res+1 << endl;
return 0;
}
//这是我赛时的做法,给出他们的做法,挺奇特的都。
#include<bits/stdc++.h>
using namespace std;
#define int long long
int n;
const int N = 1e6+10;
int c[N];
signed main() {
cin >> n;
for (int i = 1;i <= n; i++){
cin >> c[i];
}sort(c+1,c+1+n);
int sum = c[n/2];
int res = 0;
for (int i = 1;i <= n; i++) {
res += abs(c[i]-sum);
}cout << res+1 << endl;
return 0;
}
//尽量往最中间的数字靠近是吧,就能动尽量少的魔力了。
J - 平方根
题解:
签到题。
代码:
#include<bits/stdc++.h>
using namespace std;
#define int long long
int t;
signed main() {
cin >> t;
while(t--) {
int n;
cin >> n;
int m= sqrt(n);
cout << m << endl;
}
return 0;
}
K - 小美想游泳
Dijkstra竟然能跑最大值,离了个大谱
题解:
代码:
#include<iostream>
#include<cstring>
#include<algorithm>
#include<queue>
using namespace std;
#define int long long
typedef pair<int,int> PII;
const int INF = 0x3f;
//比起0x7fffffff,0x3f3f3f3f可以避免正无穷溢出变成负数的情况
//...没看懂,记住吧,就这样
const int N = 2e6+10;
const int M = 2e6+10;
int n,m;
struct ty{
int to,w,next;
}edge[2*M];
int head[2*N],dis[2*N],vis[2*N];
int cnt = 0;
void init() {
memset(vis,0,sizeof vis);
memset(dis,INF,sizeof dis);
memset(head,-1,sizeof head);
}
void add_edge(int u,int v,int w) {
cnt++;
edge[cnt].to = v;
edge[cnt].w = w;
edge[cnt].next = head[u];
head[u] = cnt;
}
void dij(int x) {
priority_queue<PII,vector<PII>,greater<PII>>pq;
pq.push({0,x});
dis[x] = 0;
while (!pq.empty()) {
x = pq.top().second;
//现在的点
pq.pop();
if(vis[x])continue;
vis[x] = 1;
for (int i = head[x]; i != -1; i = edge[i].next) {
if(!vis[edge[i].to]) {
if(dis[edge[i].to] > max(dis[x] , edge[i].w)) {
//其它都直接照搬板子了,就这里改了一下,如果你直接跑过去的最大值大于往这绕一下的最大值,就往这绕一下吧。
dis[edge[i].to] = max(dis[x] , edge[i].w);
pq.push({dis[edge[i].to],edge[i].to});
}
}
}
}
}
signed main() {
init();
cin >> n >> m;
for (int i = 0; i < m; i++) {
int a,b,c;
cin >> a >> b >> c;
add_edge(a,b,c);
add_edge(b,a,c);
}
int s,t;
cin >> s >> t;
dij(s);
cout << dis[t] << endl;
return 0;
}