福州市 2025 国庆集训 Day1 前三题题解

福州市 2025 国庆集训 Day1 前三题题解

别问为啥只有前三题,因为后面我不会……

Day1 题单

T1 旅行

传送门

注意到 \(P\) 非常小,所以可以考虑指数级别的做法。

考虑状压 dp。设 \(f_{s,u}\) 表示经过 \(P\) 内的点集为 \(s\),当前位于 \(P\) 中第 \(u\) 号点的最短路径长度。

然而我们的路径其实是 \(1\rightarrow p_1\rightarrow \cdots \rightarrow p_n\rightarrow n\),所以其实点集的级别是 \(2^{p+2}\)

那么转移就是枚举 \(s\),然后枚举 \(s\) 内经过的点 \(i\),再枚举 \(s\) 内没经过的点 \(j\),得到新的状态 s'=s|(1<<j)

然后 \(f_{s',j}=\min \left\{f_{s,i}+dis_{p_i,p_j}\right\}\)。其中 \(dis_{p_i,p_j}\) 表示点 \(p_i\) 到点 \(p_j\) 的最短路径长度。可以用 Floyd 预处理出来。

最后的答案显然就是 \(f_{2^{p+2}-1,p+1}\)

代码:

#include<bits/stdc++.h>
using namespace std;
typedef long long ljl;
const int N=205,M=1e4+5,P=15;
const ljl inf=1e18;
int n,m,p,vec[N],cur=1;
ljl dis[N][N],ans,f[(1<<P)+5][N];
bool vis[N];
int main(){
	ios::sync_with_stdio(0);
	cin>>n>>m;
	for(int i=1;i<=n;++i)
		for(int j=1;j<=n;++j)
			if(i!=j)dis[i][j]=inf;
	for(int i=1;i<=m;++i)
	{
		ljl u,v,w;
		cin>>u>>v>>w;
		dis[u][v]=dis[v][u]=min(dis[u][v],w);
	}
	cin>>p;
	for(int i=1;i<=p;++i)
	{
		cin>>vec[i];
		vis[vec[i]]=1;
	}
	vec[0]=1;vec[p+1]=n;
	for(int k=1;k<=n;++k)
		for(int i=1;i<=n;++i)
			for(int j=1;j<=n;++j)
				dis[i][j]=min(dis[i][j],dis[i][k]+dis[k][j]);
	memset(f,0x3f,sizeof(f));f[1][0]=0;
	for(int i=0;i<(1<<p+2);++i)
	{
		for(int j=0;j<p+2;++j)
		{
			if(!((i>>j)&1))continue;
			for(int k=0;k<p+2;++k)
			{
				if((i>>k)&1)continue;
				f[i|(1<<k)][k]=
				min(f[i|(1<<k)][k],f[i][j]+dis[vec[j]][vec[k]]);
			}
		}
	}
	ans=f[(1<<p+2)-1][p+1];
	if(ans>=inf)cout<<-1<<'\n';
	else cout<<ans<<'\n';
	return 0;
}

T2 种植

传送门

先看看如果不考虑 \(q_i\),那么就是个简单的 01 背包板子。

但是它有 \(q_i\) 啊?

我们可以尝试下,发现把 \(q_i\) 按照从大到小的顺序排序后依次插入是最优的。

证明听了,但不会。/kel。只懂把某个时间段移到前面,与前面的交换,则前面的也一定可以对答案做出贡献。所以不会更劣

来自 hqh 巨佬的证明:

  1. 题目没要求固定的种植顺序,所以可以改变顺序。
  2. 因为q越大意味着在相同的室内栽培时间下,它剩出的时间最多,所以放在前面绝对不劣。

所以可以得出q从大到小排序必定为最优解。

先 orz 下。

我的理解:

因为我们种植完第 \(i\) 个后是要继续种植的,而继续种植的同时是可以让 \(i\) 在户外生长的,如果同时进行的时间最长,则消耗时间是最少的,所以最优。

代码:

#include<bits/stdc++.h>
using namespace std;
const int N=305,T=2e4+5;
int n,t,f[T],ans;
struct NODE{
	int p,q,r;
	bool operator < (const NODE x)const{
		return q>x.q;
	}
}node[N];
int main(){
	ios::sync_with_stdio(0);
	cin>>n>>t;
	for(int i=1;i<=n;++i)
		cin>>node[i].p>>node[i].q>>node[i].r;
	sort(node+1,node+n+1);
	for(int i=1;i<=n;++i)
	{
		for(int j=t-node[i].q;j>=node[i].p;--j)
		{
			f[j]=max(f[j],f[j-node[i].p]+node[i].r);
		}
	}
	for(int i=1;i<=t;++i)
		ans=max(ans,f[i]);
	cout<<ans<<'\n';
	return 0;
}

T3 最短路

题目传送门

妙妙题。

考虑额外加边的本质是什么。

通过 T1,不难想到其实就是把数字化作二进制,看成集合,\(i\) 可以向 \(j\) 连边当且仅当 \(j\)\(i\) 的真子集。

那么考虑怎么优化建图。

比如说当前数字为 \((101)_2\),那么可以 \((101)_2\rightarrow (100)_2\),以及 \((101)_2\rightarrow (001)_2\)。其中边权均为 \(0\)

那么就有问题了:正常来说边权是 \(1\) 啊?

其实这些点都是虚图。点编号是从 \(n+1\) 开始的。

所以我们就建好了图。

注意,这样的话,需要开的点的大小是 \(2\times 10^5+2^{20}\),边是 \(3\times 10^5+2^{20}\times 20\)

代码:

#include<bits/stdc++.h>
using namespace std;
const int N=2e5+(1<<20)+5,M=3e5+(1<<20)*20+5;
int n,m,a[N],ehead[N],cnt_e,dis[N],maxa;
bool vis[N];
struct E{
	int to,w,pre;
}e[M];
priority_queue<pair<int,int>,vector<pair<int,int> >,greater<pair<int,int> > > h;
void adde(int from,int to,int w)
{
	e[++cnt_e].to=to;
	e[cnt_e].w=w;
	e[cnt_e].pre=ehead[from];
	ehead[from]=cnt_e;
	return;
}
void dijk()
{
	memset(dis,0x3f,sizeof(dis));dis[1]=0;
	h.push(make_pair(dis[1],1));
	while(!h.empty())
	{
		int u=h.top().second;h.pop();
		if(vis[u])continue;
		vis[u]=1;
		for(int i=ehead[u];i;i=e[i].pre)
		{
			int v=e[i].to,w=e[i].w;
			if(dis[v]>dis[u]+w)
			{
				dis[v]=dis[u]+w;
				if(!vis[v])h.push(make_pair(dis[v],v));
			}
		}
	}
	return;
}
int main(){
	ios::sync_with_stdio(0);
	cin>>n>>m;
	for(int i=1;i<=n;++i)cin>>a[i];
	for(int i=1,u,v;i<=m;++i)
	{
		cin>>u>>v;
		adde(u,v,1);
	}
	for(int i=1;i<=n;++i)
		adde(i,n+a[i]+1,0),adde(n+a[i]+1,i,1),maxa=max(maxa,a[i]);
	for(int i=1;i<=maxa;++i)//set i
	{
		//set i->set j(j\in i)
		for(int j=0;j<20;++j)
		{
			if((i>>j)&1)adde(n+i+1,n+(i^(1<<j))+1,0);
		}
	}
	dijk();
	for(int i=1;i<=n;++i)
		cout<<(dis[i]==0x3f3f3f3f?-1:dis[i])<<'\n';
	return 0;
}
posted @ 2025-10-01 15:47  Atserckcn  阅读(22)  评论(1)    收藏  举报