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(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;
}

浙公网安备 33010602011771号