2025 暑假集训 Day3

2025.8.6

Day 3 比赛,题目难度大概是黄蓝绿蓝(?)

估分 \(100+30+100+0=230\),实际 \(100+20+75+25=220\),排名 \(8/33\)

A 和谐关系

题目描述

\(n\) 个人想要理头发,但是由于出题人的一些执念,有一些人可以给自己理头。

现在给你 \(m\) 组和谐的关系,每组关系表示第 \(x\) 个人帮助第 \(y\) 个人理头发(\(x\) 可以等于 \(y\),此时第 \(x\) 个人可以给自己理头) 。

对于每一组和谐关系,都有一个消耗时间 \(w_{x,y}\),表示第 \(x\) 个人帮助第 \(y\) 个人理头需要花费的时间。

由于出题人的另一些执念,这种和谐是相互的,也就是说如果第 \(y\) 个人帮助第 \(x\) 个人理头,也将花费 \(w_{x,y}\) 个单位时间。

由于出题人就要溢出的执念,还要注意的是:

  • 每个人帮助别人理头的时候必须要求自己已经被理过头了。
  • 对于 \(m\)\(x,y\) 中不保证 \(x,y\) 不重复出现。
    求解这 \(n\) 个人理头的最短时间和,对于每个数据都保证 \(n\) 个人都可以理头。
tickets.in
2 3
1 1 1
2 2 1
1 2 3

tickets.ans
2

数据范围:\(n \le 2e3,m \le 4e6,w_{x,y} \le 200\)

思路

题解似乎有点看不懂,写一下赛时的思路吧。

首先规定每个人不能给自己理头。考虑一个的虚拟点 \(s=n+1\),这个点可以给其他人理头,但是不需要自己先被理头。

对于每一条边 \((x,y,w)\),若 \(x=y\) 则建立一条 \(s - x\) 权值为 \(w\) 的双向边,否则就建立一条 \(x - y\) 权值为 \(w\) 的双向边,然后根据建的新图(包括虚拟点 \(s\))跑一边最小生成树就是答案了。

最小生成树应该用 Prim 会快一点,但是场上忘记 Prim 咋写了,写了个 Kruskal 过了。

代码

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<ll,int> pii;
constexpr int N=2e3+7;
int s;  //虚拟点 
int n,m,g[N][N];
ll dis[N];
bool vis[N];
inline void addedge(int u,int v,int w)
{
	if(g[u][v]==0) g[u][v]=g[v][u]=w;
	else g[u][v]=g[v][u]=min(g[u][v],w);
}
inline int read()
{
	int w=0;
	char c=getchar();
	while(c<'0'||c>'9') c=getchar();
	while(c>='0'&&c<='9')
	{
		w=(w<<3)+(w<<1)+(c^48);
		c=getchar();
	}
	return w;
}
int fa[N];
int find(int u)
{
	return fa[u]==u?u:fa[u]=find(fa[u]);
}
struct edge{int u,v,w;};
vector<edge> e;
inline int kruskal()
{
	sort(e.begin(),e.end(),[&](edge A,edge B){return A.w<B.w;});
	int ans=0,cnt=0;
	for(edge ee:e)
	{
		int u=ee.u,v=ee.v,w=ee.w;
		if(find(u)!=find(v))
		{
			fa[find(u)]=find(v);
			ans+=w;
			cnt++;
		}
	}
//	cerr<<cnt;
	return ans;
}
int main()
{
	freopen("tickets.in","r",stdin);
	freopen("tickets.out","w",stdout);
	n=read(); m=read();
	s=n+1;
	for(int i=1;i<=s;i++) fa[i]=i;
	int u,v,w;
	for(int i=1;i<=m;i++)
	{
		u=read(); v=read(); w=read();
		if(u==v) addedge(u,n+1,w);
		else addedge(u,v,w);
	}
	for(int i=1;i<=s;i++)
		for(int j=i+1;j<=s;j++)
			if(g[i][j]>0) e.push_back({i,j,g[i][j]});
	printf("%d",kruskal());
	return 0;
}
/*
虚拟一个点n+1,然后把这个n+1和可以给自己理头发的人连接
然后跑kruskal(prim?) 
*/

B 小 P 别急

题目

小 P 来到了异世界,遇到了一群小怪,所有的小怪都有自己的名字 Name 和血量 HP,会发生以下事件:

  • op=1 小 P 遇到了一个小怪,名为 Name 血量是 HP。
  • op=2 小 P 急了,使用了战技对所有的小怪造成 \(P(\le 10^{12})\) 的伤害,即所有小怪损失 点血量。此时血量小于等于 \(P\) 的小怪死亡,将不会再受到伤害或被斩杀。
  • op=3 小 P 急疯了,使用了终结技斩杀此时血量最多的小怪(如果有多个野怪生命值相同,小 会选择最后出现的小怪进行斩杀)这时你需要输出被斩杀的小怪的名字和最后的血量。

注意: 关于操作三,如果没有小怪时接到这条命令的时候你需要输出:Air 0(即野怪名称是 Air 斩杀前的血量是 0

【样例输入】

20
3
1 cgztcl 814667829
1 crptql 959046213
1 crpmm 1352794853
1 thescandle 918235041
2 71
2 90
1 hd 111316588
1 andlible 1281937187
2 2
1 s 1384052255
3
2 8
3
1 nevermind 1482591351
3
2 0
3
3
2 4

【样例输出】
Air 0
s 1384052255
crpmm 1352794682
nevermind 1482591351
andlible 1281937177
crptql 959046042

思路

校 OJ 里面题目中的“异世界”有一个链接到 B 站的 Minecraft 视频是什么意思

考场上有一个线段树的离线做法:

  • op=1 插入一个数
  • op=2 把一定范围内的数全部减去多少
  • op=3 区间求最大值+单点修改

写的思路比较乱,而且线段树代码写挂了

正解:优先队列(其实线段树也可以)

如果使用优先队列的暴力做法的话,应该是在每一次释放战技后把优先队列的怪物拿出来,扣除生命值之后把剩下的生命值 \(>0\) 的再丢回去,但是这样显然会超时。

如何优化?假设有一个魔物在 \(t_1=12\) 时带着 \(s\) 的生命值加入了战场,在 \(t_2=18\) 时剩余的生命值为 \(s^\prime\),那么根据前缀和的思想,有:

\[s^\prime=s-S(12 \sim 18)=s-(S(1 \sim 18)-S(1 \sim 11)) \\ =s-S(1 \sim 18) {\color{red}+}S(1 \sim 11) \]

(其中 \(S(a \sim b)\) 表示 \(a \sim b\) 时间段内受到的伤害)

所以我们可以开一个变量 \(tot\) 记录战技造成的总伤害,在怪物加入的时候给他加上 \(tot\) 的生命值,在释放战技时仅作一个标记,释放终结技时结算战技造成的伤害。这时用优先队列就很好做了。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<ll,int> mob;
constexpr int N=1e6+7;
string s[N];
priority_queue<mob,vector<mob>,less<mob>> q;
int Q,idx=0;
ll tot=0;  //战技造成的总伤害 
inline void meet()
{
	string s1;ll hp;
	cin>>s1>>hp;
	++idx;
	s[idx]=s1;
	q.push({hp+tot,idx});
}
inline void zhanji()  //战技 
{
	ll hp;
	cin>>hp;
	tot+=hp;
}
inline void zhongjieji()  //终结技 
{
	while(!q.empty())
	{
//		cerr<<"|"<<q.top().first<<' '<<s[q.top().second]<<'\n';
		if(q.top().first-tot<=0) q.pop();
		else break;
	}
//	cerr<<'\n';
	if(q.empty())
	{
		cout<<"Air 0\n";
		return;
	}
	else
	{
		cout<<s[q.top().second]<<' '<<q.top().first-tot<<'\n';
		q.pop();
	}
}
int main()
{
	freopen("mega.in","r",stdin);
	freopen("mega.out","w",stdout);
	cin>>Q;
	int op;
	while(Q--)
	{
		cin>>op;
		if(op==1) meet();  //操作1遇见怪物 
		else if(op==2) zhanji();  //操作2群攻
		else zhongjieji();  //操作3单攻 
	}
	return 0;
}
/*
在遇到小怪的时候,让小怪的HP先加上之前战技造成的伤害
假设一个生命值为HP的小怪是在t=12出现的,在t=18时这个怪的血量应该是
HP-(12~18时的总伤害)=HP-(1~18的总伤害-1~11的总伤害)=HP-(1~18总伤害)+(1~11总伤害) 
*/

C 森林冰火人

题目

在4399有一对森林冰火人,他们有 \(n(n \le 200)\) 个任务需要完成。

每一个任务只能由其中一个人完成,给冰人(简称 A 人)完成的时间是 \(a_i\) ,相应的,火人(简称 B 人)的完成时间是 \(b_i(a_i,b_1) \le 200\)

两个人可以同时工作,现在出题人想要知道他们完成所有的任务至少需要多久。

不会有人赛时去玩森林冰火人吧(原题中就是这么写的)

【样例输入】
3
5 10
6 11
7 12
【样例输出】
12

思路

小清新 DP 题,糊一下考场思路: 但是考场上写了个基于记忆化搜索的 DP 喜提 TLE 75pts

设状态 \(dp(i,suma)\) 表示现在考虑到第 \(i\) 个任务,A 人工作 \(suma\) 时间时 B 人的最小工作时间 \(sumb\)

决策很明显是有两个,一个任务要么给 A 人做要么给 B 人做,所以状态转移方程很简单,就是一个 \(dp[i][j]=\min(dp[i-1][j-a[i]],dp[i-1][j]+b[i])\)\(i(1 \le i \le n)\) 枚举任务,\(j(0 \le j < a_ib_i)\) 枚举 A 的工作时间 \(suma\),最后再找出一个用时最少的就好了。时间复杂度是 \(O(na_ib_i)\) 的。

可以用滚动数组把第一维干掉但是我懒得写了

代码

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
constexpr int N=207;
constexpr int M=40007; 
int n,a[N],b[N],ans=INT_MAX;
int dp[N][M];  //dp[i][suma]=min(sumb) dp[i][suma]表示现在考虑到第i个任务,A人工作suma时间时最小的sumb 
int main()
{
	freopen("divide.in","r",stdin);
	freopen("divide.out","w",stdout);
	scanf("%d",&n);
	memset(dp,0x3f,sizeof dp);
	for(int i=1;i<=n;i++) scanf("%d%d",&a[i],&b[i]);
	for(int i=0;i<M;i++) dp[0][i]=0;
	for(int i=1;i<=n;i++)
	{
		for(int j=0;j<M;j++)
		{
			if(j>=a[i]) dp[i][j]=min(dp[i-1][j-a[i]],dp[i-1][j]+b[i]);
			else dp[i][j]=dp[i-1][j]+b[i];
		}
	}
	for(int j=0;j<M;j++) ans=min(ans,max(dp[n][j],j));
	printf("%d",ans);
	return 0;
}
/*
冰人->A人 火人->B人 
决策:一个任务要么给A人要么给B人
dp[i][j]=min(dp[i-1][j-a[i]],dp[i-1][j]+b[i]) 
*/ 

D 小罐跳舞

题目

小罐在看了这个视频之后感觉很快乐,于是决定和好朋友们一起跳舞。

上面这段话和题目一点关系都没有

我们都知道,如果知道了 \(A \times B \times R=C \times R\),那么可以推出 \(A \times B = C\),当然,前提是 \(A,B,R,C\) 都是正整数

我们还知道,如果一个事情做一次可能得不到正确答案,但是多做几次就有很大可能能够得到了。

上面这两段话和题目……有很大关系,这是小罐作为一名蒟蒻给你的提示

一天,小罐遇到了三个矩阵(行数和列数均为 ): ,它们挡住了小罐摆烂的路

小罐很苦恼,这时神出现了,祂告诉小罐,只要回答 \(A \times B=C\) 是否正确,自己就可以帮忙移走这三个矩阵。

神觉得这对于小罐太简单了,于是祂把 \(N\) 的范围确定为 \([1,1000]\)

神觉得这对于小罐还是太简单了,于是祂可能会给出多次询问,但是询问最多五次,因为第六次神就会跌落神坛

小罐忙着看,于是他给了你上文的两个提示,想让你帮他解决这个问题。

题目有多测,不超过 \(5\) 组。

【样例输入】

1
2
2
100

【样例输出】
No

思路

首先告诉我你在题目里面放一个电磁炮是为什么

直接进行矩阵乘法的时间复杂度是 \(O(n^3)\) 在洛谷好像是可以卡过去的,但是在学校老爷评测姬卡不过去。

题目中说到“如果知道了 \(A \times B \times R=C \times R\),那么可以推出 \(A \times B = C\)”,对于矩阵也是显然适用的。

所以我们可以随机构造出一个 \(N \times 1\) 的矩阵 \(R\),如果 \(A \times B \times R=C \times R\) 那么就是正确的,输出 Yes 即可;如果不对那就是错误的,输出 No

题目中说到“如果一个事情做一次可能得不到正确答案,但是多做几次就有很大可能能够得到了”,是要提示我们要多随机几次,否则有概率会爆 WA,但是这个概率应该比被星穹列车创的概率还要小 如果真遇到了可以去买彩票了,用神奇常数 \(\mathbf{33500336}\) 成功一次 AC。

代码

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
constexpr int N=1007;
struct matrix
{
	int n,m;
	ll a[N][N];
	matrix(int _n,int _m):n(_n),m(_m){}
	void clear(){for(int i=1;i<=n;i++) for(int j=1;j<=m;j++) a[i][j]=0;}
	void clear(int _n,int _m){n=_n;m=_m;for(int i=1;i<=n;i++) for(int j=1;j<=m;j++) a[i][j]=0;}
};
bool operator==(matrix A,matrix B)
{
	if(A.n!=B.n||A.m!=B.m) return 0;
	for(int i=1;i<=A.n;i++)
	for(int j=1;j<=A.m;j++) if(A.a[i][j]!=B.a[i][j]) return 0;
	return 1;
}
matrix operator*(matrix A,matrix B)
{
	matrix C(A.n,B.m);
	for(int i=0;i<=C.n;i++)
		for(int j=0;j<=C.m;j++) C.a[i][j]=0;
	for(int i=1;i<=A.n;i++)
		for(int k=1;k<=B.m;k++)
			for(int j=1;j<=A.m;j++) C.a[i][k]+=A.a[i][j]*B.a[j][k];
	return C;
}
void print_matrix(matrix A)
{
	for(int i=1;i<=A.n;i++)
		for(int j=1;j<=A.m;j++) cout<<A.a[i][j]<<" \n"[j==A.m];
}
/**/
mt19937 rng(33550336);
uniform_int_distribution<int> dis(1,1000);
int n;
matrix a(1,1),b(1,1),c(1,1);
inline bool judge()
{
	matrix r(n,1);
	for(int i=1;i<=n;i++) r.a[i][1]=dis(rng);
//	print_matrix((b*r)*a);
//	cerr<<(a*(b*r)).n<<' '<<(a*(b*r)).m<<'\n';
//	cerr<<'\n';
//	print_matrix(c*r);
	if((a*(b*r))==(c*r)) return 1;
	else return 0;
}
int main()
{
	freopen("transform.in","r",stdin);
	freopen("transform.out","w",stdout);
	while(scanf("%d",&n)!=EOF)
	{
		a.clear(n,n); b.clear(n,n); c.clear(n,n);
		for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) scanf("%lld",&a.a[i][j]);
		for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) scanf("%lld",&b.a[i][j]);
		for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) scanf("%lld",&c.a[i][j]);
		puts(judge()?"Yes":"No");
	}
	return 0;
}
posted @ 2025-08-06 21:11  wwwidk1234  阅读(58)  评论(0)    收藏  举报