Codeforces Round #723 div.2 A-F题解

视频讲解:BV1CN411Z76w

A. Mean Inequality

题目大意

给定一个包含 2 n ( 1 ≤ n ≤ 25 ) 2n(1 \leq n \leq 25) 2n(1n25) 个不同的数的数组 a a a ,求数组 b b b 使其满足以下条件:

  • b b b a a a 的一种排列
  • ∀ i ∈ [ 1 , 2 n ] \forall i \in [1,2n] i[1,2n] b i ≠ b i − 1 + b i + 1 2 b_i \neq \frac{b_{i-1}+b_{i+1}}{2} bi=2bi1+bi+1 ,其中 b 0 = b 2 n b_0=b_{2n} b0=b2n b 2 n + 1 = b 1 b_{2n+1}=b_{1} b2n+1=b1

题解

对于 b b b 中任意相邻的三个数,如果中间的数大于左右两个数,或小于左右两个数,那么必定可以满足条件。
对数组 a a a 排序,然后从小到大先填 b b b 中奇数位的数,再填偶数位的数。
那么得到的数组 b b b 中,奇数位的数必定小于相邻两个偶数位的数,偶数位的数必定大于相邻两个奇数位的数。

参考代码

#include<bits/stdc++.h>
#define ll long long
using namespace std;

const int MAXN=110;
int a[MAXN];

int main()
{
	int T,n,i;
	scanf("%d",&T);
	while(T--)
	{
		scanf("%d",&n);
		n*=2;
		for(i=1;i<=n;i++)
		{
			scanf("%d",&a[i]);
		}
		sort(a+1,a+n+1);
		for(i=1;i<=n;i++)
		{
			if(i%2)
				printf("%d ",a[i/2+1]);
			else
				printf("%d ",a[i/2+n/2]);
		}
		puts("");
	}
}

B. I Hate 1111

题目大意

给定整数 x ( 1 ≤ x ≤ 1 0 9 ) x(1 \leq x \leq 10^9) x(1x109) ,判断其等否表示为若干的 11 , 111 , 1111 , 11111 , . . . 11,111,1111,11111,... 11,111,1111,11111,... 之和。
例如:

  • 33 = 11 + 11 + 11 33=11+11+11 33=11+11+11
  • 144 = 111 + 11 + 11 + 11 144=111+11+11+11 144=111+11+11+11

题解

会发现, 1111 = 11 ∗ 101 , 11111 = 11 + 111 ∗ 100 , . . . 1111=11 * 101,11111=11+111 * 100, ... 1111=11101,11111=11+111100,... 即这些数都能表示为 11 11 11 111 111 111 的组合。因此若 x x x 可以表示为 11 a + 111 b ( 0 ≤ a , b ) 11a+111b(0 \leq a,b) 11a+111b(0a,b),则可以拆分,反之不行。
继续推导:
11 a + 111 b = 11 ( a + 10 b ) + b = x 11a+111b=11(a+10b)+b=x 11a+111b=11(a+10b)+b=x

b ≡ x ( m o d    11 ) b \equiv x (mod \; 11) bx(mod11)

a + 10 b = x − b 11 a+10b = \frac{x-b}{11} a+10b=11xb

a = x − b 11 − 10 b ≥ 0 a=\frac{x-b}{11}-10b \geq 0 a=11xb10b0

b b b 最小为 x % 11 x \% 11 x%11 ,因此 ⌊ x 11 ⌋ − 10 ( x % 11 ) ≥ 0 \lfloor \frac{x}{11} \rfloor -10(x\% 11) \geq 0 11x10(x%11)0 则有解,反之不行。

参考代码

#include<bits/stdc++.h>
#define ll long long
using namespace std;

int main()
{
	int T,x,y,n;
	scanf("%d",&T);
	while(T--)
	{
		scanf("%d",&n);
		y=n%11;
		if(n/11>=10*y)
			printf("YES\n");
		else
			printf("NO\n");
	}
}

C1+C2. Potions

题目大意

n ( 1 ≤ n ≤ 2 ⋅ 1 0 3 f o r    E a s y , 1 ≤ n ≤ 2 ⋅ 1 0 5 f o r    H a r d ) n(1 \leq n \leq 2 \cdot 10^3 for \; Easy,1 \leq n \leq 2 \cdot 10^5 for \; Hard) n(1n2103forEasy,1n2105forHard) 个点排列在直线上,每个点上有一瓶药水,喝下第 i i i 个点上的药水会使得生命值增加 a i ( − 1 0 9 ≤ a i ≤ 1 0 9 ) a_i(-10^9 \leq a_i \leq 10^9) ai(109ai109) ,如果为负值则表示会减少 ∣ a i ∣ |a_i| ai
初始生命中为 0 0 0 ,从左到右访问每个点,对于每瓶药水,可以选择喝它或无视它。求生命值始终为非负的情况下,最多能喝几瓶药水。

题解

动态规划解法(Easy)

d p i , j dp_{i,j} dpi,j 表示前 i i i 个点喝 j j j 瓶药水的情况下最后生命的最大值。
d p i , j = m a x ( d p i − 1 , j , d p i − 1 , j − 1 + a i ) dp_{i,j}=max(dp_{i-1,j},dp_{i-1,j-1}+a_i) dpi,j=max(dpi1,j,dpi1,j1+ai)

如果遇到为负,则设为负无穷大表示不合法。
O ( N 2 ) O(N^2) O(N2) 复杂度内可以求出结果。

贪心解法1(Hard)

从左到右遍历每瓶药水:

  • 若能喝则喝;
  • 若不能喝,则考虑之前是否有喝过毒性更强药水,若有则用当前药水替代之。即放弃喝之前毒性更强的药水,选择喝当前药水;

维护历史喝过的药水并查询毒性最强的药水,即 a i a_i ai 最小值,可以用优先队列实现。

假设之前的最优策略为喝下 k k k 瓶药水,那么对于第 i i i 瓶药水:

  • 若能喝第 i i i 瓶药水,则喝下后总数变为 k + 1 k+1 k+1,必然也是最优策略
  • 若不能喝第 i i i 瓶药水,则考虑总数为 k k k 的情况下使得血量尽可能大,即选择之前所有药水中毒性最轻的 k k k 瓶药水喝。这个操作等价考虑用当前药水替代历史喝过的毒性最强的药水。

数学归纳法可以证明这个贪心策略成立。

参考代码(官方标程)
#include <bits/stdc++.h>
using namespace std;

int main(){
	ios_base::sync_with_stdio(false); cin.tie(0);
	
	int n; cin >> n;
	priority_queue<long long, vector<long long>, greater<long long> > pq;
	long long S = 0;
	
	for(int i = 1;i <= n;i++){
		long long x; cin >> x;
		S += x;
		pq.push(x);
		while(S < 0){
			S -= pq.top();
			pq.pop();
		}
	}
	cout << (int) pq.size();
}
贪心解法2(Easy+Hard)

首先所有 a i ≥ 0 a_i \geq 0 ai0 的药水都是能直接喝的,只需考虑 a i < 0 a_i <0 ai<0 的药水最多能喝多少。

h p i hp_i hpi 表示访问第 i i i 个点后的生命值, f ( x ) f(x) f(x) 表示访问第 i i i 个点以后的生命值最小值,即 f ( x ) = m i n i = x n h p i f(x)=min_{i=x}^n{hp_i} f(x)=mini=xnhpi
那么对于第 x x x 瓶药,若 f ( x ) ≥ − a x f(x) \geq -a_x f(x)ax 则能喝,反之会毒死不能喝。

设有 x , y x,y x,y 两瓶药水, 0 > a x > a y 0 > a_x > a_y 0>ax>ay ,即 第 i i i 瓶药水毒性更轻,会发现:

  • x > y x>y x>y ,明显第 x x x 瓶药水对 h p i hp_i hpi 值变化更小,即第 x x x 瓶药水更优;
  • x < y x < y x<y,考虑以下情况:两瓶药水最多只能喝一瓶,即
    f ( x ) ≥ − a x , f ( y ) ≥ − a y , f ( y ) < − a x − a y f(x) \geq -a_x,f(y) \geq -a_y,f(y) < -a_x-a_y f(x)ax,f(y)ay,f(y)<axay

因此得到贪心策略:优先喝毒性小的药水,即 a i a_i ai 更大的药水。

对所有 a i < 0 a_i <0 ai<0 的药水排序,从大到小贪心考虑第 x x x 瓶药水能否喝,即 f ( x ) ≥ − a x f(x) \geq -a_x f(x)ax 是否成立,若能则喝下, h p hp hp 值相应变化。若不能则忽略。

对于Easy版本,直接用数组模拟 h p hp hp 变化即可。
对于Haed版本,用线段树优化。

参考代码
#include<bits/stdc++.h>
#define ll long long
using namespace std;

const int MAXN=200100;

int num[MAXN];

struct Node
{
	int l,r;
	long long sum,inc;
}segtree[MAXN<<2];

void pushUp(int i)
{
	segtree[i].sum=min(segtree[i<<1].sum,segtree[i<<1|1].sum);
}

void pushDown(int i)
{
	if(segtree[i].inc)
	{
		segtree[i].sum+=segtree[i].inc;
		segtree[i<<1].inc+=segtree[i].inc;
		segtree[i<<1|1].inc+=segtree[i].inc;
		segtree[i<<1].sum+=segtree[i].inc;
		segtree[i<<1|1].sum+=segtree[i].inc;
		segtree[i].inc=0;
	}
}

void build(int i,int l,int r)
{
	segtree[i].l=l;
	segtree[i].r=r;
	segtree[i].inc=0;
	if(l==r)
	{
		segtree[i].sum=0;
		return;
	}
	int mid=(l+r)>>1;
	build(i<<1,l,mid);
	build(i<<1|1,mid+1,r);
	pushUp(i);
}

void add(int i,int a,int b,int c)
{
	if(segtree[i].l==a && segtree[i].r==b)
	{
		segtree[i].inc+=c;
		segtree[i].sum+=c;
		return;
	}
	pushDown(i);
	int mid=(segtree[i].l+segtree[i].r)>>1;
	if(b<=mid)
		add(i<<1,a,b,c);
	else if(a>mid)
		add(i<<1|1,a,b,c);
	else
	{
		add(i<<1,a,mid,c);
		add(i<<1|1,mid+1,b,c);
	}
	pushUp(i);
}

long long query(int i,int a,int b)
{
	if(segtree[i].l==a && segtree[i].r==b)
	{
		return segtree[i].sum;
	}
	pushDown(i);
	int mid=(segtree[i].l+segtree[i].r)>>1;
	if(b<=mid)
		return query(i<<1,a,b);
	else if(a>mid)
		return query(i<<1|1,a,b);
	else
		return min(query(i<<1,a,mid),query(i<<1|1,mid+1,b));
}

int cnt=0;
pair<int,int> p[MAXN];

int main()
{
	int n,ans,i,x;
	scanf("%d",&n);
	build(1,1,n);
	ans=0;
	for(i=1;i<=n;i++)
	{
		scanf("%d",&x);
		if(x>=0)
		{
			ans++;
			add(1,i,n,x);
		}
		else
		{
			p[cnt++]=make_pair(x,i);
		}
	}
	sort(p,p+cnt);
	for(i=cnt-1;i>=0;i--)
	{
		add(1,p[i].second,n,p[i].first);
		if(query(1,p[i].second,n)>=0)
			ans++;
		else
			add(1,p[i].second,n,-p[i].first);
	}
	printf("%d\n",ans);
}

D. Kill Anton

题目大意

给定一个仅由 “A”,“N”,“O”,“T” 四种字母构成的字符串 a ( 1 ≤ ∣ a ∣ ≤ 1 0 5 ) a(1 \leq |a| \leq 10^5) a(1a105) ,求 a a a 的一种排列 b b b ,使得 b b b 为变化成 a a a 所需操作数最多的字符串。每次操作可以交换字符串中两个相邻字母。

题解

在最佳答案中,相同的字符将连续出现。
否则的话,例如 a = . . . T . . . A . . . T . . . T . . . T . . . a=...T...A...T...T...T... a=...T...A...T...T...T... ,对于其中的"A"和"T", b b b 有两种方案:

  • b = . . . A . . . T . . . T . . . T . . . T . . . b=...A...T...T...T...T... b=...A...T...T...T...T... ,"A"在T的左
  • b = . . . T . . . T . . . T . . . T . . . A . . . b=...T...T...T...T...A... b=...T...T...T...T...A... ,"A"在T的右侧
  • b = . . . T . . . T . . . A . . . T . . . T . . . b=...T...T...A...T...T... b=...T...T...A...T...T... ,"A"在T之间

明显,第二种方案最优,因为在 a a a 中"A"在"T"中偏左,应该使其在 b b b 中偏右会更好。
如上类推会发现,每种字母都应该在其他字母的某一侧会更优,即相同字母连续出现是最优情况。

因此可以枚举不同字符出现的顺序,找出操作数最大的方案。

由于相同字符连续出现,因此我们可以将 b b b 视为 a a a 按照某一种字母优先级规则排序后的结果。
因为将 b b b 变化为 a a a 的操作数,等于将 a a a 变化为 b b b 的操作数,因此求 b b b a a a 的操作数就变成了对 a a a 进行排序所需的交换次数。而排序的交换次数就等于逆序数对
因为只有四种字母,求逆序数对时可以直接枚举,省略树状数组的优化。

参考代码

#include<bits/stdc++.h>
#define ll long long
using namespace std;

const int MAXN=100100;
const int MAXM=10;
int len,sum[MAXM],rk[MAXM],vis[MAXM],st[MAXM];
ll mx;
char s[MAXN],ans[MAXN],ch[MAXM];
map<char,int> mp;

void dfs(int dep,int id)
{
	int i,j;
	if(dep==4)
	{
		ll cur=0;
		int sm[MAXM];
		memset(sm,0,sizeof(sm));
		for(i=0;i<len;i++)
		{
			for(j=0;j<4;j++)
			{
				if(rk[mp[s[i]]]<rk[j])
					cur+=sm[j];
			}
			sm[mp[s[i]]]++;
		}
		if(cur>mx)
		{
			mx=cur;
			for(i=0;i<4;i++)
			{
				for(j=st[i];j<st[i]+sum[i];j++)
					ans[j]=ch[i];
			}
		}
	}
	for(i=0;i<4;i++)
	{
		if(vis[i])
			continue;
		vis[i]=1;
		st[i]=id;
		rk[i]=dep;
		dfs(dep+1,id+sum[i]);
		vis[i]=0;
	}
}

int main()
{
	int T,i;
	mp['A']=0;mp['N']=1;mp['O']=2;mp['T']=3;
	ch[0]='A';ch[1]='N';ch[2]='O';ch[3]='T';
	scanf("%d",&T);
	while(T--)
	{
		scanf("%s",&s);
		len=strlen(s);
		memset(sum,0,sizeof(sum));
		for(i=0;i<len;i++)
		{
			sum[mp[s[i]]]++;
		}
		mx=-1;
		dfs(0,0);
		for(i=0;i<len;i++)
			printf("%c",ans[i]);
		puts("");
	}
}

E. Oolimry and Suffix Array

题目大意

给定整数 n , k ( 1 ≤ n , k ≤ 2 ⋅ 1 0 5 ) n,k(1\leq n,k \leq 2 \cdot 10^5) n,k(1n,k2105) 和长度为 n n n 后缀数组 s i ( 0 ≤ s i ≤ n − 1 ) s_i(0 \leq s_i \leq n-1) si(0sin1),求长度为 n n n 的由 k k k 种字母构成的字符串中,后缀数组为 s i s_i si 的有多少种。

题解

考虑最少需要多少种字母,才能生成具有给定后缀数组的字符串。

考虑两个的后缀字符串 x y xy xy a b ab ab ,满足 x y < a b xy < ab xy<ab,其中 x , a x,a x,a 为字符, y , b y,b y,b 为字符串。会得到以下结论:

  • y < b y < b y<b ,则必须 x ≤ a x \leq a xa ;
  • y > b y > b y>b ,则必须 x < a x < a x<a

因此当 x y < a b xy < ab xy<ab y > b y > b y>b 时, x ≤ a − 1 x \leq a-1 xa1 ,即需要引入一个新字母满足条件。

后缀数组有以下性质:

  • 排序后相邻两个后缀字符串 s i s_i si s i + 1 s_{i+1} si+1 的首字母必定满足 s i [ 0 ] ≤ s i + 1 [ 0 ] s_i[0] \leq s_{i+1}[0] si[0]si+1[0]

因此我们可以枚举排序后两个相邻的后缀字符串 x y , a b ( x y < a b ) xy,ab(xy < ab) xy,ab(xy<ab) ,判断 y y y b b b 的大小,若 y > b y>b y>b ,则 x < a x < a x<a ,即表示最少所需字母集大小 + 1 +1 +1 ,反之 x ≤ a x \leq a xa

具体而言,枚举后缀数组中两个相邻的数 a r r i , a r r i + 1 arr_i,arr_{i+1} arri,arri+1 ,判断 p o s [ a r r i + 1 ] > p o s [ a r r i + 1 + 1 ] pos[arr_i+1] > pos[arr_{i+1}+1] pos[arri+1]>pos[arri+1+1] 是否成立,其中 p o s [ x ] pos[x] pos[x] 表示 x x x 在后缀数组中的位置。若成立,则表示第 i i i 小的字符必须小于第 i + 1 i+1 i+1 小的字符。这样,就可以得到使得后缀数组成立所需的最小字母集大小 c n t + 1 cnt+1 cnt+1 (这里有 + 1 +1 +1 是因为还有一个最小的字母)。

n n n 个排序后的字符 c i c_i ci 中,字母集大小为 k k k ,有 c n t cnt cnt 个字符比前一个字符大,剩余 n − c n t n-cnt ncnt 个字符不小于前一个字符的方案数即是答案。
考虑在这些字符 c i c_i ci 前添加 c 0 = 1 c_0=1 c0=1 ,在之后添加 c n + 1 = k c_{n+1}=k cn+1=k ,然后对这些字符差分,那么差分后有 c n t cnt cnt 个元素必须 ≥ 1 \geq 1 1 ,其余 n + 1 − c n t n+1-cnt n+1cnt 个元素必须 ≥ 0 \geq 0 0 。那么问题就可以转化为:

  • 给定一个长度为 n + 1 n+1 n+1 的非负数组,求其总和为 k − c n t − 1 k-cnt-1 kcnt1 的方案数

这个问题可以转化:

  • k − c n t − 1 k-cnt-1 kcnt1 个球分为 n + 1 n+1 n+1 个不同的集合的方案数。

额外添加球使得集合要求变为非空:

  • n + k − c n t n+k-cnt n+kcnt 个球分为 n + 1 n+1 n+1 个不同的非空集合的方案数。

隔板法可等价于:

  • n + k − 1 − c n t n+k-1-cnt n+k1cnt 个间隔中选择 n n n 个间隔插上隔板的方案数,即
    C n + k − 1 − c n t n C_{n+k-1-cnt}^{n} Cn+k1cntn

参考代码

#include<bits/stdc++.h>
#define ll long long
using namespace std;

const int MAXN=400400;
const ll mod=998244353;
ll fct[MAXN],invf[MAXN];
int arr[MAXN],pos[MAXN];

ll powmod(ll x,ll p)
{
	ll ret=1;
	while(p)
	{
		if(p&1)
			ret=ret*x%mod;
		x=x*x%mod;
		p>>=1;
	}
	return ret;
}

ll C(ll n,ll m)
{
	if(m>n||m<0)
		return 0;
	return fct[n]*invf[m]%mod*invf[n-m]%mod;
}

int main()
{
	ll n,k,ans,cnt,i;
	fct[0]=1;
	for(i=1;i<MAXN;i++)
		fct[i]=fct[i-1]*i%mod;
	invf[MAXN-1]=powmod(fct[MAXN-1],mod-2);
	for(i=MAXN-2;i>=0;i--)
		invf[i]=invf[i+1]*(i+1)%mod;
	scanf("%lld%lld",&n,&k);
	for(i=0;i<n;i++)
	{
		scanf("%d",&arr[i]);
		pos[arr[i]]=i;
	}
	pos[n]=-1;
	cnt=0;
	for(i=0;i<n-1;i++)
	{
		if(pos[arr[i]+1]>pos[arr[i+1]+1])
			cnt++;
	}
	ans=C(n+k-1-cnt,n);
	printf("%lld\n",ans);
}

F. Median Queries

题目大意

有一个隐藏的长度为 n ( 20 ≤ n ≤ 1 0 5 ) n(20 \leq n \leq 10^5) n(20n105) 1 1 1 n n n 的排序 p p p ,满足 p 1 < p 2 p_1 < p_2 p1<p2 。现在你需要通过交互问答猜出这个序列。

每次询问可以给出三个不同的整数 a , b , c ( 1 ≤ a , b , c ≤ n ) a,b,c(1 \leq a,b,c \leq n) a,b,c(1a,b,cn) ,会返回 { ∣ p a − p b ∣ , ∣ p a − p c ∣ , ∣ p b − p c ∣ } \{ |p_a-p_b|,|p_a-p_c|,|p_b-p_c|\} {papb,papc,pbpc} 其中的中位数。

你需要在 2 n + 420 2n+420 2n+420 次询问内猜出排序 p p p

题解

尝试倒推问题。
q u e r y ( a , b , c ) query(a,b,c) query(a,b,c) 为询问 x , y , z x,y,z x,y,z 得到的结果。

如果我们知道了 1 1 1 2 2 2 所在的位置 x , y x,y x,y ,即 p x = 1 , p y = 2 p_x=1,p_y=2 px=1,py=2 ,那么对于其他数,询问 ( x , y , i ) (x,y,i) (x,y,i) ,即可得到 p i = q u e r y ( x , y , i ) + 2 p_i=query(x,y,i)+2 pi=query(x,y,i)+2 。可以用 n − 2 n-2 n2 次询问得到排序结果。
如果知道 n n n n − 1 n-1 n1 的位置,同样也可以得到结果。

考虑如何找到 1 1 1 2 2 2 的位置或 n n n n − 1 n-1 n1 的位置。
假设我们知道了两个数 a , b a,b a,b 的位置 p o s [ a ] pos[a] pos[a] p o s [ b ] pos[b] pos[b] ,并且这两个数之差 ∣ a − b ∣ |a-b| ab 比较小。那么对于其他数,我们可以询问 ( p o s [ a ] , p o s [ b ] , i ) (pos[a],pos[b],i) (pos[a],pos[b],i) ,得到返回结果最大的 i i i ,必定是 1 1 1 n n n 的位置。第二大结果的 j j j ,必定是 2 2 2 n − 1 n-1 n1 的位置。这样我们可以用 n − 2 n-2 n2 次询问找到 1 1 1 2 2 2 n n n n − 1 n-1 n1

那么 ∣ a − b ∣ |a-b| ab 需要多小才能满足条件呢?会发现应满足
∣ a − b ∣ ≤ ⌊ n − 4 3 ⌋ |a-b| \leq \lfloor \frac{n-4}{3} \rfloor ab3n4

才能保证
q u e r y ( p o s [ 1 ] , p o s [ a ] , p o s [ b ] ) > q u e r y ( p o s [ 2 ] , p o s [ a ] , p o s [ b ] ) > ∣ a − b ∣ query(pos[1],pos[a],pos[b])>query(pos[2],pos[a],pos[b])>|a-b| query(pos[1],pos[a],pos[b])>query(pos[2],pos[a],pos[b])>ab


q u e r y ( p o s [ n ] , p o s [ a ] , p o s [ b ] ) > q u e r y ( p o s [ n − 1 ] , p o s [ a ] , p o s [ b ] ) > ∣ a − b ∣ query(pos[n],pos[a],pos[b])>query(pos[n-1],pos[a],pos[b])>|a-b| query(pos[n],pos[a],pos[b])>query(pos[n1],pos[a],pos[b])>ab

为了找到这样 a , b a,b a,b 的位置,不妨寻找三元组 a , b , c a,b,c a,b,c ,如果满足
q u e r y ( p o s [ a ] , p o s [ b ] , p o s [ c ] ) ≤ ⌊ n − 4 6 ⌋ query(pos[a],pos[b],pos[c]) \leq \lfloor \frac{n-4}{6} \rfloor query(pos[a],pos[b],pos[c])6n4

那么必定可以得到
∣ a − b ∣ ≤ 2 ⋅ ⌊ n − 4 6 ⌋ ≤ ⌊ n − 4 3 ⌋ |a-b| \leq 2 \cdot \lfloor \frac{n-4}{6} \rfloor \leq \lfloor \frac{n-4}{3} \rfloor ab26n43n4

注意,在上述过程中,我们并不需要知道 a , b , c a,b,c a,b,c 的具体值,只需要知道他们的位置 p o s [ a ] , p o s [ b ] , p o s [ c ] pos[a],pos[b],pos[c] pos[a],pos[b],pos[c]

寻找 a , b , c a,b,c a,b,c 的方法,可以通过随机选取,也可以在至少 13 13 13 个数内枚举所有三元组。
关于为什么 13 13 13 个数内至少有一个三元组的询问结果不超过 ⌊ n − 4 6 ⌋ \lfloor \frac{n-4}{6} \rfloor 6n4 的证明如下:

  1. d 1 , d 2 , . . . d 1 2 d_1,d_2,...d_12 d1,d2,...d12 表示相邻两个数的距离,如果任意两个连续距离的都不超过 ⌊ n − 4 6 ⌋ \lfloor \frac{n-4}{6} \rfloor 6n4 ,那么就找到了结果。
  2. 由于 ∑ i = 1 12 d i = n − 1 \sum_{i=1}^{12}{d_i}=n-1 i=112di=n1 ,因此大于最多有 5 5 5 d i d_i di 不小于 ⌊ n − 4 6 ⌋ + 1 \lfloor \frac{n-4}{6} \rfloor+1 6n4+1
  3. 剩余 7 7 7 个最大为 ⌊ n − 4 6 ⌋ \lfloor \frac{n-4}{6} \rfloor 6n4 d i d_i di 中,必定至少有 2 2 2 个是相邻的。即必定有一个三元组的询问结果不超过 ⌊ n − 4 6 ⌋ \lfloor \frac{n-4}{6} \rfloor 6n4

参考代码

#include<bits/stdc++.h>
#define ll long long
using namespace std;

const int MAXN=100100;
int p[MAXN];
vector<int> vec[MAXN];

int query(int a,int b,int c)
{
	int ret;
	cout<<"? "<<a<<" "<<b<<" "<<c<<endl;
	cin>>ret;
	return ret;
}

int main()
{
	ios_base::sync_with_stdio(false), cin.tie(nullptr);
	int T,n,i,j,k,a,b,mx,id1,id2,ret;
	cin>>T;
	while(T--)
	{
		cin>>n;
		a=0;
		for(i=1;i<=13&!a;i++)
			for(j=i+1;j<=13&!a;j++)
				for(k=j+1;k<=13&!a;k++)
				{
					if(query(i,j,k)<=(n-4)/6)
					{
						a=i,b=j;
						break;
					}
				}
		mx=-1;
		for(i=1;i<=n;i++)
		{
			if(i==a||i==b)
				continue;
			ret=query(i,a,b);
			vec[ret].push_back(i);
			mx=max(ret,mx);
		}
		id1=vec[mx][0];
		id2=vec[mx-1][0];
		if(vec[mx-1].size()>=2)
		{
			if(query(id1,vec[mx-1][0],a)>query(id1,vec[mx-1][1],a))
				id2=vec[mx-1][1];
		}
		p[id1]=1;
		p[id2]=2;
		for(i=1;i<=n;i++)
		{
			if(i==id1||i==id2)
				continue;
			p[i]=query(id1,id2,i)+2;
		}
		if(p[1]>p[2])
		{
			for(i=1;i<=n;i++)
				p[i]=n-p[i]+1;
		}
		cout<<"!";
		for(i=1;i<=n;i++)
			cout<<" "<<p[i];
		cout<<endl;
		cin>>n;
		for(i=0;i<=mx;i++)
			vec[i].clear();
	}
}
  • 15
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值