$2^{-100}$ 秒虐杀 CSP-S 2025

\(2^{-100}\) 秒虐杀 CSP-S 2025

qoj::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-5543problem:洛谷-P14362 得到 ac, 而加上这一步也依然会在 problem:QOJ-14598problem: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.

posted @ 2025-11-24 16:52  young_tea  阅读(0)  评论(0)    收藏  举报