2024CCPC哈尔滨部分题解
赛时被评测机卡死了
M.奇怪的上取整
求\(\sum_{i=1}^{n} f(n,i)\)
\(Input\)
第一行一个整数\(T(1<=T<=10^3)\),表示数据组数
对于每组数据,一行一个整数\(n(1<=n<=10^9)\)
\(Output\)
对于每组数据,输出一行一个整数,表示答案。
\(Sample\)
3
5
451
114514
————————
21T
10251
7075858
思路:如果\(i\)不是\(n\)的因数,显然\(i\)会减少到成为\(n\)的因数为止,随后返回\(n/i\),这个对于\(i\)等于\(1或2\)也成立.
所以直接分解出\(n\)的因数\(\sqrt n\),然后\(sort\)一下\(nlogn\),遍历一遍即可,总时间复杂度\((\sqrt n+nlogn)*T\)左右
注意:\((\sqrt n+nlogn)*T\)最坏接近\(10^{8.5}\),不能用\(map\)
#include<iostream>
#include<queue>
#include<map>
#include<set>
#include<vector>
#include<algorithm>
#include<deque>
#include<cctype>
#include<string.h>
#include<math.h>
#include<time.h>
#include<random>
#include<stack>
#include<string>
#define ll long long
#define lowbit(x) (x & -x)
using namespace std;
const ll mod=1e9+7;
ll ksm(ll x,ll y)
{
ll ans=1;
while(y)
{
if(y&1)
{
ans=ans%mod*(x%mod)%mod;
}
x=x%mod*(x%mod)%mod;
y>>=1;
}
return ans;
}
ll gcd(ll x, ll y)
{
if (y == 0)
return x;
else
return gcd(y, x % y);
}
void fio()
{
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
}
ll a[3250000];
int main()
{
fio();
ll t;
cin>>t;
//map<ll,bool>q;
while(t--)
{
//q.clear();
ll n;
cin>>n;
ll cnt=0;
ll u=n;
for(ll i=1;i*i<=n;i++)
{
if(n%i==0)
{
cnt++;
a[cnt]=i;
if(n/i==i)
continue;
else
{
cnt++;
a[cnt]=n/i;
}
}
}
sort(a+1,a+1+cnt);
ll ans=0;
for(ll i=cnt;i>=1;i--)
{
if(n>=a[i])
{
ans+=(n-a[i]+1)*(u/a[i]);
n=a[i]-1;
}
}
ans+=n*u;
cout<<ans<<endl;
}
}
C.在哈尔滨指路
在哈尔滨问路,把行人给的指路方向和位移,转换成你自己的行动方向和位移
\(Input\)
第一行一个整数\(T(1<=T<=10^4)\)
对于每组数据,第一行一个整数\(n(1<=n<=10)\),表示指路指令的个数
接下来\(n\)行,每行按照绝对位置描述一个指令,包含一个字符 \(d(d\in\{N,S,W,E\})\) 和一个整数 \(x(1<=x<=10)\)
表示「往\(d\)方位走到第\(x\)个路口」。其中\(N\)表示向北,\(S\)表示向南,\(W\)表示向西,\(E\)表示向东,保证相邻两个指令中d
不相同且不相反(北与南互相相反,西与东互相相反).
\(Output\)
对于每组数据,第一行输出一个整数\(m(1<=m<=20)\)和一个字符\(f(f\in\{N,S,W,E\})\),分别表示按哈尔滨人习惯的指路方式的指令条数和初始面向的方位,方位的含义同输入中描述
接下来输出\(m\) 行,每行首先输出一个字符\(g\in\{Z,L,R\}\),其中\(Z\)表示直走,\(L\)表示左转,\(R\)表示右转。如果输出的字符为\(Z\),此行还需输出一个整数\(y(1<y<100)\)
表示直走到第\(y\)个路口。第一个输出的指令必须以\(Z\)开头,输出中相邻两个指令的字符\(g\) 不能相同,并且\(L\)指令和\(R\)指令不能相邻
本题中你无需最小化\(m\),如果有多种方案可以到达同一目的地,输出任意一个均可。
\(Sample\)
1
2
S 2
E 1
——————————
3 S
Z 2
L
Z 1
思路:本题说那么多就是告诉你它会给出一个人在上北下南左西有东为方位时的走路状况,你只要把他变成第一人称运动按照它给的路径行动即可
暴力模拟,数据给的很宽裕
注意:目前可用的评测机如果endl不用换行符代替,一定会\(TLE\)
#include<iostream>
#include<queue>
#include<map>
#include<set>
#include<vector>
#include<algorithm>
#include<deque>
#include<cctype>
#include<string.h>
#include<math.h>
#include<time.h>
#include<random>
#include<stack>
#include<string>
#define ll long long
#define lowbit(x) (x & -x)
#define endl "\n"
using namespace std;
const ll mod=1e9+7;
ll ksm(ll x,ll y)
{
ll ans=1;
while(y)
{
if(y&1)
{
ans=ans%mod*(x%mod)%mod;
}
x=x%mod*(x%mod)%mod;
y>>=1;
}
return ans;
}
ll gcd(ll x, ll y)
{
if (y == 0)
return x;
else
return gcd(y, x % y);
}
void fio()
{
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
}
pair<char,ll>ans[32];
vector<pair<char,ll>>g;
ll pd(char x)
{
if(x=='S')
return 1;
else if(x=='E')
return 2;
else if(x=='N')
return 3;
else
return 4;
}
int main()
{
fio();
ll t;
cin>>t;
while(t--)
{
g.clear();
ll n;
cin>>n;
ll l=0;
for(ll i=1;i<=n;i++)
{
cin>>ans[i].first>>ans[i].second;
ll j=pd(ans[i].first);
if(j==l)
{
g.push_back({'Z',ans[i].second});
continue;
}
if(l==0)
{
if(j==1)
{
l=j;
g.push_back({'S',0});
}
else if(j==2)
{
l=j;
g.push_back({'E',0});
}
else if(j==3)
{
l=j;
g.push_back({'N',0});
}
else
{
l=j;
g.push_back({'W',0});
}
g.push_back({'Z',ans[i].second});
}
else if(l==1)
{
if(j==2)
{
g.push_back({'L',0});
g.push_back({'Z',ans[i].second});
}
else
{
g.push_back({'R',0});
g.push_back({'Z',ans[i].second});
}
}
else if(l==2)
{
if(j==3)
{
g.push_back({'L',0});
g.push_back({'Z',ans[i].second});
}
else
{
g.push_back({'R',0});
g.push_back({'Z',ans[i].second});
}
}
else if(l==3)
{
if(j==4)
{
g.push_back({'L',0});
g.push_back({'Z',ans[i].second});
}
else
{
g.push_back({'R',0});
g.push_back({'Z',ans[i].second});
}
}
else if(l==4)
{
if(j==1)
{
g.push_back({'L',0});
g.push_back({'Z',ans[i].second});
}
else
{
g.push_back({'R',0});
g.push_back({'Z',ans[i].second});
}
}
l=j;
}
cout<<(ll)g.size()-1<<" ";
ll k=0;
for(auto j:g)
{
k++;
if(k==1)
cout<<j.first<<endl;
else
{
if(j.second==0)
cout<<j.first<<endl;
else
cout<<j.first<<" "<<j.second<<endl;
}
}
}
}
G.欢迎加入线上会议!
你想在 MeLink 上组织一次有 \(n\) 位参会者的线上会议,参会者编号为 \(1\) 到 \(n\)。对于这 \(n\) 位参会者的每一位,都至少认识一位除了自己之外的参会者,认识关系是双向的。
会议的组织过程如下:首先由一个人创建会议并加入。随后,已经进入会议的成员可以拉一些自己认识但还没入会的参会者入会,直到所有 \(n\) 位参会者都入会。但是有 \(k\) 位参会者正忙着调试程序,这些人可以被拉进会议,但不会创建会议或拉他认识的人入会。
你希望确定是否有可能让所有 \(n\) 位成员都入会。如果可行,请确定拉人入会的方案。
$Input$第一行三个整数 \(n, m, k\) (\(2 \le n \le 2 \times 10^5\), \(1 \le m \le \min\{5 \times 10^5, \frac{n(n-1)}{2}\}\), \(0 \le k \le n\)),分别表示参会者人数,互相认识的关系数和目前正忙的人数。
第二行 \(k\) 个整数 \(a_1, \ldots, a_k\) (\(1 \le a_i \le n\)),其中第 \(i\) 个整数表示第 \(a_i\) 位成员正忙。这些整数两两不同。如果 \(k=0\),这一行将为空,但不会省略。
接下来的 \(m\) 行中,每行两个整数 \(p_i\) 和 \(q_i\) (\(1 \le p_i, q_i \le n\), \(p_i \neq q_i\)),表示 \(p_i\) 和 \(q_i\) 相互认识。认识关系是双向的。保证同一认识关系不会重复出现,且每个人都至少认识另一个人。
$Output$如果无法组织有这 \(n\) 位成员参加的会议,则在第一行输出 \(\texttt{No}\)。
如果可以,则在第一行输出 \(\texttt{Yes}\)。接下来,在第二行输出一个整数 \(t\) (\(1 \le t \le n\)),表示组织该会议所需的步骤数。
接下来 \(t\) 行,每行描述组织该会议的一步。在第 \(j\) 行,首先输出一个整数 \(x_j\) (\(1 \leq x_j \leq n\))。如果 \(j=1\),则 \(x_j\) 表示创建会议的成员,否则,\(x_j\) 必须是已经被拉入会议的一位成员。所有的 \(x_j\) 应两两不同。接下来,输出一个整数 \(y_j\) (\(1 \leq y_j \leq n\)),表示 \(x_j\) 拉 \(y_j\) 个成员入会。最后,输出 \(y_j\) 个整数 \(z_l\) (\(1 \leq z_l \leq n\)),表示被 \(x_j\) 拉入会议的成员编号。\(z_l\) 应当两两不同,并且整个过程中同一个人不能多次被拉入会。
你不必最小化 \(t\),输出任意一种合法方案均可。
\(Sample1\)
4 5 2
3 4
1 2
1 3
2 3
3 4
2 4
————————————
Yes
2
1 2 2 3
2 1 4
\(Sample2\)
4 5 3
2 4 3
1 2
1 3
2 3
3 4
2 4
——————————
No
思路:其实给的是张图,这个图有\(k\)个点不能拉人入会,但是可以被拉入会,而其他的点可以进行传播扩散拉人入会
由于多个输出还得是会中人作为邀请方,加上拉朋友只能拉建立变的,所以考虑用\(bfs\)进行扩散
首先对于只能被邀请的点进行\(vis\)标记,然后遍历所有点,找到第一个不被标记的点,以它为圆心扩散
如果一个数的相邻数被打上\(vis\)了,就不能让它作为邀请方,否则进入\(vector\).但是如果一个数有没被邀请入会,我该怎么统计?这时考虑用第二个\(vi\)标记
来作为是否被拉入会的标志,这样可以避免重复
用两个\(vector\)分别存储邀请方和被邀请方即可得出答案
注意:如果所有人一开始都是被邀请方,或者\(bfs\)走一遍后,没有所有点被\(vis\)标记过,得输出\(No\)
#include<iostream>
#include<queue>
#include<map>
#include<set>
#include<vector>
#include<algorithm>
#include<deque>
#include<cctype>
#include<string.h>
#include<math.h>
#include<time.h>
#include<random>
#include<stack>
#include<string>
#define ll long long
#define lowbit(x) (x & -x)
#define endl "\n"
#define ll long long
using namespace std;
const ll mod=1e9+7;
ll ksm(ll x,ll y)
{
ll ans=1;
while(y)
{
if(y&1)
{
ans=ans%mod*(x%mod)%mod;
}
x=x%mod*(x%mod)%mod;
y>>=1;
}
return ans;
}
ll gcd(ll x, ll y)
{
if (y == 0)
return x;
else
return gcd(y, x % y);
}
void fio()
{
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
}
vector<ll>g[250000],ans[250000];
bool vis[250000];
bool vi[250000];
vector<ll>uo;
void bfs(ll x)
{
queue<ll>q;
q.push(x);
while(!q.empty())
{
ll x=q.front();
q.pop();
if(vis[x])continue;
uo.push_back(x);
vis[x]=1;
vi[x]=1;
for(auto j:g[x])
{
if(vi[j]==0)
{
vi[j]=1;
ans[x].push_back(j);
}
if(vis[j]==0)
{
q.push(j);
}
}
}
}
int main()
{
fio();
ll n,m,k;
cin>>n>>m>>k;
for(ll i=1;i<=k;i++)
{
ll x;cin>>x;
vis[x]=1;
}
while(m--)
{
ll l,r;
cin>>l>>r;
g[l].push_back(r);
g[r].push_back(l);
}
ll s=0;
for(ll i=1;i<=n;i++)
{
if(!vis[i])
{
s=i;
break;
}
}
if(s==0)
{
cout<<"No"<<endl;
return 0;
}
bfs(s);
ll cnt=0;
for(ll i=1;i<=n;i++){
if(vi[i])cnt++;
}
if(cnt!=n)
{
cout<<"No"<<endl;
return 0;
}
cnt=0;
for(ll i=1;i<=n;i++)
{
if(ans[i].size()>0)cnt++;
}
cout<<"Yes"<<endl;
cout<<cnt<<endl;
for(auto i:uo)
{
if(ans[i].size()==0)continue;
cout<<i<<" "<<ans[i].size()<<" ";
for(auto j:ans[i])
{
cout<<j<<" ";
}
cout<<endl;
}
}
k.农场经营
你放弃了编程,来到了三江平原开始务农。在劳动过程中你改掉了作息不规律的毛病,每天你都恰好工作 \(m\) 个单位时间。现在到了收获的季节,你需要收割并加工你种植的 \(n\) 种作物,对于第 \(i\) 种作物,处理一单位时间该种作物将获得 \(w_i\) 的收益。为了使每天的工作不会太单调,对于第 \(i\) 种作物,你每天处理它的总时间长度可以是 \([l_i, r_i]\) 范围内的整数。
某天,天气预报说第二天的天气不好,于是在今天你需要调整时间安排以尽快抢收作物。具体地说,你能最多选择一种作物,并删除每天处理这种作物的时间范围限制,即删除后处理该作物的总时间长度可以是 \([0, m]\) 范围内的任意整数,而处理其他作物的时间范围不变。你仍然在这一天恰好工作 \(m\) 个单位时间。
你想知道满足上述条件的情况下,这一天能获得的最大收益是多少。
\(Input\)
第一行两个整数 \(n\) 和 \(m\) (\(1 \le n \le 10^5\), \(1 \le m \le 10^{11}\)),分别表示作物种类数和一天工作时间长度。
接下来 \(n\) 行,每行三个整数 \(w_i\), \(l_i\), 和 \(r_i\) (\(1 \le w_i \le 10^6\), \(1 \le l_i \le r_i \le 10^6\)),表示作物的收益和总时间长度的限制。
数据保证 \(\sum_{i=1}^n l_i \le m \le \sum_{i=1}^n r_i\)。
\(Output\)
输出一行一个整数,表示这一天能获得的最大收益。
\(Sample\)
5 17
2 3 4
6 1 5
8 2 4
4 3 3
7 5 5
——————
109
思路:仔细想一下,不难发现删除一个作物并没一个准确的答案(值最大\(or\)最小),所以得枚举所有作物作为被删除时的最大值
如何枚举?
不妨想想如果一个作物被删除时(不妨设它单位时间价值为\(x\)),除了保证其他作物最x基本的抢收时间外,肯定是剩下时间用于抢收能抢收的最大值最赚,
于是引申出两种状况
1.满足基本情况时,剩下时间用于抢收\(W_i\)比被x大的作物的时间总和小于他们的抢收时间最大值之和
这时一定会有\(W_i\)比x大的作物不存在实际抢收时间等于它的最大抢收时间,这时肯定是从右往左能抢收时间最大就最大,显然有单调性,做两个个后缀数组分别储存
后\(i\)个作物的抢收时间最大时的价值之和,和最大抢收时间-最小抢收时间的和,这时可以二分答案哪里要细处理,哪里直接全拿,至此\(W_i\)比x的作物的价值和得出,\(W_i\)比x小的作物直接用最小抢收
时间的价值前缀和数组即可得出,至此答案得出
2.满足基本情况时,剩下时间用于抢收\(W_i\)比被x大的作物的时间总和大于等于他们的抢收时间最大值之和
这时直接用后缀和数组贪心全拿\(W_i\)比x大的作物价值之和,前缀和数组得出\(W_i\)比x小的作物价值之和,剩下时间全拿被删除的作物
#include<iostream>
#include<queue>
#include<map>
#include<set>
#include<vector>
#include<algorithm>
#include<deque>
#include<cctype>
#include<string.h>
#include<math.h>
#include<time.h>
#include<random>
#include<stack>
#include<string>
#define ll long long
#define lowbit(x) (x & -x)
#define endl "\n"
#define ll long long
using namespace std;
const ll mod=1e9+7;
ll ksm(ll x,ll y)
{
ll ans=1;
while(y)
{
if(y&1)
{
ans=ans%mod*(x%mod)%mod;
}
x=x%mod*(x%mod)%mod;
y>>=1;
}
return ans;
}
ll gcd(ll x, ll y)
{
if (y == 0)
return x;
else
return gcd(y, x % y);
}
void fio()
{
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
}
struct s
{
ll w;
ll l,r;
}p[250000];
bool cmp(s x,s y)
{
return x.w<y.w;
}
ll sub[250000];
ll gs[250000];
ll pre[250000];
ll to[250000];
ll op[250000];
ll wz[250000];
int main()
{
ll n,m;
cin>>n>>m;
for(ll i=1;i<=n;i++)
{
ll w,l,r;
cin>>w>>l>>r;
p[i].w=w;
p[i].l=l;
p[i].r=r;
}
sort(p+1,p+1+n,cmp);
for(ll i=n;i>=1;i--)
{
sub[i]=p[i].w*p[i].r+sub[i+1];
gs[i]=gs[i+1]+p[i].r;
}
for(ll i=1;i<=n;i++)
{
pre[i]=pre[i-1]+p[i].l*p[i].w;
//cout<<p[i].w<<endl;
to[i]=to[i-1]+p[i].l;
}
ll ans=pre[n];
//cout<<ans<<endl;
for(ll i=1;i<=n;i++)
{
op[i]=op[i-1]+p[n-i+1].r-p[n-i+1].l;
wz[i]=n-i+1;
}
for(ll i=1;i<=n;i++)
{
ll cnt=to[i-1]+gs[i+1];//个数
if(cnt>m)
{
ll u=m-(to[n]-p[i].l);
ll j=lower_bound(op+1,op+1+n,u)-op;
j--;
ll cnt1=m-(to[n]-p[i].l+op[j]);
ans=max(ans,pre[wz[j+1]]-p[i].l*p[i].w+sub[wz[j]]+cnt1*(p[wz[j+1]].w));
}
else
{
ans=max(ans,sub[i+1]+pre[i-1]+(m-cnt)*p[i].w);
}
}
cout<<ans<<endl;
}
J.新能源汽车
有一辆新能源汽车,这辆车有 \(n\) 个电瓶,第 \(i\) 个电瓶容量为 \(a_i\) 单位,每消耗 \(1\) 单位电力能恰好前进 \(1\) 公里。车只能前进,不能反向行驶。你可以选择汽车行驶的每一公里所使用的电力来自哪个电瓶。
汽车在出发前每个电瓶都是充满电的。行驶中途会经过 \(m\) 个充电站,第 \(j\) 个充电站距离起点 \(x_j\) 公里,并且只能给第 \(t_j\) 个电瓶充电,每个充电站能提供的电力是无限的。
请计算这辆新能源汽车最远可以行驶多少公里。
$Input$第一行一个整数 \(T\) (\(1\le T\le 10^4\)),表示测试数据组数。
对于每组数据,第一行两个整数 \(n, m\) (\(1\le n,m\le 10^5\)),表示汽车电瓶个数和充电站的个数。
第二行 \(n\) 个整数 \(a_1,a_2,\ldots,a_n\) (\(1\le a_i\le 10^9\)),分别表示每个电瓶的容量。
接下来 \(m\) 行,每行两个整数 \(x_j, t_j\) (\(1\le x_j\le 10^9\), \(1\le t_j\le n\)),分别表示每个充电站的位置和它能给哪个电瓶充电。
对于每组测试数据,保证 \(1\le x_1<x_2<\ldots<x_m\le 10^9\)。所有测试数据的 \(n\) 之和与 \(m\) 之和均不超过 \(2\cdot 10^5\)。
$Output$对于每组数据,输出一行一个整数,表示这辆车最远可以行驶多少公里。
\(Sample\)
2
3 1
3 3 3
8 1
2 2
5 2
1 2
2 1
————————
12
9
思路:显然最好的使用电池方法就是,下个充电站是哪个就用哪个电池,如果相应电池没电力,用后面充电站可以充的电池继续前行即可,不能充的电池最好留到最后再用
怎么维护?
考虑开多维\(set\),这样对于能充电的电池能储存它对应的充电站位置,随后开个\(set<pair<ll,ll>>\)前面储存最近的充电位置,后面储存对应的电池序号
如果一个电池无充电站了,就把他存储起来,最后用,如果还有则用多维\(set\)储存的最近冲电站位置在\(set<pair<ll,ll>>\)里进行更新,随后如此反复即可
具体还得看看代码
注意:车到了充电站时得考虑目前使用得电池是否被用完
#include<iostream>
#include<queue>
#include<map>
#include<set>
#include<vector>
#include<algorithm>
#include<deque>
#include<cctype>
#include<string.h>
#include<math.h>
#include<time.h>
#include<random>
#include<stack>
#include<string>
#define ll long long
#define lowbit(x) (x & -x)
#define endl "\n"
#define ll long long
using namespace std;
const ll mod=1e9+7;
ll ksm(ll x,ll y)
{
ll ans=1;
while(y)
{
if(y&1)
{
ans=ans%mod*(x%mod)%mod;
}
x=x%mod*(x%mod)%mod;
y>>=1;
}
return ans;
}
ll gcd(ll x, ll y)
{
if (y == 0)
return x;
else
return gcd(y, x % y);
}
void fio()
{
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
}
ll a[325000];
ll b1[325000];//位置
ll b2[325000];//几号电池
map<ll,ll>mp;//还有多少个位置点
set<ll>q[325000];//下个位置点
set<pair<ll,ll>>f;//使用组
//set<pair<ll,ll>>cd;
ll d[3250000];
int main()
{
fio();
ll t;
cin>>t;
while(t--)
{
f.clear();
mp.clear();
ll n,m;
cin>>n>>m;
for(ll i=1;i<=n;i++)
{
cin>>a[i];
d[i]=a[i];
q[i].clear();
}
for(ll i=1;i<=m;i++)
{
ll x,y;
cin>>b1[i]>>b2[i];
q[b2[i]].insert(b1[i]);
mp[b2[i]]++;
}
ll cn=0;
for(ll i=1;i<=n;i++)
{
if(mp[i]==0)cn+=a[i];
else f.insert({*q[i].begin(),i});
}
b1[m+1]=1e18;
ll l=0;//包括自己
ll wz=1;//位置指针
while(1)
{
//讨论还有的
if((ll)f.size()>0)
{
ll su=l;
l=min(l+d[(*f.begin()).second],b1[wz]);
ll k=(*f.begin()).second;d[k]-=(l-su);
ll cs=(*f.begin()).first;
if(b1[wz]==l)wz++;
f.erase(*f.begin());
if(l==cs)
{
d[k]=a[k];mp[k]--;
q[k].erase(cs);
if(mp[k]>0)
{
f.insert({*q[k].begin(),k});
}
else cn+=d[k];
}
else
{
if(l==b1[wz-1])
{
if(d[k]>0)
{
f.insert({*q[k].begin(),k});
}
ll c=b2[wz-1];
d[c]=a[c];mp[c]--;
q[c].erase(l);
if(mp[c]>0)
{
f.insert({*q[c].begin(),c});
}
else cn+=d[c];
}
}
}
else
{
ll su=l;
l=min(l+cn,b1[wz]);
cn-=(l-su);
if(l!=b1[wz])break;
wz++;
ll c=b2[wz-1];
d[c]=a[c];mp[c]--;
q[c].erase(l);
if(mp[c]>0)
{
f.insert({*q[c].begin(),c});
}
else cn+=d[c];
}
}
cout<<l<<endl;
}
}