$2^{-100}$ 秒虐杀 CSP-S 2025
\(2^{-100}\) 秒虐杀 CSP-S 2025
配个 bgm: 隐藏 feat 竹游人 - 北京爆竹
智商灰飞烟灭 又是谁造的冤孽
你看看街边绿化带灰的并不鲜艳
我走在你前面 一个城市一个前线
这故事结局依旧是个悬念
prob a 社团招新
problem:QOJ-14597
problem:UniversalOJ-1008
problem:LibreOJ-5542
problem:洛谷-P14361
挂那么多题目链接是为了凑字数吗?
我认为 greedy 算法是甲级战犯, 于是我们使用 dp 解决这个问题, 注意只能得到 \(55\) 分.
看上去和区间无关, 那么就是普通的线性 dp 了.
既然要确保选部门 \(1\) \(2\) \(3\) 的人都不能超过 \(\cfrac{n}{2}\) 个, 那么我们在状态里也需要记录这个信息.
于是可以设计出 \(f_{t,x,y}\) 表示前 \(t\) 个人有 \(x\) 个在 \(1\) 部门有 \(y\) 个在 \(2\) 部门有 \(t-x-y\) 个在 \(3\) 部门时的最优权值.
时间复杂度 \(O(\frac{n^{3}}{2})\), 可以得到 \(55\) 分.
可以很容易的写出代码.
#include<cmath>
#include<climits>
#include<cstddef>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#include<deque>
#include<iomanip>
#include<iostream>
#include<map>
#include<queue>
#include<set>
#include<string>
#include<tuple>
#include<vector>
using lf=double;
using ll=std::ptrdiff_t;
using ull=std::size_t;
using ui=unsigned;
constexpr ll inf=0x3f3f3f3f3f3f3f3fl;
constexpr ll mod=0x3b800001l;
constexpr ll len=1l<<8;
ll f[len][len>>1][len>>1];
ll a[len],b[len],c[len];
void solve(void){
ll n,fusu=~inf;
std::cin>>n;
for(ll x=1;x<=n;x++)
std::cin>>a[x]>>b[x]>>c[x];
memset(f,~inf,sizeof f),f[0][0][0]=0;
for(ll t=1;t<=n;t++)
for(ll x=0;x<=std::min(t,n>>1);x++){
for(ll y=0;y<=std::min(t-x,n>>1);y++){
if(x) f[t][x][y]=std::max(f[t][x][y],f[t-1][x-1][y]+a[t]);
if(y) f[t][x][y]=std::max(f[t][x][y],f[t-1][x][y-1]+b[t]);
if(t-x-y) f[t][x][y]=std::max(f[t][x][y],f[t-1][x][y]+c[t]);
if(t-x-y<=n>>1) fusu=std::max(fusu,f[t][x][y]);
}
} std::cout<<fusu<<"\n";
}
auto main(void)->signed{
// freopen(".in","r",stdin);
// freopen(".out","w",stdout);
std::ios::sync_with_stdio(0);
std::cin.tie(nullptr);
std::cout.tie(nullptr);
ll t=1;
std::cin>>t;
while(t--)
solve();
return 0;
}
prob b 道路修复
problem:QOJ-14598
problem:UniversalOJ-1009
problem:LibreOJ-5543
problem:洛谷-P14362
第一眼
最小生成树.
第二眼
有额外顶点, 但是 \(k\le10\), 一眼状压 dp.
第三眼
好像每次只需要标记每个额外顶点是否被选, 遍历已经被排序的边集就可以了, 无需每次都排序.
第四眼
可以想到最优的边集一定是原图导出的 mst 加上一些额外顶点的边集 (网上有证明, 我就懒得证了), 那么我们先导出原图的 mst 就能加速代码.
注意到跳过这一步也能在 problem:LibreOJ-5543 和 problem:洛谷-P14362 得到 ac, 而加上这一步也依然会在 problem:QOJ-14598 和 problem:UniversalOJ-1009 的 extra test case \(5\) (即 hack 点) 得到 tle.
如果有朋友会更快的做法欢迎评论.
时间复杂度
预处理用 kruskal 导出原图 mst 的时间复杂度: \(O(m\log{m})\).
排序新图的时间复杂度为 \(O((n+nk)\log(n+nk))\).
状压 dp 的时间复杂度 \(2^{k}\).
每次 kruskal 新图的时间复杂度为 \(O(n+nk)\).
总时间复杂度: \(O(m\log{m}+(n+nk)\log(n+nk)+2^{k}(n+nk))=O(2^{k}nk)\).
所以就能写出代码
#include<cmath>
#include<climits>
#include<cstddef>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#include<deque>
#include<iomanip>
#include<iostream>
#include<map>
#include<queue>
#include<set>
#include<string>
#include<tuple>
#include<vector>
using lf=double;
using ll=std::ptrdiff_t;
using ull=std::size_t;
using ui=unsigned;
constexpr ll inf=0x3f3f3f3f3f3f3f3fl;
constexpr ll mod=0x3b800001l;
constexpr ll ver=1l<<14;
using vtx=struct _vtx;
struct _vtx{
vtx *f; // 并查集的根
ll v,jqk; // 顶点权值和是否被选, 考虑到结构体对齐于是也开成 ll
auto fnd(void)->vtx*;
} v[ver];
std::vector<std::tuple<vtx*,vtx*,ll>>e,e1;
auto vtx::fnd(void)->vtx*{
// 处理并查集, 注意并查集复杂度不是胡扯的反阿克曼函数 O(alpha n) 而是 O(1)
if(f==nullptr)
return this;
else return f=f->fnd();
}
auto kruskal(ll n,ll k)->ll{
ll mycall=0,tot=1;
for(auto &[x,y,z]:e){
if(x->jqk==0||y->jqk==0) // 没被选的额外顶点不能用
continue;
if(x->fnd()==y->fnd())
continue;
x->fnd()->f=y->fnd();
mycall+=z;
if(++tot==n+k)
return mycall;
} return inf;
}
auto cmp(std::tuple<vtx*,vtx*,ll>&x,std::tuple<vtx*,vtx*,ll>&y)->bool{
return std::get<2>(x)<std::get<2>(y);
}
void init(ll n){ // 提前导出原图的 mst 可以加速
ll tot=1;
std::sort(e1.begin(),e1.end(),cmp);
for(auto &[x,y,z]:e1){
if(x->fnd()==y->fnd())
continue;
x->fnd()->f=y->fnd();
e.emplace_back(x,y,z); // 把 mst 导出到新图
if(++tot==n)
return;
}
}
void solve(void){
ll n,m,k;
std::cin>>n>>m>>k;
while(m--){
ll x,y,z;
std::cin>>x>>y>>z;
e1.emplace_back(v+k+x,v+k+y,z);
} init(n);
for(ll x=1;x<=k;x++){
std::cin>>v[x].v;
for(ll y=1;y<=n;y++){
ll z;
std::cin>>z;
e.emplace_back(v+x,v+k+y,z);
}
} for(ll x=k+1;x<=k+n;v[x++].jqk=1); // 原图所有顶点都需要被选
std::sort(e.begin(),e.end(),cmp);
ll fusu=inf;
for(ll s=0;s<1l<<k;s++){ // 状压 dp
ll f=0; // 累计当前选的额外顶点的权值和
for(ll x=0;x<k;x++)
if(s&1<<x)
v[x+1].jqk=1,
f+=v[x+1].v;
else v[x+1].jqk=0;
for(ll x=1;x<=n+k;v[x++].f=nullptr); // 重置所有顶点的并查集
fusu=std::min(fusu,f+kruskal(n,__builtin_popcountl(s)));
} std::cout<<fusu<<"\n";
}
auto main(void)->signed{
// freopen(".in","r",stdin);
// freopen(".out","w",stdout);
std::ios::sync_with_stdio(0);
std::cin.tie(nullptr);
std::cout.tie(nullptr);
ll t=1;
// std::cin>>t;
while(t--)
solve();
return 0;
}
prob c 谐音替换
problem:QOJ-14599
problem:UniversalOJ-1010
problem:LibreOJ-5544
problem:洛谷-P14363
acam 是非常诡异的东西, trie 和 hjt 线段树的做法更加诡异.
注意: 这里的 hjt 线段树是黄嘉泰线段树, 即用 \(f-1\) 和 \(g\) 两个前缀和的值域线段树的差来查询 \([f,g]\) 的区间信息. 请不要牵扯任何无关人员.
prob d 员工招聘
problem:QOJ-14600
problem:UniversalOJ-1011
problem:LibreOJ-5545
problem:洛谷-P14364
新的诡异东西: 提前钦定.
这里有个 提前钦定 的好题 problem:UniversalOJ-1005.

浙公网安备 33010602011771号