2024 中国大学生程序设计竞赛全国邀请赛(山东) The 2024 CCPC Shandong Invitational Contest and Provincial Collegiate Programming Contest
I. Left Shifting 左移
签到
点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
using pii=pair<int,int>;
using ll=long long;
const ll inf=1e18;
const int mod =1e9+7;
void solve(){
string s;
cin>>s;
if(s[0]==s.back()){
cout<<0<<endl;
return;
}
for(int i=0;i<s.size()-1;i++){
if(s[i]==s[i+1]){
cout<<i+1<<endl;
return;
}
}
cout<<-1<<endl;
}
signed main(){
ios::sync_with_stdio(0);
cin.tie(0);
int ct=1;
cin>>ct;
while(ct--){
solve();
}
return 0;
}
A. Printer 打印机
显然的二分。
如何发现是二分?因为时间越长,打印的题目数量一定变多,题目数量是随着时间单调递增的
假设时间是mid,如何找到第i个打印机在mid时间内打印了多少题目?
mid / (t[i] * L[i] + w[i]) * L[i] + min(L[i], mid % (t[i] * L[i] + w[i]) / t[i]);
分为两部分,(t[i] * L[i] + w[i]) * L[i] 和 +min(L[i], mid % (t[i] * L[i] + w[i]) / t[i])
前一部分是,每打印L个题目后,会休息w分钟,这相当于是一个循环,所以有几个这个时间,就相当于打印了多少 L个题目
后一部分,是最后剩下的小于一个循环的时间,因为一个循环最多打印L个,所以要跟L取min。这点在样例里也有体现
两个可能wa的点:r如果不确定就尽量开大点;check中途只要符合条件随时退出,否则会爆ll。(曾经某次div3 f二分题因此被hack)
点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
using pii=pair<int,int>;
using ll=long long;
const ll inf=1e18;
const int mod =1e9+7;
void solve(){
int n,k;
cin>>n>>k;
vector<int> t(n+10),L(n+10),w(n+10);
for(int i=1;i<=n;i++){
cin>>t[i]>>L[i]>>w[i];
}
int l=1,r=2e18;
auto check=[&](int mid)->bool {
int cnt=0;
for(int i=1;i<=n;i++){
cnt+=mid/(t[i]*L[i]+w[i])*L[i]+min(L[i],mid%(t[i]*L[i]+w[i])/t[i]);
if(cnt>=k) return 1;
}
return 0;
};
while(l<r){
int mid=l+r>>1;
if(check(mid)) r=mid;
else l=mid+1;
}
cout<<l<<endl;
}
signed main(){
ios::sync_with_stdio(0);
cin.tie(0);
int ct=1;
cin>>ct;
while(ct--){
solve();
}
return 0;
}
K. Matrix 矩阵
这辈子赛时写不出来构造了
n=5时:
1 1 1 1 1
2 2 2 2 2
3 3 3 3 3
4 5 6 7 8
4 5 6 9 10
点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
using pii=pair<int,int>;
using ll=long long;
const ll inf=1e18;
const int mod =1e9+7;
void solve(){
int n;
cin>>n;
int g[n+1][n+1];
int now=1;
for(int i=1;i<=n-2;i++){
for(int j=1;j<=n;j++){
g[i][j]=now;
}
now++;
}
for(int j=1;j<=n-2;j++){
for(int i=n-1;i<=n;i++){
g[i][j]=now;
}
now++;
}
for(int i=n-1;i<=n;i++){
for(int j=n-1;j<=n;j++){
g[i][j]=now++;
}
}
cout<<"Yes\n";
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
cout<<g[i][j]<<" ";
}
cout<<endl;
}
}
signed main(){
ios::sync_with_stdio(0);
cin.tie(0);
int ct=1;
// cin>>ct;
while(ct--){
solve();
}
return 0;
}
F. Divide the Sequence 分割序列
前六题里最有意思的一道题
首先先找到n-1个后缀,从大到小排序
初始值是sum
后续k每+1,其实就是加一个此时最大的后缀进来
所以在从大到小的后缀上做前缀和即可,这个数组称为t
k从2到n (k=1是sum)
ans(k)=sum+b[k-1] (若b数组下标从1开始)
点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
using pii=pair<int,int>;
using ll=long long;
const ll inf=1e18;
const int mod =1e9+7;
void solve(){
int n,sum=0;
cin>>n;
vector<int> a(n+10),suf(n+10),t;
for(int i=1;i<=n;i++){
cin>>a[i];
sum+=a[i];
}
for(int i=n;i>=2;i--){
suf[i]=a[i]+suf[i+1];
t.push_back(suf[i]);
}
sort(t.begin(),t.end(),greater<int>());
for(int i=1;i<t.size();i++){
t[i]=t[i-1]+t[i];
}
cout<<sum<<" ";
for(int k=2;k<=n;k++){
cout<<sum+t[k-2]<<" ";
}
cout<<endl;
}
signed main(){
ios::sync_with_stdio(0);
cin.tie(0);
int ct=1;
cin>>ct;
while(ct--){
solve();
}
return 0;
}
C. Colorful Segments 2 多彩的线段 2
感觉很板的题
将所有线段按照右端点排序
以此加进来每一条线段,如果加进来是,这条线段上被 cnt 条线段覆盖,则这条线段可选颜色有k - cnt个
如果cnt>=k,直接输出0
如何找区间内有几条线段覆盖?离散化+线段树维护区间最大值
点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
using pii=pair<int,int>;
using ll=long long;
const ll inf=1e18;
const int mod =998244353;
class SegmentTree{
public:
#define lc u<<1
#define rc u<<1|1
struct Node{
int l,r,mx;
int add;
};
int n;
vector<int> w;
vector<Node> tr;
SegmentTree(int n){
init(n);
}
void init(int n){
this->n=n;
w.resize(n+10);
tr.resize(4*n+10);
}
void pushup(int u){
tr[u].mx=max(tr[rc].mx,tr[lc].mx);
}
void pushdown(int u){
if(tr[u].add){
tr[rc].add+=tr[u].add;
tr[lc].add+=tr[u].add;
tr[rc].mx+=tr[u].add;
tr[lc].mx+=tr[u].add;
tr[u].add=0;
}
return;
}
void build(int u,int l,int r){
if(l==r) tr[u]={l,r,w[r],0};
else{
tr[u]={l,r};
int mid=l+r>>1;
build(lc,l,mid);
build(rc,mid+1,r);
pushup(u);
}
}
int query(int u,int l,int r){
if(l<=tr[u].l && r>=tr[u].r) return tr[u].mx;
else{
pushdown(u);
int mx=0,mid=tr[u].l+tr[u].r>>1;
if(l<=mid) mx=query(lc,l,r);
if(r>mid) mx=max(mx,query(rc,l,r));
return mx;
}
}
void modify(int u,int l,int r,int k){
if(l<=tr[u].l && r>=tr[u].r){
tr[u].mx+=k;
tr[u].add+=k;
}
else{
pushdown(u);
int mid=tr[u].r+tr[u].l>>1;
if(l<=mid) modify(lc,l,r,k);
if(r>mid) modify(rc,l,r,k);
pushup(u);
}
}
};
void solve(){
int n,k;
cin>>n>>k;
SegmentTree tr(2*n);
tr.build(1,1,2*n);
vector<pii> p(n+1);
vector<int> b;
for(int i=1;i<=n;i++){
int x,y;
cin>>x>>y;
p[i]={x,y};
b.push_back(x);
b.push_back(y);
}
sort(b.begin(),b.end());
b.erase(unique(b.begin(),b.end()),b.end());
map<int,int> mp;
for(int i=0;i<b.size();i++){
mp[b[i]]=i+1;
}
for(int i=1;i<=n;i++){
auto [x,y]=p[i];
p[i]={mp[x],mp[y]};
}
sort(p.begin(),p.end());
ll ans=1;
for(int i=1;i<=n;i++){
auto [x,y]=p[i];
int cnt=tr.query(1,x,y);
if(cnt>k){
cout<<0<<endl;
return;
}
ans*=(k-cnt);
ans%=mod;
tr.modify(1,x,y,1);
}
cout<<ans<<endl;
}
signed main(){
ios::sync_with_stdio(0);
cin.tie(0);
int ct=1;
cin>>ct;
while(ct--){
solve();
}
return 0;
}
J. Colorful Spanning Tree 多彩的生成树
对每个颜色做最小生成树,跟普通最小生成树唯一不同的是每个颜色可能有多个点
做克鲁斯卡尔时,遍历到一个边u,v,w
如果u == v 如果u此时已经在一个连通块内,不是一个孤立的点,则跳过,否则 ans+=(a[i]-1) * g[u][u]
(把所有颜色是u的点连起来)
如果u != v 先给答案+w,如果u是一个孤立的点,则给用v给所有u连上,ans+=(a[u]-1) * w,v同理
点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
using pii=pair<int,int>;
using ll=long long;
const ll inf=1e18;
const int mod =1e9+7;
class DSU{
public:
vector<int> fa,sz;
int setCount;
int n;
DSU(){}
DSU(int n){
init(n);
}
void init(int n){
fa.resize(n+1);
sz.resize(n+1,1);
this->n=n;
setCount=n;
iota(fa.begin(), fa.end(), 0);
}
int find(int x) {
if(fa[x] == x) return x;
return fa[x] = find(fa[x]);
}
void unite(int x, int y) {
x = find(x),y = find(y);
if(x == y) return;
if(sz[x] <= sz[y] ) swap(x, y);
fa[y] = fa[x];
sz[x] += sz[y];
--setCount;
}
};
void solve(){
int n;
cin>>n;
vector<int> w(n+10);
int g[n+1][n+1];
for(int i=1;i<=n;i++){
cin>>w[i];
}
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
cin>>g[i][j];
}
}
DSU dsu(n);
vector<array<int,3>> edge;
for(int i=1;i<=n;i++){
for(int j=i;j<=n;j++){
edge.push_back({g[i][j],i,j});
}
}
sort(edge.begin(),edge.end());
int ans=0;
set<int> st;
for(auto [len,u,v]:edge){
if(u==v){
if(st.count(u)) continue;
st.insert(u);
ans+=(w[u]-1)*len;
}else{
if(dsu.find(u)!=dsu.find(v)){
dsu.unite(u,v);
ans+=len;
if(!st.count(u)){
ans+=(w[u]-1)*len;
st.insert(u);
}
if(!st.count(v)){
ans+=(w[v]-1)*len;
st.insert(v);
}
}
}
}
cout<<ans<<endl;
}
signed main(){
ios::sync_with_stdio(0);
cin.tie(0);
int ct=1;
cin>>ct;
while(ct--){
solve();
}
return 0;
}
D. Hero of the Kingdom 王国英雄
每次买卖时,肯定是能买多少买多少。
如果买卖一次就计算一次,复杂度o(t)不可接受
每次买货物的数量肯定是不减的,所以我们可以把每次买货物数量相同的来回合起来。
假设一开始可以买1个货物,且每次买卖的时间至少是2,则(1+2+3+...+k)* 2 = t
则k的值是sqrt(n)数量级的。
当剩余时间不足以让单次购买的货物数量增加(让k增加)时,跳出循环,特殊处理完剩下的时间。
点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
using pii=pair<int,int>;
using ll=long long;
const ll inf=1e18;
const int mod =1e9+7;
void solve(){
int p,a,b,q,c,d,m,t;
cin>>p>>a>>b>>q>>c>>d>>m>>t;
while(1){
if(t<a+b) break;
int now=m/p;//计算当前情况,一次来回能背多少货,假设当前时间充足
if(now==0) break;
int nowget=now*(q-p);//每次买now个货物,一次赚到的钱
int round=((now+1)*p-m+nowget-1)/(nowget);//这个now可以跑多少个来回
int time=a*now+b+c*now+d;//交易1轮的时间
int tottime=round*time;//round轮交易总时间
if(t>=tottime){
t-=tottime;
m+=round*nowget;
}
else{
m+=t/time*nowget;
t%=time;
if(t<b+d) break;
t-=b+d;
m+=t/(a+c)*(q-p);
break;
}
}
cout<<m<<endl;
}
signed main(){
ios::sync_with_stdio(0);
cin.tie(0);
int ct=1;
cin>>ct;
while(ct--){
solve();
}
return 0;
}
H. Stop the Castle 阻止城堡
二分图最大匹配,建图
先按行找,找到所有行上的可以相互攻击到的点对
再找到所有列上的可以相互攻击到的点对
普通情况是,增加一个障碍,可以消除一个点对
最有情况是,增加一个障碍,可以消除两个点对
而一个障碍可以消除两个点对的条件是,这两个点对,必须一个在行另上一个在列上,且这两个点对的连线会相交。
如何构建二分图?
设左边的点集是L,表示所有在行上的点对,二分图右边的点集是R,表示所有在列上的点对。
而连边的条件是,这两个点对的连线相交,此时可以连一条从L到R的点对。
用匈牙利算法计算二分图最大匹配即可
点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
using pii=pair<int,int>;
using ll=long long;
const ll inf=1e18;
const int mod =1e9+7;
void solve(){
int n,m;
cin>>n;
vector<pii> c(n+10);
for(int i=1;i<=n;i++){
cin>>c[i].first>>c[i].second;
}
cin>>m;
vector<pii> o(m+10);
for(int i=1;i<=m;i++){
cin>>o[i].first>>o[i].second;
}
vector<pii> L,R;//L:行上相邻可攻击的城堡对 ,R:列上的
L.push_back({-1,-1});
R.push_back({-1,-1});
//枚举所有城堡,找同一行上的可攻击对
for(int i=1;i<=n;i++){
for(int j=i+1;j<=n;j++){
auto [a,l]=c[i];
auto [x,r]=c[j];
if(a!=x) continue;
if(l>r) swap(l,r);
if(l+1==r){
cout<<-1<<endl;
return;
}
bool flag=1;
//检查是否已有障碍挡住
for(int k=1;k<=m;k++){
if(o[k].first==a && l<o[k].second && o[k].second<r) flag=0;
}
//检查是否已有城堡挡住
for(int k=1;k<=n;k++){
if(k==i || k==j) continue;
if(c[k].first==a && l<c[k].second && c[k].second<r) flag=0;
}
if(flag) L.push_back({i,j});
}
}
//枚举所有城堡,找同一列上的可攻击对
for(int i=1;i<=n;i++){
for(int j=i+1;j<=n;j++){
auto [l,a]=c[i];
auto [r,x]=c[j];
if(a!=x) continue;
if(l>r) swap(l,r);
if(l+1==r){
cout<<-1<<endl;
return;
}
bool flag=1;
//检查是否已有障碍挡住
for(int k=1;k<=m;k++){
if(o[k].second==a && l<o[k].first && o[k].first<r) flag=0;
}
//检查是否已有城堡挡住
for(int k=1;k<=n;k++){
if(k==i || k==j) continue;
if(c[k].second==a && l<c[k].first && c[k].first<r) flag=0;
}
if(flag) R.push_back({i,j});
}
}
vector<vector<int>> g(n*3);
for(int i=1;i<L.size();i++){
for(int j=1;j<R.size();j++){
auto [u,v]=L[i];
auto [a,b]=R[j];
int xi=c[u].first, yj=c[a].second;
int xl=c[a].first, xr=c[b].first;
if(xl>xr) swap(xl,xr);
int yl=c[u].second, yr=c[v].second;
if(yl>yr) swap(yl,yr);
if(xi>xl && xi<xr && yj>yl && yj<yr){
g[i].push_back(j);
}
}
}
vector<int> vis(R.size()+10),match(R.size()+10);
auto dfs=[&](auto dfs,int u)->bool {
for(auto v:g[u]){
if(vis[v]) continue;
vis[v]=1;
if(!match[v] || dfs(dfs,match[v])){
match[v]=u;
return 1;
}
}
return 0;
};
for(int i=1;i<L.size();i++){
fill(vis.begin(),vis.end(),0);
dfs(dfs,i);
}
vector<pii> ans;
vector<int> ok(210);
//把匹配上的点对加进ans
for(int j=1;j<R.size();j++){
if(match[j]!=0){
int i=match[j];
ok[i]=1;//标记已经对行中的第i个处理完了
ans.push_back({c[L[i].first].first, c[R[j].first].second});
}
}
//处理剩余的行上的点对
for(int i=1;i<L.size();i++){
if(ok[i]==0){
auto [u,v]=L[i];
int l=c[u].second,r=c[v].second;
if(l>r) swap(l,r);
ans.push_back({c[u].first,l+1});
}
}
for(int i=1;i<R.size();i++){
if(match[i]==0){
auto [u,v]=R[i];
int l=c[u].first,r=c[u].first;
if(l>r) swap(l,r);
ans.push_back({l+1,c[u].second});
}
}
cout<<ans.size()<<endl;
for(auto [u,v]:ans){
cout<<u<<" "<<v<<endl;
}
}
signed main(){
ios::sync_with_stdio(0);
cin.tie(0);
int ct=1;
cin>>ct;
while(ct--){
solve();
}
return 0;
}

浙公网安备 33010602011771号