写在前面
笔者五月二十四日要参加26年CC南昌邀请赛,为练题拉了八套前两年的CC邀请赛铜牌题及以下,计划在比赛前把这些题做完并补题,目前是第二篇,24CCPC山东邀请赛,五题铜七题银
这场是我们全队vp的,结果是五题504罚时,我第六题有完整思路但是懒得写了,自估难度I<K<<A<F<C<<J<D,赛时我是一眼锁定I题,然后飞快秒掉,接着开K(不知道为啥自动锁这两个题了,可能有寻找签到题的被动),剩下队友合作开的A、F、C,我写完K就开始看J,然后想了两个错误策略,最后想到了正确策略,但是懒得写了,而且这个时候已经达到铜牌线了,D题剩余时间有点不可做,队友也燃尽了,就没继续写
这篇博客主要补一下队友开的A、F,还有我没写完的J
A
题面:

题意:
n台打印机,每一台都有效率和休息间隔以及休息时间,问打印指定数量试题需要多长时间
思路:
这道题有点像二分答案 ,二分秒数,check函数就直接带入那个时间,然后计算这时打印了多少试题,再决定下一次时间的偏向,下界肯定是0,上界就是最差情况,最差情况是t=1e9,l=1,w=1e9,k=1e9,n=1,此时每1e9秒打印1份试题,要停机1e9秒才能继续,而最后需要1e9份试题,所以应该是(1e9+1e9)*1e9=2e18,这个值非常幸运的处在ll的范围内,但是如果用这个上界来计算的话,n=100时,算出来的试题数就超出ll的范围了,所以应该在算出试题大于k的时候果断停手
代码:
cpp
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
struct vec
{
ll t;
ll l;
ll w;
};
vector<vec>ds;
ll check(ll k,ll cnt)
{
ll sum=0;
for(int i=0;i<ds.size();i++)
{
ll cishu=cnt/(ds[i].t*ds[i].l+ds[i].w);
sum+=ds[i].l*cishu;
cishu=cnt%(ds[i].t*ds[i].l+ds[i].w);
cishu/=ds[i].t;
sum+=min(ds[i].l,cishu);
if(sum>=k) return sum;
}
return sum;
}
void solve()
{
ds.clear();
ll n,k;
cin>>n>>k;
while(n--)
{
ll a,b,c;
cin>>a>>b>>c;
ds.push_back({a,b,c});
}
ll r=2e18;
ll l=0;
while(l+1<r)
{
ll mid=l+r>>1;
ll sum=check(k,mid);
//cout<<sum<<endl;
if(sum>=k) r=mid;
else l=mid;
}
cout<<r<<endl;
}
int main()
{
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
ll t=1;
cin>>t;
while(t--)
{
solve();
}
return 0;
}
F
题面:

题意:
给定一个序列,里边的元素可正可负,然后要求这样一个东西:指定一个k,将这个序列划分为k段,并求出1*第一段的和+2*第二段的和+......+k*第k段的和,然后要输出n次,就是输出k从1到序列长度,每一次k都要输出一次答案
思路:
使用数学推导一下:设si是分割后的第i段,则结果为1*s1+2*s2+3*s3+......+k*sk,创一个后缀和数组,使用Si存第i段最左边的后缀和,则s1=S1-S2,s2=S2-S3,所以结果变成了1*(S1-S2)+2*(S2-S3)+......+(k-1)*(Sk-1-Sk)+k*Sk,即S1-S2+2*S2-2*S3+......+(k-1)Sk-1-(k-1)Sk+k*Sk,最后就化简成了S1+S2+S3+......+Sk,所以其实结果就是找第1段到第k段最左边的后缀和的和的最大值,很显然,只要这k个每一个都是最大值就是最大值,所以找S数组前k大的和即可,这个地方也需要用到一个后缀和或者前缀和
讲到这里就要提一个容易出错的地方:因为无论怎么划分,第一个点是第一段序列的最左边,所以不能取前k大的,无论如何都要把S1加进去,只能取前k-1大的,然后把S1加上
代码:
cpp
void solve()
{
ll n;
cin>>n;
vector<ll>a(n+1);
for(int i=1;i<=n;i++)
{
cin>>a[i];
}
vector<ll>sus(n+1);
sus[n]=a[n];
for(int i=n-1;i>=1;i--)
{
sus[i]=sus[i+1]+a[i];
}
ll t=sus[1];
sort(sus.begin()+2,sus.end());
//for(int i=1;i<=n;i++) cout<<sus[i]<<" ";
vector<ll>s2(n+1);
s2[n]=sus[n];
for(int i=n-1;i>=1;i--)
{
s2[i]=s2[i+1]+sus[i];
}
cout<<t<<" ";
for(int i=2;i<=n;i++)
{
cout<<s2[n-i+2]+t<<" ";
}
cout<<endl;
}
J
题面:

题意:
给定n种颜色,每种颜色都有ai个节点,再给定每两种颜色连接起来产生的消耗,然后问把所有节点联通产生的最少消耗
思路:
将所有权值忽略掉颜色的连接从小到大排序,然后每一个这样的权值都遍历一次,看看两端点是否在同一个连通块中,这个步骤可以使用并查集,如果不在一个连通块就要连起来,如果这两端点颜色内部已经连起来,只需要连一根就行,如果没连起来,就把所有这个颜色的点连在一个点上,如果两端点是同一个颜色,也是所有点连在一个点上,直接这样就行了,遍历完所有权值即可
代码:
cpp
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=1003;
struct edge
{
ll str;
ll end;
ll w;
};
bool cmp(edge a,edge b)
{
return a.w<b.w;
}
ll p[N];
ll ask(ll n)
{
if(p[n]!=n) p[n]=ask(p[n]);
return p[n];
}
void solve()
{
ll n;
cin>>n;
vector<ll>color(n+1);
for(int i=1;i<=n;i++) cin>>color[i];
for(int i=1;i<=n;i++) p[i]=i;
vector<edge>vec;
for(int i=1;i<=n;i++)
{
for(int j=1;j<=n;j++)
{
ll w;
cin>>w;
vec.push_back({i,j,w});
}
}
sort(vec.begin(),vec.end(),cmp);
ll sum=0;
vector<bool>st(n+1);
for(int i=0;i<vec.size();i++)
{
ll str=vec[i].str;
ll end=vec[i].end;
if(str==end)
{
if(st[str]) continue;
else
{
st[str]=true;
sum+=(vec[i].w*(color[str]-1));
}
}
else
{
//两个颜色已经联通且两个端点内部也都联通
if(ask(str)==ask(end)&&st[end]==true&&st[str]==true) continue;
//两个颜色联通但一个端点内部还没联通
else if(ask(str)==ask(end)&&st[end]==false)
{
sum+=vec[i].w*(color[end]-1);
st[end]=true;
}
//两个颜色还没联通且一个端点内部还没联通
else if(ask(str)!=ask(end)&&st[end]==false)
{
p[ask(end)]=ask(str);
st[end]=true;
sum+=vec[i].w*color[end];
}
//两个颜色还没联通但一个端点内部已经联通
else if(ask(str)!=ask(end)&&st[end]==true)
{
sum+=vec[i].w;
p[ask(end)]=ask(str);
}
}
}
cout<<sum<<endl;
}
int main()
{
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
ll t=1;
cin>>t;
while(t--)
{
solve();
}
return 0;
}
篇末总结
山东邀请赛也算是补完了,目前一共补了两场,但是离省赛就剩一天,离南昌就剩一周了,这周因为学校的某些课要结课,需要做没用的大作业所以浪费了很多时间,只能说学生想发展必须对抗学校,想想就挺搞笑的
可能做不完八场了,我只能是尽可能做,希望明天省赛我们能拿个铜,今天下午vp一下去年省赛,有时间会补的,没时间就算了