GJOI 10.20/10.22 题解

1.CF1852A Ntarsis' Set

题意

多组测试数据 1 ≤ T ≤ 10 1\le T\le 10 1≤T≤10, 1 ≤ n ≤ 1 0 5 1\le n\le 10^5 1≤n≤105, 1 ≤ k ≤ 1 0 9 1\le k\le 10^9 1≤k≤109,保证 a i a_i ai 单调递增, a i ∈ [ 1 , 1 0 9 ] a_i\in[1,10^9] ai∈[1,109]。

思路

赛时写了一个很奇妙的做法。首先若 a 1 ≠ 1 a_1\neq 1 a1=1,答案必然是 1 1 1。于是只讨论 a 1 = 1 a_1=1 a1=1 的情况。

即每次都是先删除 S S S 最小的数,题意转化为 k + 1 k+1 k+1 天要删的第 1 1 1 个数。考虑研究每天删去的第一个数有什么性质。那就拿一个样例:

复制代码
5 6
1 2 6 8 11
每次删去:
 1  2  6  8 11
 3  4 10 13 16
 5  7 15 18 21
 9 12 20 23 26
14 17 25 28 31
19 22 30 33 36
24(ans)

我们发现,删到一定程度,相邻天数总是相差 n = 5 n=5 n=5。这是为何?

  • 首先第 11 11 11 大总是跑在前面且差 5 5 5,因为实时的第 11 11 11 小,前面已经删掉了 5 5 5 个数(包括自己,下同),所以第 11 11 11 大会一直往后推 5 5 5。

  • 对于第 8 8 8 小:

    • 前面删去 4 4 4 个数,按说是 8 + 4 = 12 8+4=12 8+4=12,但是前面又删一个 11 11 11,于是还要 12 + 1 = 13 12+1=13 12+1=13。
    • 13 13 13 后面亦然重复这个 + 4 + 1 = + 5 +4+1=+5 +4+1=+5 的过程。
  • 对于第 6 6 6 小:

    • 前面删去 3 3 3 个数,于是 6 + 3 = 9 6+3=9 6+3=9,前面又有 8 8 8 删去,于是 9 + 1 = 10 9+1=10 9+1=10;
    • 第三天前面又删 3 3 3 个数, 10 + 3 = 13 10+3=13 10+3=13,一是前面有个 11 11 11 已经删掉,二是 13 13 13 在第二天的第 8 8 8 大被删去。于是 13 + 1 + 1 = 15 13+1+1=15 13+1+1=15。
    • 后面亦然重复这个 + 3 + 1 + 1 = + 5 +3+1+1=+5 +3+1+1=+5 的过程。
  • 以此类推,推导第 1 1 1 小:

    • 1 1 1 前面 1 1 1 个数被删了, 1 + 1 = 2 1+1=2 1+1=2 有占位, 2 + 1 = 3 2+1=3 2+1=3 结束第二天;
    • 前面又被删掉 1 1 1 个数, 3 + 1 = 4 3+1=4 3+1=4 但是 4 4 4 被删过于是 4 + 1 = 5 4+1=5 4+1=5;
    • 5 + 1 = 6 5+1=6 5+1=6 但是 6 6 6 被删过了,后面 7 , 8 7,8 7,8 都被删过了,于是 6 → 9 6\to 9 6→9;
    • 后面 10 ∼ 13 10\sim 13 10∼13 都被删过了于是 9 → 14 9\to 14 9→14,亦然重复这个 + 1 + 1 + 1 + 1 + 1 = 5 +1+1+1+1+1=5 +1+1+1+1+1=5 的过程。

考虑这个"删过"的本质?就是 a 2 ∼ n a_{2\sim n} a2∼n 先一步到达了某些数,导致"删过";而 a 1 = 1 a_1=1 a1=1 的增速最慢,于是前面总是 n n n 个数被删了(后面的数也会 + 5 +5 +5,于是形成某种周期),下一次就要删 + 5 +5 +5 之后的数了。

考虑加速这个过程。设删过 p o s pos pos 个数,一开始 c u r = 1 cur=1 cur=1,若 c u r + p o s ≥ a p o s + 1 cur+pos\ge a_{pos+1} cur+pos≥apos+1,说明跑到一个被删过的数,于是 p o s ← p o s + 1 pos\leftarrow pos+1 pos←pos+1,直到 c u r + p o s < a p o s + 1 cur+pos<a_{pos+1} cur+pos<apos+1。前面删过多少数是可以继承的。直到 p o s = n pos=n pos=n 时,前面就总是删过 n n n 个数了。记录这样的 + p o s +pos +pos 进行了 t i c k tick tick 轮,若 t i c k < k tick<k tick<k 就可以直接 break 输出答案;否则答案再加 ( k − t i c k ) × n (k-tick)\times n (k−tick)×n。

时间复杂度 ≤ O ( n ) \le O(n) ≤O(n)。CF 218ms。

他们都夸我注意力惊人怎么办?还是推荐一篇严谨一点的 O ( n ) O(n) O(n) 做法

代码

cpp 复制代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const ll N=1e5+9,inf=1e18;
ll id,Q,n,k;
ll a[N];
int main()
{
	freopen("superset.in","r",stdin);
	freopen("superset.out","w",stdout);
	scanf("%lld%lld",&id,&Q);
	while(Q--)
	{
		scanf("%lld%lld",&n,&k);
		for(int i=1;i<=n;i++)
		scanf("%lld",&a[i]);
		if(a[1]!=1)
		{
			puts("1");
			continue;
		}
		//a[1]=1
		ll cur=1,pos=1,tick=0;
		a[n+1]=inf;
		while(1)
		{
			while(pos<=n)
			{
				if(a[pos]<cur+pos&&cur+pos<a[pos+1])break;
				pos++;
			}
			cur+=pos;
			tick++;
			if(tick==k)break;
			if(pos==n)break;
		}
		if(tick==k)
		{
			printf("%lld\n",cur);
			continue;
		}
		cur+=(k-tick)*n;
		printf("%lld\n",cur);
	}
	return 0;
}

2.CF1936C Pokémon Arena

题意

多组测试数据 1 ≤ T ≤ 5 1\le T\le 5 1≤T≤5, 1 ≤ n × m ≤ 2 × 1 0 5 1\le n\times m\le 2\times 10^5 1≤n×m≤2×105。

思路

赛时觉得还算可做,但是没有往建模去想。于是去后面拼暴力了。

首先对于三只宝可梦 x , y , z x,y,z x,y,z 的同一个属性 j j j,有 a x , j > a y , j > a z , j a_{x,j}>a_{y,j}>a_{z,j} ax,j>ay,j>az,j 并且 x x x 在擂台。雇佣依次雇佣三只宝可梦并充能,费用是 ( a x , j − a y , j + c y ) + ( a y , j − a z , j + c z ) = a x , j − a z , j + c y + c z (a_{x,j}-a_{y,j}+c_y)+(a_{y,j}-a_{z,j}+c_z)=a_{x,j}-a_{z,j}+c_y+c_z (ax,j−ay,j+cy)+(ay,j−az,j+cz)=ax,j−az,j+cy+cz。但显然直接雇佣宝可梦 z z z 给它充能,只用 a x , j − a z , j + c z a_{x,j}-a_{z,j}+c_z ax,j−az,j+cz。

发现要打败 x x x 的某个性质,只雇佣一只来搞是更优的。

到这里发现宝可梦和性质特别多,于是可以考虑建模。剩下的交给 dijkstra 就好了。

对于任意一对点 ( x , y ) (x,y) (x,y),首先 x x x 连向 m m m 个性质点 x 1 ∼ m x_{1\sim m} x1∼m 然后 x j x_{j} xj 和 y j y_{j} yj 之间双向边,边权分别是给 x , y x,y x,y 的 j j j 性质充能所用的价值,即 x j → y j : max ⁡ ( a y , j − a x , j , 0 ) , y j → x j : max ⁡ ( a x , j − a y , j , 0 ) x_j\to y_j:\max(a_{y,j}-a_{x,j},0),y_j\to x_j:\max(a_{x,j}-a_{y,j},0) xj→yj:max(ay,j−ax,j,0),yj→xj:max(ax,j−ay,j,0)。

但是挂着个雇佣费怎么整?我们直接令 x x x 的性质点出去 x o u t x_{out} xout 花费 c x c_x cx 即可。

但是建图规模来到 n 2 m n^2m n2m。我们发现有个 max ⁡ ( a y , j − a x , j , 0 ) \max(a_{y,j}-a_{x,j},0) max(ay,j−ax,j,0)。有的边必然边权为 0 0 0。于是 n n n 个点的 j j j 属性排序,连成一条链,这相当于把一些边权为 0 0 0 的边略去。于是 x j x_j xj 充能到 y j y_j yj 的费用就直接在链上走过去即可(这里巧妙利用了绝对值的性质)。

然后跑 1 → n 1\to n 1→n 最短路即可。

代码

cpp 复制代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define pll pair<ll,ll>
#define fi first
#define se second
#define mk make_pair
const ll N=4e5+9,M=6e5+9,inf=1e18;
ll id,Q,n,m;
ll c[N];
vector<ll>a[N];
struct edge
{
	ll to,next,w;
}e[M<<1];
ll idx,head[M];
void addedge(ll u,ll v,ll w)
{
	idx++;
	e[idx].to=v;
	e[idx].next=head[u];
	e[idx].w=w;
	head[u]=idx;
}
ll dis[M];
bool vis[M];
pll b[N];
void dijkstra(ll s)
{
	for(int i=0;i<=n+n*m;i++)
	{
		dis[i]=inf;
		vis[i]=0;
	}
	priority_queue<pll>q;
	dis[s]=0;
	q.push(mk(0,s));
	while(!q.empty())
	{
		pll tem=q.top();
		q.pop();
		ll u=tem.se;
		if(vis[u])continue;
		vis[u]=1;
		for(int i=head[u];i;i=e[i].next)
		{
			ll v=e[i].to,w=e[i].w;
			if(dis[u]+w<dis[v])
			{
				dis[v]=dis[u]+w;
				q.push(mk(-dis[v],v));
			}
		}
	}
}
int main()
{
	freopen("pokemon.in","r",stdin);
	freopen("pokemon.out","w",stdout);
	scanf("%lld%lld",&id,&Q);
	while(Q--)
	{
		idx=0;
		memset(head,0,sizeof(head));
		memset(e,0,sizeof(e));
		scanf("%lld%lld",&n,&m);
		for(int i=1;i<=n;i++)
		{
			scanf("%lld",&c[i]);
			for(int j=1;j<=m;j++)
			{
				addedge(i,i+j*n,0);
				addedge(i+j*n,i,c[i]);
			}
		}
		for(int i=1;i<=n;i++)
		{
			a[i].resize(m+1);
			for(int j=1;j<=m;j++)
			scanf("%lld",&a[i][j]);
		}
		for(int j=1;j<=m;j++)
		{
			for(int i=1;i<=n;i++)
			b[i]=mk(a[i][j],i);
			sort(b+1,b+n+1);
			for(int i=1;i<n;i++)
			{
				addedge(b[i].se+j*n,b[i+1].se+j*n,0);
				addedge(b[i+1].se+j*n,b[i].se+j*n,b[i+1].fi-b[i].fi);
			}
		}
		dijkstra(1);
		printf("%lld\n",dis[n]);
	}
	return 0;
}

反思

没有敢于去建模啊,不过这题建模还是太厉害了,感觉是图论建模的 trick 见少了。同时也没有在赛时分析一些可行的性质......

3.CF983E NN country

nflsoi 也考过一次,这次测试的 D . D. D. 题

待补。

4.AT_code_festival_2017_qualc_f Three Gluttons

接下来是 GJOI 10.22,来到 wyx 出题 round。

1.洛谷 P9464 EGOI2023 Padel Prize Pursuit / 追梦笼式网球

题意

题目传送门添加链接描述。建议前往原题阅读题干、样例以及其解释。

思路

注意奖牌持有天数可以叠加。

考虑模拟第 i i i 天的胜者拿到奖牌 i i i,奖牌 i i i 在各个选手之间是怎么游走了。

第 i i i 天胜者 x i x_i xi,在第 i i i 天之后的第 j j j 天,是 x i = y j x_i=y_j xi=yj 赢之后第一次输,奖牌全部给了 y i y_i yi。

于是考虑开 2 m 2m 2m 个点,每天 x i ← y i x_i\leftarrow y_i xi←yi 连边, x i → y j x_i\to y_j xi→yj 连边, y j y_j yj 的含义如上所述。如果 x i x_i xi 赢了后面没有输过,就单独开一个虚拟节点 e n d end end, x i → e n d x_i\to end xi→end。

对于第 2 2 2 个样例如此建图。而若把胜点和败点合并,就会变成一棵 m + 1 m+1 m+1 个节点的树,并且将边权转化为点权。

这个树保证了节点对应的胜点,浅处胜利时间大于深处胜利时间,即将把天数 i i i 作为节点编号,在天数 i i i 拿到奖牌 i i i,奖牌 i i i 在各个人之间的游走,就是从 i → e n d i\to end i→end 途径过的人,每个人持有的时间就是途径的人各自的点权和。在这里插入代码片

于是从 e n d end end 开始 dfs,维护 e n d end end 到 i i i 路径上,维护每个人的点权和,就是 i i i 奖牌被每个人拿的天数。

考虑维护持有天数最多的人 m a ma ma,加入奖牌 i i i 和最开始拿着 i i i 的 u u u,不难发现更新之后的 m a ′ ma' ma′ 要么是 i i i 要么是 m a ma ma。根据各自点权和以及编号大小更新即可。

时间复杂度 O ( n ) O(n) O(n)。

代码

cpp 复制代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const ll N=2e5+9;
ll n,m;
struct term
{
	ll x,y;
}p[N];
map<ll,ll>pos,pre;
ll ANS[N],val[N];
vector<ll>G[N],Cnt;
void dfs(ll u,ll ma)
{
	for(auto v:G[u])
	{
		ll id=pos[v];
		Cnt[id]+=val[v];
		if(Cnt[id]>Cnt[ma])
		{
			ANS[id]++;
			dfs(v,id);
		}
		else if(Cnt[id]==Cnt[ma])
		{
			ll z=min(ma,id);
			ANS[z]++;
			dfs(v,z);
		}
		else 
		{
			ANS[ma]++;
			dfs(v,ma);
		}
		Cnt[id]-=val[v];
	}
}
int main()
{
	freopen("ybt.in","r",stdin);
	freopen("ybt.out","w",stdout);
	scanf("%lld%lld",&n,&m);
	for(int i=1;i<=m;i++)
	{
		ll x,y;
		scanf("%lld%lld",&x,&y);
		p[i]=(term){x+1,y+1};
	}
	ll en=m+1;
	for(int i=m;i>=1;i--)//倒插更方便查询i天赢得人在哪一天第一次输
	{
		if(!pre[p[i].x])
		{
			G[en].push_back(i);
			val[i]=m+1-i;
		}
		else
		{
			ll z=pre[p[i].x];
			G[z].push_back(i);
			val[i]=z-i;
		}
		pos[i]=p[i].x;
		pre[p[i].y]=i;
	}
	Cnt.resize(n+3);
	dfs(en,n+1);
	for(int i=1;i<=n;i++)
	printf("%lld ",ANS[i]);
	return 0;
}

2.CF1310D Tourism

题意


n ≤ 80 , k ≤ 10 n\le 80,k\le 10 n≤80,k≤10。

思路

长度为 k + 1 k+1 k+1 的游走序列 p p p,其中 p 1 = p k + 1 = 1 p_1=p_{k+1}=1 p1=pk+1=1。

一开始想要 2 ∼ k 2\sim k 2∼k 全部枚举,然后判断不合法的情况,计算上一次出现的下标 p r e x pre_x prex,当前下标 i i i 能选 x x x 当且仅当 i − p r e x i-pre_x i−prex 为偶数------这个必然跑满。

但是这个是可以优化的。我们直接考虑合法情况,以及折半搜索。

我们直接枚举 p 3 , p 5 , . . . , p k − 1 p_3,p_5,...,p_{k-1} p3,p5,...,pk−1( k k k 是偶数,这里枚举奇数位),这里可以随便枚举。然后考虑填充偶数位,要求偶数位的数不能在奇数位出现。

我们可以类似 Floyd 的思路,预处理点 x , z x,z x,z 之间, d i s x , y + d i s y , z dis_{x,y}+dis_{y,z} disx,y+disy,z 最小的最佳途经点 y y y,要求 y ≠ x , y ≠ z y\neq x,y\neq z y=x,y=z。为了防止 p 1 , p 3 , p 5 , . . . , p k − 1 p_1,p_3,p_5,...,p_{k-1} p1,p3,p5,...,pk−1 都是不同的极端占用情况,至少要预处理出两个点之间 5 5 5 组优解。

时间复杂度 O ( n k 2 − 1 ) O(n^{\frac{k}{2}-1}) O(n2k−1)。

代码

cpp 复制代码
#pragma GCC optimise(2)
#pragma GCC optimise(3,"Ofast","inline")
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const ll N=82,inf=1e18,mod=1e9+7;
ll n,k;
ll G[N][N];
ll pass[N][N][7];
ll a[N],vis[N];
ll ans=inf;
ll dis(ll i,ll j,ll k)
{
	if(k==0)return inf;
	return G[i][k]+G[k][j];
}
void dfs(ll id)//决定奇数位4个 
{
	if(id>k/2)
	{
		ll ret=0;
		for(int i=1;i<=k/2;i++)
		{
			ll nxt=i+1;
			if(nxt>k/2)nxt=1;
			for(int t=0;t<=5;t++)
			{
				if(!vis[pass[a[i]][a[nxt]][t]])
				{
					ret+=dis(a[i],a[nxt],pass[a[i]][a[nxt]][t]);
					break;
				}
			}
		}
		ans=min(ans,ret);
		return;
	}
	for(int i=1;i<=n;i++)
	{
		a[id]=i;
		vis[i]++;
		dfs(id+1);
		a[id]=-1;
		vis[i]--;
	}
}
int main()
{
//	freopen("luogu.in","r",stdin);
//	freopen("luogu.out","w",stdout);
	scanf("%lld%lld",&n,&k);
	for(int i=1;i<=n;i++)
	for(int j=1;j<=n;j++)
	scanf("%lld",&G[i][j]);
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=n;j++)
		{
			for(int k=1;k<=n;k++)
			{
				if(k==i||k==j)continue;
				pass[i][j][6]=k;
				for(int t=5;t>=0;t--)//前6优的途经点pass (0~5)
				{
					if(dis(i,j,pass[i][j][t+1])<dis(i,j,pass[i][j][t]))swap(pass[i][j][t+1],pass[i][j][t]);
					else break;
				}
			}
		}
	}
	a[1]=1;
	vis[1]=1;
	dfs(2);
	printf("%lld",ans);
	return 0;
}

3.P3203 HNOI2010 弹飞绵羊

我的博客

4.洛谷 P4383 八省联考2018 林克卡特树

相关推荐
我搞slam4 小时前
插入区间--leetcode
算法·leetcode
前进之路94 小时前
Leetcode每日一练--40
算法·leetcode
Swift社区4 小时前
LeetCode 405 - 数字转换为十六进制数
算法·leetcode·职场和发展
赵杰伦cpp4 小时前
数据结构——二叉搜索树深度解析
开发语言·数据结构·c++·算法
大数据张老师4 小时前
数据结构——希尔排序
数据结构·算法·排序算法·1024程序员节
第七序章5 小时前
【C + +】unordered_set 和 unordered_map 的用法、区别、性能全解析
数据结构·c++·人工智能·算法·哈希算法·1024程序员节
草莓熊Lotso5 小时前
《算法闯关指南:优选算法--二分查找》--23.寻找旋转排序数组中的最小值,24.点名
开发语言·c++·算法·1024程序员节
文火冰糖的硅基工坊5 小时前
[嵌入式系统-150]:智能机器人(具身智能)内部的嵌入式系统以及各自的功能、硬件架构、操作系统、软件架构
android·linux·算法·ubuntu·机器人·硬件架构
郝学胜-神的一滴5 小时前
主成分分析(PCA)在计算机图形学中的深入解析与应用
开发语言·人工智能·算法·机器学习·1024程序员节