The 2024 CCPC National Invitational Contest (Changchun) , The 17th Jilin Provincial Collegiate Programming Contest
1|0Preface
又是经典省赛找信心,这场虽然中间经典三开三卡,但最后都调出来了 4h 10 题下班
剩下的 A 对着题解推了下式子感觉好麻烦就白兰了,J 更是经典动态维护凸壳写不了一点
2|0A. Eminor Array
神秘 DP 题,比赛的时候推了几个性质感觉没啥思路就白兰了,后面看了下题解就是耐心地不停化式子,鉴定为寄
3|0B. Dfs Order 0.5
小清新 DP + 贪心,由于 DFS 序的性质很容易想到按照子树划分状态
令 fx,0/1 表示处理了以 x 为根的子树,其中偶数/奇数位置上的数和的最大值
对于 x 的转移考虑其所有子节点 y,如果每个子树大小都是偶数那么其实贡献是固定的,不随顺序变化
否则假设存在 k 个奇数大小的子树,此时显然所有偶数子树都可以取到 max(fy,0,fy,1)
而奇数子树总是一半取 0,一半取 1,当 k 为奇数时其中一边会多出一个
这是个很经典的贪心问题,我们先强制所有的都选 0,然后按照 fy,1−fy,0 的值从大到小排序后选最大的对应个数即可
#include<cstdio>
#include<iostream>
#include<vector>
#include<algorithm>
#define int long long
#define RI register int
#define CI const int&
using namespace std;
const int N=200005;
int t,n,a[N],x,y,sz[N],f[N][2]; vector <int> v[N];
inline void DFS(CI now=1,CI fa=0)
{
f[now][0]=0; f[now][1]=a[now]; sz[now]=1;
int odd_cnt=0;
for (auto to:v[now]) if (to!=fa)
{
DFS(to,now); sz[now]+=sz[to];
odd_cnt+=(sz[to]%2==1);
}
if (odd_cnt)
{
int sum_even=0,sum_odd=0; vector <int> cg;
for (auto to:v[now]) if (to!=fa)
if (sz[to]%2==1) sum_odd+=f[to][0],cg.push_back(f[to][1]-f[to][0]);
else sum_even+=max(f[to][0],f[to][1]);
sort(cg.begin(),cg.end(),greater <int>());
int token=odd_cnt/2+odd_cnt%2,dlt=0;
for (RI i=0;i<token;++i) dlt+=cg[i];
f[now][0]+=sum_even+sum_odd+dlt;
token=odd_cnt/2; dlt=0;
for (RI i=0;i<token;++i) dlt+=cg[i];
f[now][1]+=sum_even+sum_odd+dlt;
} else
{
for (auto to:v[now]) if (to!=fa)
f[now][0]+=f[to][1],f[now][1]+=f[to][0];
}
}
signed main()
{
for (scanf("%lld",&t);t;--t)
{
scanf("%lld",&n);
for (RI i=1;i<=n;++i) scanf("%lld",&a[i]),v[i].clear();
for (RI i=1;i<n;++i) scanf("%lld%lld",&x,&y),v[x].push_back(y),v[y].push_back(x);
DFS(); printf("%lld\n",f[1][0]);
}
return 0;
}
4|0C. Fibonacci Sum
徐神一波大力推式子直接秒了此题,ORZ
#include <bits/stdc++.h>
using llsi = long long signed int;
constexpr int $n = 10'000'007;
constexpr llsi mod = 1'000'000'007;
int fac[$n], facinv[$n];
llsi ksm(llsi a, llsi b) {
llsi res = 1;
while(b) {
if(b & 1) res = res * a % mod;
a = a * a % mod;
b >>= 1;
}
return res;
}
void init_fac(int n = 10'000'000) {
fac[0] = 1;
for(llsi i = 1; i <= n; ++i) fac[i] = fac[i - 1] * i % mod;
facinv[n] = ksm(fac[n], mod - 2);
for(llsi i = n; i >= 0; --i) facinv[i - 1] = facinv[i] * i % mod;
return ;
}
inline llsi C(int a, int b) {
return llsi(fac[a]) * facinv[b] % mod * facinv[a - b] % mod;
}
int f[$n], g[$n], h[$n];
void init_f(int n = 10'000'000) {
f[1] = f[2] = 1;
for(int i = 3; i <= n; ++i) f[i] = (f[i - 1] + f[i - 2]) % mod;
g[1] = 1, g[2] = 3; h[0] = 1, h[1] = 1; h[2] = 2;
for(int i = 3; i <= n; ++i) g[i] = (llsi(g[i - 1]) * 3 + mod - g[i - 2]) % mod;
for(int i = 3; i <= n; ++i) h[i] = (llsi(h[i - 1]) * 3 + mod - h[i - 2]) % mod;
}
int n;
char s[$n];
int main() {
init_fac(); init_f();
scanf("%s", s + 1); n = strlen(s + 1);
// for(int i = 1; i <= 10; ++i) printf("%d %d %d\n", f[i], g[i], h[i]);
int b = 0;
llsi ans = 0;
for(int i = 1; i <= n; ++i) {
if(s[i] == '1') {
ans += (llsi(f[b]) * h[n - i] + llsi(f[b + 1]) * g[n - i]) % mod;
b += 1;
} else {
// Nothing happens
}
}
ans += f[b];
printf("%lld\n", ans % mod);
return 0;
}
5|0D. Parallel Lines
神秘题,注意到题目保证有解,且每条直线上至少有 2 个点,因此最后答案直线的斜率一定可以由一个点对得到
考虑随机搞一个点对,它是答案的概率显然不会低于 1k,而检验一个答案是否合法可以用扫描线 O(nlogn) 解决
最后大致复杂度为 O(nklogn)
#include<bits/stdc++.h>
using namespace std;
#define int long long
struct Pt{
int x, y;
Pt operator-(const Pt &b)const{return Pt{x-b.x, y-b.y};}
int dot(const Pt &b)const{return x*b.x+y*b.y;}
bool operator<(const Pt &b)const{return x!=b.x ? x<b.x : y<b.y;}
Pt rot90()const{return Pt{-y, x};}
};
mt19937 rnd(chrono::steady_clock::now().time_since_epoch().count());
const int N = 1e4+5;
int n, k;
Pt pt[N];
int randint(int l, int r) {
return uniform_int_distribution{l, r}(rnd);
}
pair<int, int> rpair() {
int l = randint(0, n - 1), r;
do r = randint(0, n - 1); while(l == r);
return {l, r};
}
signed main(){
ios::sync_with_stdio(0); cin.tie(0);
cin >> n >> k;
for (int i=0; i<n; ++i){
cin >> pt[i].x >> pt[i].y;
}
while (1){
auto [o, x] = rpair();
Pt ox = pt[x] - pt[o];
Pt v = ox.rot90();
map<int, vector<int>> mp;
for (int i=0; i<n; ++i){
int res = v.dot(pt[i]-pt[o]);
mp[res].push_back(i);
if (mp.size() > k) break;
}
bool ok=true;
if (mp.size()!=k) ok=false;
if (ok){
for (auto [_, vec] : mp){
if (vec.size()<2){ok=false; break;}
}
}
if (ok){
for (auto [_, vec] : mp){
cout << vec.size();
for (int x : vec) cout << ' ' << x+1;
cout << '\n';
}
break;
}
}
return 0;
}
6|0E. Connected Components
首先转化题意,令 xi=ai−i,yi=i−bi,则两个点之间有边等价于它们间满足二维偏序关系
考虑按照 xi 从小到大排序,用增量法一个一个地将点加入
可以用一个单调栈来维护之前每个连通块的 yi 最小的元素,对于新加入的点我们钦定栈顶不变然后弹出被合并掉的点即可
实现的时候对于 xi 相同的点之间的处理顺序需要注意,复杂度 O(nlogn)
#include<bits/stdc++.h>
using namespace std;
const int N = 1e6+5;
int n, A[N], B[N], X[N], Y[N], id[N];
int stk[N], top=0;
signed main(){
ios::sync_with_stdio(0); cin.tie(0);
cin >> n;
for (int i=1; i<=n; ++i){
cin >> A[i] >> B[i];
X[i] = A[i]-i; Y[i] = i-B[i];
id[i] = i;
}
sort(id+1, id+n+1, [&](int a, int b){return X[a]!=X[b] ? X[a]<X[b] : Y[a]<Y[b];});
int cur=id[1];
for (int i=2; i<=n; ++i){
if (Y[id[i]] >= Y[cur]){
while (top>0 && Y[id[i]] >= Y[stk[top]]) --top;
}else{
if (X[cur]!=X[id[i]]) stk[++top] = cur;
cur = id[i];
}
}
stk[++top] = cur;
cout << top << '\n';
return 0;
}
7|0F. Best Player
Light DS 题
首先有一个很直观的 O(nm) 做法,枚举最后获胜的人 p,然后枚举每场比赛
如果这场比赛包括 p,则让它拿走所有的分即可;否则每场比赛我们把 zi 尽量平均分配一定最优
有了这个做法后我们发现可以先把所有的比赛都平均分配,然后枚举到人 p 的时候直接把与它有关的比赛修改即可,用 multiset
可以轻松维护
总复杂度 O(nlogn)
#include<cstdio>
#include<iostream>
#include<set>
#include<vector>
#include<algorithm>
#define RI register int
#define CI const int&
using namespace std;
const int N=200005;
int t,n,m,a[N],b[N],x[N],y[N],z[N],tx[N],ty[N],tz[N];
vector <int> vec[N]; multiset <int> s[N];
int main()
{
ios::sync_with_stdio(0); cin.tie(0);
for (cin>>t;t;--t)
{
cin>>n>>m;
for (RI i=1;i<=n;++i) s[i].clear(),vec[i].clear();
for (RI i=1;i<=m;++i)
{
cin>>a[i]>>b[i]>>x[i]>>y[i]>>z[i];
tx[i]=x[i]; ty[i]=y[i]; tz[i]=z[i];
vec[a[i]].push_back(i); vec[b[i]].push_back(i);
if (x[i]<=y[i])
{
int dlt=min(y[i]-x[i],z[i]); x[i]+=dlt; z[i]-=dlt;
if (z[i]>0) x[i]+=z[i]/2+(z[i]%2),y[i]+=z[i]/2;
s[a[i]].insert(x[i]); s[b[i]].insert(y[i]);
} else
{
int dlt=min(x[i]-y[i],z[i]); y[i]+=dlt; z[i]-=dlt;
if (z[i]>0) x[i]+=z[i]/2+(z[i]%2),y[i]+=z[i]/2;
s[a[i]].insert(x[i]); s[b[i]].insert(y[i]);
}
}
multiset <int> rst;
for (RI i=1;i<=n;++i) rst.insert(*s[i].rbegin());
vector <int> ans;
for (RI i=1;i<=n;++i)
{
vector <int> tmp;
for (auto id:vec[i]) tmp.push_back(a[id]),tmp.push_back(b[id]);
sort(tmp.begin(),tmp.end());
tmp.erase(unique(tmp.begin(),tmp.end()),tmp.end());
for (auto p:tmp) rst.erase(rst.find(*s[p].rbegin()));
for (auto id:vec[i])
{
s[a[id]].erase(s[a[id]].find(x[id]));
s[b[id]].erase(s[b[id]].find(y[id]));
}
for (auto id:vec[i])
{
if (a[id]==i)
{
int nx=tx[id]+tz[id],ny=ty[id];
s[a[id]].insert(nx); s[b[id]].insert(ny);
} else
{
int nx=tx[id],ny=ty[id]+tz[id];
s[a[id]].insert(nx); s[b[id]].insert(ny);
}
}
// printf("i = %d\n",i);
// for (RI j=1;j<=n;++j) printf("%d ",*s[j].rbegin()); putchar('\n');
for (auto p:tmp) rst.insert(*s[p].rbegin());
auto mx=rst.end(); --mx;
auto smx=mx; --smx;
if (*mx==*s[i].rbegin()&&*mx!=*smx) ans.push_back(i);
for (auto p:tmp) rst.erase(rst.find(*s[p].rbegin()));
for (auto id:vec[i])
{
if (a[id]==i)
{
int nx=tx[id]+tz[id],ny=ty[id];
s[a[id]].erase(s[a[id]].find(nx));
s[b[id]].erase(s[b[id]].find(ny));
} else
{
int nx=tx[id],ny=ty[id]+tz[id];
s[a[id]].erase(s[a[id]].find(nx));
s[b[id]].erase(s[b[id]].find(ny));
}
}
for (auto id:vec[i])
{
s[a[id]].insert(x[id]);
s[b[id]].insert(y[id]);
}
for (auto p:tmp) rst.insert(*s[p].rbegin());
}
printf("%d\n",(int)ans.size());
for (auto x:ans) printf("%d ",x); putchar('\n');
}
return 0;
}
8|0G. Platform Game
签到题,祁神开场写的我题目都没看
#include<bits/stdc++.h>
using namespace std;
struct Seg{
int l, r, y;
bool operator<(const Seg &b)const{return y>b.y;}
};
void solve(){
int n; cin >> n;
vector<Seg> segs(n);
for (int i=0; i<n; ++i){
int l, r, y; cin >> l >> r >> y;
segs[i] = Seg{l, r, y};
}
sort(segs.begin(), segs.end());
int sx, sy; cin >> sx >> sy;
for (auto [l, r, y] : segs){
if (y>sy) continue;
if (sx>l && sx<r){
sy = y;
sx = r;
}
}
cout << sx << '\n';
}
signed main(){
ios::sync_with_stdio(0); cin.tie(0);
int t; cin >> t; while (t--) solve();
return 0;
}
9|0H. Games on the Ads 2: Painting
有铸币写拓扑排序写假了爆 TLE 了五发,后面徐神上去给我重新实现了下跑得飞快
首先不难想到根据 ci,j 和 pi,qj 的关系进行分讨:
- 若 ci,j≠pi∧ci,j≠qj,则这种情况一定无解;
- 若 ci,j≠pi∧ci,j=qj,则说明列 j 的染色时间一定晚于行 i;
- 若 ci,j=pi∧ci,j≠qj,则说明行 i 的染色时间一定晚于列 j;
- 若 ci,j=pi∧ci,j=qj,则行 i 和列 j 的染色时间不定
由于 {p},{q} 都是排列,因此满足第四个条件的点有且仅有 n 个,我们考虑 2n 枚举它们对应的行列的先后信息
这类先后选的问题一眼转为拓扑序计数,即我们用边 x→y 表示点 x 要在 y 之前选
朴素的拓扑序计数是 NPC 的,但这题的图是个二分竞赛图,手玩下会发现若当前存在 k 个度数为 0 的点,则不把这些点取完是不会得到新的度数为 0 的点的
因此每次找到度数为 0 的点数量 k,将 k! 计入贡献,然后删去这些点继续处理即可;注意当出现环时强制贡献为 0
总复杂度 O(2n×n2),需要较好的实现
#include<cstdio>
#include<iostream>
#include<vector>
#include<cassert>
#define RI register int
#define CI const int&
using namespace std;
const int N=45,mod=998244353;
int n,m,ans,p[N],q[N],c[N][N],deg[N],tdeg[N],fact[N],x[N],y[N],num[N][N]; vector <int> v[N];
inline void init(CI n)
{
fact[0]=1; for (RI i=1;i<=n;++i) fact[i]=1LL*fact[i-1]*i%mod;
}
int main()
{
//freopen("H.in","r",stdin);
init(40); scanf("%d",&n);
for (RI i=1;i<=n;++i) scanf("%d",&p[i]);
for (RI i=1;i<=n;++i) scanf("%d",&q[i]);
for (RI i=1;i<=n;++i) for (RI j=1;j<=n;++j) scanf("%d",&c[i][j]);
for (RI i=1;i<=n;++i) for (RI j=1;j<=n;++j)
{
if (c[i][j]!=p[i]&&c[i][j]!=q[j]) return puts("0"),0;
if (c[i][j]==p[i]&&c[i][j]==q[j])
{
num[i][j]=m; x[m]=i; y[m]=j; ++m;
} else
{
if (c[i][j]==p[i]) v[n+j].push_back(i),++tdeg[i];
else v[i].push_back(n+j),++tdeg[n+j];
}
}
assert(m==n);
for(int i = 1; i <= 2 * n; ++i) v[i].reserve(n);
for (RI mask=0;mask<(1<<m);++mask)
{
for (RI i=1;i<=2*n;++i) deg[i]=tdeg[i];
for (RI i=0;i<m;++i)
if ((mask>>i)&1) v[x[i]].push_back(n+y[i]),++deg[n+y[i]];
else v[n+y[i]].push_back(x[i]),++deg[x[i]];
int ret; // vector <int> pnt;
/*for (RI i=1;i<=2*n;++i) pnt.push_back(i);
while (!pnt.empty())
{
vector <int> vec,rmv;
for (auto x:pnt) if (!deg[x]) rmv.push_back(x); else vec.push_back(x);
if (rmv.empty()) { ret=0; break; } else ret=1LL*ret*fact[(int)(rmv.size())]%mod;
for (auto x:rmv) for (auto y:v[x]) --deg[y]; pnt=vec;
}*/
{
static int base[2][45];
auto f1 = base[0], f2 = base[1];
int count = 0, cc;
for(RI i = 1; i <= 2 * n; ++i) if(deg[i] == 0) f1[count++] = i;
ret = fact[count]; cc = count;
while(count) {
int ncount = 0;
for(RI i = 0; i < count; ++i) for(auto v: v[f1[i]])
if(--deg[v] == 0) f2[ncount++] = v;
ret = 1LL * ret * fact[ncount] % mod;
std::swap(f1, f2); cc += count = ncount;
}
if(cc < 2 * n) ret = 0;
}
(ans+=ret)%=mod;
for (RI i=m-1;i>=0;--i)
if ((mask>>i)&1) v[x[i]].pop_back(); else v[n+y[i]].pop_back();
}
return printf("%d",ans),0;
}
10|0I. The Easiest Problem
还真是最简单的题,直接 puts("21")
即可
#include<cstdio>
using namespace std;
int main()
{
puts("21");
return 0;
}
11|0J. Lone Trail
沟槽的动态凸包,直接白兰弃疗
12|0K. String Divide II
string master 徐神直接高阶科技 runs 秒了,30s 时限的题跑 300ms 可海星
#include <bits/stdc++.h>
int n, k;
char s[1000006];
namespace has {
using namespace std;
const int mod = 998'244'853, b = 131;
int a[1000006];
int pw[1000006], s[1000006];
inline void build(int n) {
pw[0] = 1;
for (int i = 1; i <= n; i++) {
pw[i] = 1ll * pw[i - 1] * b % mod;
s[i] = (s[i - 1] + 1ll * pw[i - 1] * a[i]) % mod;
}
}
inline int query(int l, int r) {
return s[r] < s[l - 1] ? s[r] + mod - s[l - 1] : s[r] - s[l - 1];
}
inline int lcs(int i, int j, int up = 1 << 30) {
if (a[i] != a[j])
return 0;
if (i > j)
swap(i, j);
if (!i)
return 0;
if (1ll * query(1, i) * pw[j - i] % mod == query(j - i + 1, j))
return i;
int l = 2, r = min(i, up), ans = 1;
if (1ll * query(i - r + 1, i) * pw[j - i] % mod == query(j - r + 1, j))
return r;
while (l <= r) {
int mid = (l + r) >> 1;
if (1ll * pw[j - i] * query(i - mid + 1, i) % mod ==
1ll * query(j - mid + 1, j))
ans = mid, l = mid + 1;
else
r = mid - 1;
}
return ans;
}
inline int lcp(int i, int j, int up = 1 << 30) {
if (a[i] != a[j])
return 0;
if (i > j)
swap(i, j);
if (j > n)
return 0;
int l = 2, r = min(up, n - j + 1), ans = 1;
if (1ll * query(i, i + r - 1) * pw[j - i] % mod == query(j, j + r - 1))
return r;
while (l <= r) {
int mid = (l + r) >> 1;
if (1ll * pw[j - i] * query(i, i + mid - 1) % mod == query(j, j + mid - 1))
ans = mid, l = mid + 1;
else
r = mid - 1;
}
return ans;
}
} // namespace has
using has::lcp;
using has::lcs;
struct runs {
int l, r, p;
bool operator < (const runs &j) const {
return l != j.l ? l < j.l : r < j.r;
}
bool operator ==(const runs &j) const {
return l == j.l && r == j.r && p == j.p;
}
};
std::vector<runs> q;
inline void get_runs() {
static int st[1000001];
st[0] = n + 1;
using has::a;
for (int i = n, top = 0, lt = 0; i; i--) {
while (top) {
int x = std::min(st[top] - i, st[top - 1] - st[top]);
lt = lcp(i, st[top], x);
if ((lt == x && st[top] - i < st[top - 1] - st[top]) ||
(lt < x && a[i + lt] < a[st[top] + lt]))
top--, lt = 0;
else
break;
}
int j = st[top];
st[++top] = i;
int x = lcs(i - 1, j - 1, n), y;
if (x < j - i) {
y = lcp(i, j, n);
if (x + y >= j - i) {
q.push_back(runs{i - x, j + y - 1, j - i});
}
}
}
}
int main() {
scanf("%d%d", &n, &k);
scanf("%s", s + 1);
for (int i = 1; i <= n; i++)
has::a[i] = 25 - (s[i] - 'a');
has::build(n), get_runs();
for (int i = 1; i <= n; i++)
has::a[i] = 25 - has::a[i];
has::build(n), get_runs();
int ans = 0;
for(auto [l, r, p]: q) {
// printf("[%d, %d, %d]\n", l, r, p);
int len = r - l + 1, part = len / p;
ans = std::max(ans, part / k * k * p);
}
printf("%d\n", ans);
return 0;
}
13|0L. Recharge
简单贪心分讨题,优先用 2 然后如果 k 是奇数就补上一个 1,直到把某一种数用完
#include<cstdio>
#include<iostream>
#define int long long
#define RI register int
#define CI const int&
using namespace std;
const int INF=1e18;
int t,k,x,y;
signed main()
{
for (scanf("%lld",&t);t;--t)
{
scanf("%lld%lld%lld",&k,&x,&y);
int c2=k/2,c1=k-c2*2,c=min(c1?x/c1:INF,c2?y/c2:INF);
int ans=c; x-=c1*c; y-=c2*c;
while (x>0&&y>0&&x+2*y>=k)
{
int c2=min(k/2,y),tmp=c2*2; y-=c2;
if (k-tmp<=x) x-=k-tmp,++ans;
else if ((k-tmp+1)/2<=y) y-=(k-tmp+1)/2,++ans;
}
if (x) ans+=x/k; else ans+=y/((k+1)/2);
printf("%lld\n",ans);
}
return 0;
}
14|0Postscript
多校我唯唯诺诺,省赛我重拳出击,这就是虐菜大师啊
__EOF__

本文链接:https://www.cnblogs.com/cjjsb/p/18352681.html
关于博主:复活的ACM新生,目前爱好仅剩Gal/HBR/雀魂/单机/OSU
版权声明:转载请注明出处
声援博主:欢迎加QQ:2649020702来DD我
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!
2023-08-10 UESTC 2023 Summer Training #25 div2/2021 HDU Multi-University Training Contest 7
2018-08-10 NOI Day1线上同步赛梦游记