GJOI 9.6 题解

发布于:2025-09-11 ⋅ 阅读:(15) ⋅ 点赞:(0)

挺难的一场。

1.CF1497D Genius

题意

内存限制: 32 M B 32MB 32MB

n n n 个问题从 1 1 1 n n n 编号,第 i i i 个问题给出
c i = 2 i , t a g i , s i c_i=2^i,tag_i,s_i ci=2i,tagi,si

解决问题 i i i 后,能够解决问题 j j j条件是: I Q < ∣ c i − c j ∣ IQ<|c_i-c_j| IQ<cicj t a g i ≠ t a g j tag_i\neq tag_j tagi=tagj

在解决完 i i i 后解决了 j j j,你的 I Q IQ IQ 变为 I Q = ∣ c i − c j ∣ IQ=|c_i-c_j| IQ=cicj,同时获得 ∣ s i − s j ∣ |s_i-s_j| sisj 分。一开始 I Q = 0 IQ=0 IQ=0,且问题解决的次数和顺序不受限制。

求最高可获得的分数。

t t t 组测试数据, 1 ≤ t ≤ 100 1\le t\le 100 1t100 1 ≤ n ≤ 5000 1\le n\le 5000 1n5000 ∑ n ≤ 5000 \sum n\le 5000 n5000 s i ≤ 1 0 9 s_i\le 10^9 si109

思路

我们想要建出一个完全图,二元边权为 ( ∣ c i − c j ∣ , ∣ s i − s j ∣ ) (|c_i-c_j|,|s_i-s_j|) (cicj,sisj),满足题面要求而且分数最大,边和点经过次数不受限制。然后考虑一个 f i f_i fi 表示以 i i i 点为终点的最大分数。因为 I Q IQ IQ 是严格单调递增的,所以边权相同的边至多只会经过一次。

但是不论从空间还是时间,我们都不能开下这个图。于是考虑从那个单调性质入手:

假若从 k → j → i k\to j\to i kji j < i j<i j<i,需要满足 ∣ c k − c j ∣ < ∣ c i − c j ∣ |c_k-c_j|<|c_i-c_j| ckcj<cicj,不难发现 ∀ k < i \forall k<i k<i 均满足上述不等式。

于是考虑从小到大钦定当前最大的 i i i,枚举中转点 j j j;相当于每次加上 1 ↔ i , 2 ↔ i , . . . i − 1 ↔ i 1\leftrightarrow i,2\leftrightarrow i,...i-1\leftrightarrow i 1i,2i,...i1i 的边。对于边 j ↔ i j\leftrightarrow i ji j j j 有前继状态 k k k,根据上面的结论 k → j → i k\to j\to i kji 的转移必然合法。

if(tag[i]==tag[j])continue;
ll tem=f[i];
f[i]=max(f[i],f[j]+abs(s[i]-s[j]));
f[j]=max(f[j],tem+abs(s[i]-s[j]));

我们需要从 i − 1 i-1 i1 1 1 1 枚举 j j j。为什么不从小到大呢?反证一下,考虑一个 j a < j b j_a<j_b ja<jb,先枚举 j a j_a ja 可能会产生 j a → i j_a\to i jai 的转移, f i f_i fi 被更新,此时再枚举 j b j_b jb,就可能出现 j a → i → j b j_a\to i\to j_b jaijb 的转移,由于 c i − c j a > c i − c j b c_i-c_{j_a}>c_i-c_{j_b} cicja>cicjb,因此这个转移是不合法的。

#include<bits/stdc++.h>
using namespace std;
#define ll long long
const ll N=5003;
ll Q,n,tag[N],s[N];
ll f[N];
int main()
{
	freopen("sol.in","r",stdin);
	freopen("sol.out","w",stdout);
	scanf("%lld",&Q);
	while(Q--)
	{
		scanf("%lld",&n);
		memset(f,0,sizeof(f));
		for(int i=1;i<=n;i++)
		scanf("%lld",&tag[i]);
		for(int i=1;i<=n;i++)
		scanf("%lld",&s[i]);
		for(int i=1;i<=n;i++)
		{
			for(int j=i-1;j>=1;j--)
			{
				if(tag[i]==tag[j])continue;
				ll tem=f[i];
				f[i]=max(f[i],f[j]+abs(s[i]-s[j]));
				f[j]=max(f[j],tem+abs(s[i]-s[j]));
			}
		}
		ll ret=0;
		for(int i=1;i<=n;i++)
		ret=max(ret,f[i]);
		printf("%lld\n",ret);
	}
	return 0;
}

2.金币

题意

在这里插入图片描述
t ≤ 100 t\le 100 t100 2 ≤ n , k ≤ 1 0 12 2\le n,k\le 10^{12} 2n,k1012

思路

从第 1 1 1 个开始,每隔 k k k 个删一个,一次操作下来 n ← n − ⌈ n k ⌉ n\leftarrow n-\left\lceil \frac{n}{k} \right\rceil nnkn

F ( n , k ) F(n,k) F(n,k) 表示按照题目要求做,删到只剩下一个的操作轮数。那么我们想要找一个最小的 z z z,使得 F ( z , k ) = F ( n , k ) F(z,k)=F(n,k) F(z,k)=F(n,k)

为什么是这样呢?我们手玩一下:

n = 8 , k = 3 n=8,k=3 n=8,k=3

1 2 3 4 5 6 7 8
2 3 5 6 8
3 5 8
5 8
8

n = 9 , k = 3 n=9,k=3 n=9,k=3

1 2 3 4 5 6 7 8 9
2 3 5 6 8 9
3 5 8 9
5 8
8

在第二种情况,第 3 3 3 步时 9 9 9 被删去,前面的 8 8 8 留下了。而两种情况的 F ( n , k ) F(n,k) F(n,k) 是相同的,也即我们知道了步数 F ( n , k ) F(n,k) F(n,k),找一个最小的 z z z,从 1 1 1 个开始还原 F ( z , k ) = F ( n , k ) F(z,k)=F(n,k) F(z,k)=F(n,k) 步回去,能还原出完整的 1 ∼ z 1\sim z 1z,的最小的 z z z

我们考虑快速地计算 F ( n , k ) F(n,k) F(n,k) 和实现还原过程。

每一轮都会使 n ← n − ⌈ n k ⌉ n\leftarrow n-\left\lceil \frac{n}{k} \right\rceil nnkn,虽然 n n n 的值一直变化,但是也会存在一段变化使得每轮减少的数 ⌈ n k ⌉ \left\lceil \frac{n}{k} \right\rceil kn 不变的情况,可以在这里优化:

b = ⌈ n k ⌉ b=\left\lceil \frac{n}{k} \right\rceil b=kn,我们求 ⌈ n − x b k ⌉ = ⌈ n k ⌉ = b \left\lceil \frac{n-xb}{k} \right\rceil=\left\lceil \frac{n}{k} \right\rceil=b knxb=kn=b 的最大的 x x x,在接下来的 x x x 轮中减少的数的个数均为 b b b。根据 ⌈ n − x b k ⌉ = ⌊ n − x b + k − 1 k ⌋ \left\lceil \frac{n-xb}{k} \right\rceil=\left\lfloor \frac{n-xb+k-1}{k} \right\rfloor knxb=knxb+k1,得到 x = ⌊ n − b k + k − 1 b ⌋ x=\left\lfloor \frac{n-bk+k-1}{b} \right\rfloor x=bnbk+k1。这个步数 x x x 要记得 + 1 +1 +1,使得减少个数减 1 1 1

ll cur=n,turn=0;
while(cur>0)
{
	ll b=Ceil(cur,k);
	ll x=(cur-b*k+k-1)/b;
	x++;
	//最大的x使得ceil(n-xb,k)=b,再多减一个结果为b-1 
	turn+=x;
	cur-=b*x;
}
turn--;//这里计算的turn把最后那1个也删掉了

然后还原回去,还原过程中当前有 z z z 个数,加数时每隔 k − 1 k-1 k1 个数就加回一个数,于是 z ← z + ⌈ z k − 1 ⌉ z\leftarrow z+\left\lceil \frac{z}{k-1} \right\rceil zz+k1z。类似上面的推导过程还原即可。计算 c = ⌈ z k − 1 ⌉ = ⌈ z + y c k − 1 ⌉ c=\left\lceil \frac{z}{k-1} \right\rceil=\left\lceil \frac{z+yc}{k-1} \right\rceil c=k1z=k1z+yc y y y

while(turn)
{
	ll c=Ceil(ans,k-1);
	ll y=((k-1)*c-ans)/c;
	y++;
//	cout<<c<<" "<<y<<endl;
	if(turn>=y)
	{
		turn-=y;
		ans+=y*c;
	}
	else 
	{
		ans+=turn*c;
		turn-=turn;
	}
}

时间复杂度怎么保证?一个神秘的结论:本质不同的 ⌈ n k ⌉ \left\lceil \frac{n}{k} \right\rceil kn n \sqrt{n} n 左右, ⌈ z k − 1 ⌉ \left\lceil \frac{z}{k-1} \right\rceil k1z 同理。证明见这篇博客

代码

#include<bits/stdc++.h>
using namespace std;
#define ll long long
ll T;
ll Ceil(ll fz,ll fm)
{
	return (fz+fm-1)/fm;
}
int main()
{
	freopen("coin.in","r",stdin);
	freopen("coin.out","w",stdout);
	scanf("%lld",&T);
	while(T--)
	{
		ll n,k;
		scanf("%lld%lld",&n,&k);
		ll cur=n,turn=0,ans=1;
		while(cur>0)
		{
			ll b=Ceil(cur,k);
			ll x=(cur-b*k+k-1)/b;
			x++;
			//最大的x使得ceil(n-xb,k)=b,再多减一个结果为b-1 
			turn+=x;
			cur-=b*x;
		}
	//	cout<<turn<<" "<<cur<<endl;
		turn--;
		while(turn)
		{
			ll c=Ceil(ans,k-1);
			ll y=((k-1)*c-ans)/c;
			y++;
		//	cout<<c<<" "<<y<<endl;
			if(turn>=y)
			{
				turn-=y;
				ans+=y*c;
			}
			else 
			{
				ans+=turn*c;
				turn-=turn;
			}
		}
		printf("%lld\n",ans);
	}
	return 0;
}

3.洛谷 P11340 COI 2019 TENIS

题意

在这里插入图片描述
1 ≤ N , Q ≤ 1 0 5 1\le N,Q\le 10^5 1N,Q105

思路

我们看到样例:

1 2 3 4
2 1 3 4
2 4 3 1

把在 3 3 3 个场地上,前面的点能够赢后面的点的所有关系建单向边:
在这里插入图片描述
我们发现这 4 4 4 个点,都可以成为必胜点——因为这些点的关系边拧成了一个 scc,比如对于 4 4 4,赢了 3 3 3 就能依靠 3 3 3 1 , 2 1,2 1,2,从而赢掉所有人。

样例中进行了一次,把场地 3 3 3 1 , 2 1,2 1,2 排名调转的操作:

2 3 1 4

那么 3 3 3 个场地排名序列变成:

1 2 3 4
2 1 3 4
2 1 3 4

在这里插入图片描述
发现这个图,排名最靠前的 scc 只包含 1 , 2 1,2 1,2,即 3 3 3 永远赢不了 1 , 2 1,2 1,2

这个排名最靠前的 scc,怎么转化呢?

在第 2 2 2 幅图,如果 3 3 3 想要赢 1 , 2 1,2 1,2,要往 1 1 1 2 2 2 连边。也即在 3 3 3 个场地中, 3 3 3 的最靠前排名要小于 1 , 2 1,2 1,2 的三个排名其一。

于是关联到一个结论:一个 scc 可以表示为,存在一个排名 p p p,该 scc 中选手的 3 3 3 个场地的名次,均 ≤ p \le p p。我们要求最靠前的 scc,就是要找到一个最小的 p p p,此时满足所有必胜选手的 3 3 3 个排名均 ≤ p \le p p

为什么这样是对的?上两段解释了 3 3 3 要怎么样才能赢掉 1 , 2 1,2 1,2 3 3 3 的最靠前排名要小于 1 , 2 1,2 1,2 的三个排名其一, 3 3 3 的排名 ≤ p \le p p p p p 就要往后移动,直到包含 3 3 3 的所有排名位置。

考虑维护每个选手 i i i l i , r i l_i,r_i li,ri 表示 3 3 3 个场地中的最靠前的排名和最靠后的排名。把这个找 p p p 的过程具象化:
在这里插入图片描述
(其实就是找一个最小的 p p p,使得这个位置上均为选手排名的 r r r

询问次数来到 Q ≤ 1 0 5 Q\le 10^5 Q105,考虑 O ( log ⁡ n ) O(\log n) O(logn) 的查询,如果想二分的话单调性何在呢?

可以考虑在线段树上操作:考虑每个选手 i i i l i , r i l_i,r_i li,ri,在 [ l i , r i ) [l_i,r_i) [li,ri) 上区间 + 1 +1 +1,特别的在 r i r_i ri 上不操作——这样满足全部都是结束点 r r r 的合法位置 p p p 处的和就为 0 0 0。我们线段树维护最小值和区间加操作,在线段树上二分 p p p:如果左区间存在最小值为 0 0 0 的就往左边搜,否则往右边搜。

更新就 O ( 1 ) O(1) O(1) 更新排名信息以及 O ( log ⁡ n ) O(\log n) O(logn) 修改区间和。时间复杂度 O ( n log ⁡ n + q log ⁡ n ) O(n\log n+q\log n) O(nlogn+qlogn)。代码不会很难打,转化之后思路就很明显了。

代码

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define ls u<<1
#define rs u<<1|1
const ll N=1e5+9;
ll n,Q;
ll a[4][N],rk[4][N];
ll l[N],r[N];
void findd(ll x)
{
	l[x]=min({rk[1][x],rk[2][x],rk[3][x]});
	r[x]=max({rk[1][x],rk[2][x],rk[3][x]});
}
struct SegT
{
	struct node
	{
		ll mi,tag;
	}T[N<<2];
	void pushup(ll u)
	{
		T[u].mi=min(T[ls].mi,T[rs].mi);
	}
	void modi(ll u,ll tg)
	{
		T[u].mi+=tg;
		T[u].tag+=tg;
	}
	void pushdown(ll u,ll l,ll r)
	{
		modi(ls,T[u].tag);
		modi(rs,T[u].tag);
		T[u].tag=0;
	}
	void modify(ll u,ll l,ll r,ll ql,ll qr,ll k)
	{
		if(qr<l||r<ql)return;
		if(ql<=l&&r<=qr)
		{
			T[u].mi+=k;
			T[u].tag+=k;
			return;
		}
		pushdown(u,l,r);
		ll mid=(l+r)>>1;
		if(ql<=mid)modify(ls,l,mid,ql,qr,k);
		if(qr>mid)modify(rs,mid+1,r,ql,qr,k);
		pushup(u); 
	}
	ll findpos(ll u,ll l,ll r)
	{
		if(l==r)return l;
		pushdown(u,l,r);
		ll mid=(l+r)>>1;
		if(T[ls].mi==0)return findpos(ls,l,mid);
		return findpos(rs,mid+1,r); 
	}
}A;
void add(ll x)
{
	A.modify(1,1,n,l[x],n,1);
	A.modify(1,1,n,r[x],n,-1);
}
void del(ll x)
{
	A.modify(1,1,n,l[x],n,-1);
	A.modify(1,1,n,r[x],n,1);
}
int main()
{
	freopen("go.in","r",stdin);
	freopen("go.out","w",stdout);
	scanf("%lld%lld",&n,&Q);
	for(int st=1;st<=3;st++)
	{
		for(int i=1;i<=n;i++)
		{
			scanf("%lld",&a[st][i]);
			rk[st][a[st][i]]=i;
		}
	}
	for(int i=1;i<=n;i++)
	{
		findd(i);
		add(i);
	}
	ll chap=0;
	while(Q--)
	{
		ll op,x,u,v;
		scanf("%lld%lld",&op,&x);
		if(op==1)
		{
			if(l[x]<=A.findpos(1,1,n))puts("DA");
			else puts("NE");
		}
		else 
		{
			scanf("%lld%lld",&u,&v);
			del(u);
			del(v);
			swap(rk[x][u],rk[x][v]);
			a[x][rk[x][u]]=u,a[x][rk[x][v]]=v;
			findd(u);
			findd(v);
			add(u);
			add(v);
		}
	}
	return 0;
}

网站公告

今日签到

点亮在社区的每一天
去签到