2022ahcpc(ABCDEHI)

A 设备排列

在这里插入图片描述
较经典的一道dp
f[i][1/0]表示第i个位置是(1)否(0)放仪器的方案数
f[i][0]=f[i-1][1]+f[i-1][0]
f[i][1]=f[i-k][1]+f[i-k][0]

#pragma GCC optimize(1)
#pragma GCC optimize(2)
#pragma GCC optimize(3)
#include<bits/stdc++.h>
using namespace std;
#define endl "\n"
typedef long long ll;
#define int long long
const int mod=5000011;
int f[100005][2];
void slove(){
	int n,c;
	cin>>n>>c;
	f[0][0]=1;
	c++;
	for(int i=1;i<=n;i++){
		f[i][0]=f[i-1][0]+f[i-1][1];
		int lxt=max(0ll,i-c);
		f[i][1]=f[lxt][1]+f[lxt][0];
		f[i][0]%=mod;
		f[i][1]%=mod;
	}
	cout<<(f[n][0]+f[n][1])%mod<<endl;
}
signed main(){
	ios::sync_with_stdio(false);
	cin.tie(0);cout.tie(0);
	int T=1;
	//	cin>>T;
	while(T--){
		slove();
	}
	return 0;
}

B 堆集装箱

在这里插入图片描述
类似 NOI2002 银河英雄传说 并查集

#pragma GCC optimize(1)
#pragma GCC optimize(2)
#pragma GCC optimize(3)
#include<bits/stdc++.h>
using namespace std;
#define endl "\n"
typedef long long ll;
int f[30005],s[30005],t[30005];
int find(int x){
	if(f[x]==x)return x;
	int fa=find(f[x]);
	s[x]+=s[f[x]];
	return f[x]=fa;
}
void slove(){
	int q;
	cin>>q;
	for(int i=1;i<=30000;i++)f[i]=i,t[i]=1;
	while(q--){
		char op;
		int x,y;
		cin>>op>>x;
		if(op=='M'){
			cin>>y;
			int fx=find(x),fy=find(y);
			if(fx!=fy){
				f[fx]=fy;
				s[fx]+=t[fy];
				t[fy]+=t[fx];
				t[fx]=0;
			}
		}
		else{
			find(x);
			cout<<s[x]<<endl;
		}
	}
}
int main(){
	ios::sync_with_stdio(false);
	cin.tie(0);cout.tie(0);
	int T=1;
//	cin>>T;
	while(T--){
		slove();
	}
	return 0;
}

C 搜索航桥

在这里插入图片描述
航桥对应的是割边,但是这题可以使用lca来解决
在这里插入图片描述
对于上图可以看出,对于一棵树而言2点之间的航桥数量即为2点之间的边数(到lca前经过的节点数)。如果在上图4和6中间连接一条边,对于集合{3,4,5,6}这些节点之间都没有办法产生航桥,对于7-8之间航桥数量就从6变成了2。
对于题目给出的图,我们先将其建成一颗树,每个节点的贡献为1。 剩下一开始没接上的边(u,v)就相当于让 u->lca(u,v) v->lca(u,v)之间的点无法提供航桥。
由于题目给出了删边的操作,但是删边是较为困难的,我们可以反向思考,询问的数据先记录下来,然后从后往前建边,查询航桥。

#pragma GCC optimize(1)
#pragma GCC optimize(2)
#pragma GCC optimize(3)
#include<bits/stdc++.h>
using namespace std;
#define endl "\n"
typedef long long ll;
struct node{
	int opt,x,y;
};
int n,m;
vector<int>to[30005];
int f[30005],s[30005],dep[30005],fa[30005];
int find(int x){
	return f[x]==x?x:f[x]=find(f[x]);
}
void dfs(int x,int l){
	fa[x]=l;
	dep[x]=dep[l]+1;
	for(auto y:to[x]){
		if(y==l)continue;
		dfs(y,x);
	}
}
void upd(int x,int y){
	if(dep[x]>dep[y])swap(x,y);
	while(dep[x]<dep[y]){
		s[y]=0;
		y=fa[y];
	}
	while(x!=y){
		s[x]=s[y]=0;
		x=fa[x];y=fa[y];
	}
}
int ask(int x,int y){
	if(dep[x]>dep[y])swap(x,y);
	int ans=0;
	while(dep[x]<dep[y]){
		ans+=s[y];
		y=fa[y];
	}
	while(x!=y){
		ans+=s[x]+s[y];
		x=fa[x];y=fa[y];
	}
	return ans;
}
void slove(){
	cin>>n>>m;
	vector<pair<int,int>>e(m);
	vector<node>b;
	vector<int>ans;
	set<pair<int,int>>mp;
	for(int i=1;i<=n;i++)f[i]=i,s[i]=1;
	for(auto &[x,y]:e)cin>>x>>y;
	int q;
	while(cin>>q&&q!=-1){
		int x,y;
		cin>>x>>y;
		b.push_back({q,x,y});
		if(!q)mp.insert({x,y});
		else ans.push_back(0);
	}
	for(auto [x,y]:e){
		if(mp.count({x,y}))continue;
		int fx=find(x),fy=find(y);
		if(fx!=fy){
			f[fx]=fy;
			to[x].push_back(y);
			to[y].push_back(x);
			mp.insert({x,y});
		}
	}
	dfs(1,0);
	for(auto [x,y]:e){
		if(mp.count({x,y}))continue;
		upd(x,y);
	}
	int j=ans.size();
	while(b.size()){
		auto [opt,x,y]=b.back();
		if(!opt){
			upd(x,y);
		}
		else ans[--j]=ask(x,y);
		b.pop_back();
	}
	for(auto x:ans)cout<<x<<endl;
}
int main(){
	ios::sync_with_stdio(false);
	cin.tie(0);cout.tie(0);
	int T=1;
//	cin>>T;
	while(T--){
		slove();
	}
	return 0;
}

D 太空供水

在这里插入图片描述
如果我们不知道这个最短时间的情况下,我们很难决定在哪些位置放置水源。但是如果知道的话,就可以依此为依据判断那个位置需要放置水源。于是可以考虑二分答案这个最短时间,然后在用dfs去判断哪些点需要放置水源,判断水源的安装个数是否在题目要求的M个以内。

#pragma GCC optimize(1)
#pragma GCC optimize(2)
#pragma GCC optimize(3)
#include<bits/stdc++.h>
using namespace std;
#define endl "\n"
typedef long long ll;
const int inf=1e8;
int n,m;
vector<int>to[300005];
bool nd[300005];
int f[300005],d[300005];
int res;
void dfs(int x,int fa,int k){
	f[x]=-inf;
	d[x]=inf;
	for(auto y:to[x]){
		if(y==fa)continue;
		dfs(y,x,k);
		f[x]=max(f[x],f[y]+1);
		d[x]=min(d[x],d[y]+1);
	}
	if(nd[x]&&d[x]>k)f[x]=max(f[x],0);
	if(f[x]+d[x]<=k)f[x]=-inf;
	if(f[x]==k)res++,f[x]=-inf,d[x]=0;
}
bool check(int k){
	res=0;
	dfs(1,0,k);
	if(f[1]>=0)res++;
	return res<=m;
}
void slove(){
	cin>>n>>m;
	for(int i=1;i<=n;i++){
		int x;cin>>x;
		if(x)nd[i]=true;
	}
	for(int i=1;i<n;i++){
		int a,b;
		cin>>a>>b;
		to[a].emplace_back(b);
		to[b].emplace_back(a);
	}
	int l=0,r=n;
	while(l<r){
		int mid=l+r>>1;
		if(check(mid))r=mid;
		else l=mid+1;
	}
	cout<<r;
}
int main(){
	ios::sync_with_stdio(false);
	cin.tie(0);cout.tie(0);
	int T=1;
//	cin>>T;
	while(T--){
		slove();
	}
	return 0;
}

E 太空通勤

在这里插入图片描述
可以看出这题是最短路并且N只有70,但是加上了一个最多经过k条通道的限制。
其实就是一道floyd,不过加上了一维来记录当前走了几步。
比较坑的一点是N只有70,M却有1e6会给出重边,建边的时候只需要建最小的。

#pragma GCC optimize(1)
#pragma GCC optimize(2)
#pragma GCC optimize(3)
#include<bits/stdc++.h>
using namespace std;
#define endl "\n"
typedef long long ll;
#define int long long
int n,m,k,q,inf;
int d[75][75][75];
void slove(){
	memset(d,127,sizeof d);
	inf=d[0][0][0];
	cin>>n>>m;
	for(int i=1;i<=m;i++){
		int a,b,c;
		cin>>a>>b>>c;
		d[a][b][1]=min(d[a][b][1],c);
	}
	cin>>k>>q;
	k=min(k,n);
	for(int c=2;c<=k;c++){
		for(int t=1;t<=n;t++){
			for(int i=1;i<=n;i++){
				if(i==t)continue;
				for(int j=1;j<=n;j++){
					if(j==i||j==t)continue;
					for(int a=1,b=c-1;b>=1;a++,b--){
						if(d[i][t][a]==inf||d[t][j][b]==inf)continue;
						d[i][j][c]=min(d[i][j][c],d[i][t][a]+d[t][j][b]);
					}
				}
			}
		}
	}
	while(q--){
		int a,b;
		cin>>a>>b;
		if(a==b){
			cout<<"0\n";
			continue;
		}
		int ans=inf;
		for(int i=1;i<=k;i++)ans=min(d[a][b][i],ans);
		cout<<(ans!=inf?ans:-1)<<endl;
	}
}
signed main(){
	ios::sync_with_stdio(false);
	cin.tie(0);cout.tie(0);
	int T=1;
	//	cin>>T;
	while(T--){
		slove();
	}
	return 0;
}

H 选左选右

在这里插入图片描述
区间dp
f[i][j]表示从区间i~j中能拿到的最多分数
对于区间i~j 如果先手取了左边那么后手一定取得 f[i+1][j]剩下的即为先手取得的分数,反之如果先手取了右边那么后手一定取得f[i][j-1]剩下的即为先手取得的分数
则可得出状态转移方程为f[i][i+k]= sum(i~i+k)-min(f[i][i+k-1],f[i+1][i+k])

#pragma GCC optimize(1)
#pragma GCC optimize(2)
#pragma GCC optimize(3)
#include<bits/stdc++.h>
using namespace std;
#define endl "\n"
typedef long long ll;
#define int long long
int f[5005][5005];
void slove(){
	int n;
	cin>>n;
	vector<int>a(n+1,0);
	for(int i=1;i<=n;i++)cin>>a[i],a[i]+=a[i-1];
	for(int i=1;i<n;i++)f[i][i+1]=max(a[i]-a[i-1],a[i+1]-a[i]);
	for(int k=2;k<n;k++){
		for(int i=1;i+k<=n;i++){
			f[i][i+k]=a[i+k]-a[i-1]-min(f[i][i+k-1],f[i+1][i+k]);
		}
	}
	cout<<f[1][n]<<endl;
}
signed main(){
	ios::sync_with_stdio(false);
	cin.tie(0);cout.tie(0);
	int T=1;
//	cin>>T;
	while(T--){
		slove();
	}
	return 0;
}

I 玩捉迷藏

在这里插入图片描述
比赛的时候以为是数学题,完全没想到竟然是最小生成树
那么如果把这题抽象成一个最小生成树呢?
对于每个Cij可以得知房间(i-1,j]的人员总数奇偶,又因为每个房间最多只能有一个人。那么我们把Cij当作一条权值为Cij连接i-1和j的边。
在这里插入图片描述
该图为询问Cii的情况下得出的一颗生成树。你会发现如果可以得出每个房间是否有人的情况下,图中的点是联通的。为了花费最少的纪念币,仅需要生成一颗最小生成树即可。

#pragma GCC optimize(1)
#pragma GCC optimize(2)
#pragma GCC optimize(3)
#include<bits/stdc++.h>
using namespace std;
#define endl "\n"
#define int long long
typedef long long ll;
struct edge{
	int x,y,c;
	bool operator<(const edge&b)const {
		return c<b.c;
	}
};
int f[2005];
int find(int x){
	return f[x]==x?x:f[x]=find(f[x]);
}
void slove(){
	int n;
	cin>>n;
	vector<edge>e;
	for(int i=1;i<=n;i++){
		f[i]=i;
		for(int j=i;j<=n;j++){
			int x;
			cin>>x;
			e.push_back({i-1,j,x});
		}
	}
	int ans=0;
	sort(e.begin(),e.end());
	for(auto [x,y,c]:e){
		int fx=find(x),fy=find(y);
		if(fx!=fy){
			f[fx]=fy;
			ans+=c;
		}
	}
	cout<<ans<<endl;
}
signed main(){
	ios::sync_with_stdio(false);
	cin.tie(0);cout.tie(0);
	int T=1;
	//	cin>>T;
	while(T--){
		slove();
	}
	return 0;
}
  • 3
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值