P5208 [WC2019] I 君的商店 题解
题目描述
\(T\) 组数据,有 \(n\) 个 \(01\) 变量,初始值未知,保证至少存在一个 \(1\) 。
交互库初始会告诉你 \(1\) 的个数的奇偶性,你的目标是求出哪些变量的值是 \(1\) 。
你需要实现以下函数:
-
find_price(int id,int n,int k,int *ans)。其中 \(id\) 为子任务编号, \(n\) 为数组长度, \(k\in\{0,1\}\) 表示 \(1\) 的个数奇偶性,你需要将每个变量的值放在 \(ans\) 数组中。
你可以调用以下函数:
-
int query(int *S,int nS,int *T,int nT)。你可以传入两个不交集合,然后交互库会告诉你哪个集合 \(1\) 的个数更多。
如果两个集合 \(1\) 的个数相等,交互库会按照自己的方式选择一个集合告诉你。
显然你不能无限询问下去,因此交互库设定了集合大小( \(\sum (|S|+|T|)\) )的上限 \(m\) ,你不能违反这个限制。
数据范围
- \(id=1\) : \(1\le n\le 5,m=100\) 。
- \(id=2\) : \(1\le n\le 10^3,m=10^6\) 。
- \(id=3\) : \(1\le n\le 10^5, m=100\) ,保证 \(ans\) 数组单调。
- \(id=4\) : \(1\le n\le 5\cdot 10^4,m=350100\) 。
- \(id=5\) : \(1\le n\le 10^5,m=500100\) 。
分析
宣传: 一篇有趣的题解 ,本文在一定程度上参考了这篇文章。
算法一
先看子任务 \(3\) ,显然目标是二分出边界点。
先比较 \(x_1\) 和 \(x_n\) ,如果 \(x_1\le x_n\) 就翻转整个序列,从而把 \(1\) 放前面, \(0\) 放后面。
由于题目保证至少有一个 \(1\) ,所以 \(x_1=1\) 。
每次拿相邻两个数与 \(x_1\) 比较:
| \(x_i\) | \(x_{i+1}\) | \(query(\{x_i,x_{i+1}\},\{1\})\) |
|---|---|---|
| 1 | 1 | 1 |
| 1 | 0 | 0,1 都有可能 |
| 0 | 0 | 0 |
先正常二分,假设 query({l,l+1},1)=1,query({r,r+1},1)=0,r-l=1 ,那么我们能确定 \(x[1\sim l]=1\) , \(x[r+1\sim n]=0\) ,但 \(x_r\) 无法确定。
但是别忘了奇偶性! \(x_r\) 可以通过奇偶性反推出来。
于是我们用 \(3\log n+2\) 的代价解决了子任务 \(3\) ,期望得分 \(9pts\) 。
算法二
根据数据范围,大胆猜测子任务 \(3\) 为本题的最后一个步骤。
再来看子任务\(1,2\),目标变为在 \(\mathcal O(n^2)\)的时间内将所有元素倒序排序。
将所有数两两比较一次。
Key observation:按照每个数 \(\ge\) 其他数的次数排序即可!
这是因为,如果 \(x_i=1,x_j=0\) ,总共有 \(t\) 个 \(0\) ,那么 \(x_i\) 至少 \(\ge t\) 个数,而 \(x_j\) 至多\(\ge t-1\) 个数。
总代价 \(n(n-1)+3\log n+2\) ,期望得分 \(40pts\) 。
#include<bits/stdc++.h>
#define fi first
#define se second
#define mp make_pair
#define pii pair<int,int>
using namespace std;
const int maxn=1e5+5;
int p[maxn],s[maxn],t[maxn];
pii v[maxn];
int query(int*,int,int*,int);
void find_price(int id,int n,int k,int *ans)
{
for(int i=0;i<n;i++) p[i]=i;
if(id<=2)
{
for(int i=0;i<n;i++) v[i]=mp(0,i);
for(int i=0;i<n;i++)
for(int j=i+1;j<n;j++)
s[0]=i,t[0]=j,v[!query(s,1,t,1)?i:j].fi++;
sort(v,v+n,greater<pii>());
for(int i=0;i<n;i++) p[i]=v[i].se;
}
else
{
s[0]=0,t[0]=n-1;
if(query(s,1,t,1)) reverse(p,p+n);
}
int l=0,r=n-1;
while(r-l>1)
{
int mid=(l+r)/2;
s[0]=p[mid],s[1]=p[mid+1],t[0]=p[0];
if(query(s,2,t,1)) r=mid;
else l=mid;
}
for(int i=0;i<=l;i++) ans[p[i]]=1;
for(int i=r+1;i<n;i++) ans[p[i]]=0;
ans[p[r]]=k-r&1;
}
算法三
\(\mathcal O(n^2)\) 的排序显然不优,但是询问 \(2\) 个数本质就是比较大小,如果两数不同则可以得到正确结果,如果相同虽然结果随机,但不影响排序。
随便换成一个 \(\mathcal O(n\log n)\) 的排序即可通过子任务 \(4\) ,期望得分 \(52pts\) 。
算法四
基于比较的排序下界复杂度为 \(\mathcal O(n\log n)\) ,因此上述算法无法继续优化。
题目保证了至少有一个 \(1\) ,扫描整个数组可以用 \(2n\) 的代价把最大值(必然是 \(1\) )找出来。
接下来执行以下算法:
-
如果只有 \(1\) 个数没有确定,用奇偶性解决。
-
否则至少有 \(2\) 个数 \(x,y\) 没有确定。
先对 \(x,y\) 比较大小,不妨 \(x\ge y\) 。
再比较 \(\{x,y\}\) 和最大值的大小,有三种情况:
- \(x=1,y=1\) : \(ans=0\) 。
- \(x=1,y=0\) : \(ans=0/1\) 。
- \(x=0,y=0\) : \(ans=1\) 。
换言之:
- 若 \(ans=0\) ,则 \(x=1\) 。
- 若 \(ans=1\) ,则 \(y=0\) 。
因此我们可以用 \(5n\) 的代价确定一个数!
算上前面找最大值的开销,总代价 \(7n\),期望得分 \(60pts\) (没拼子任务 \(3\) )。
#include<bits/stdc++.h>
using namespace std;
const int maxn=1e5+5;
int s[maxn],t[maxn];
queue<int> q;
int query(int*,int,int*,int);
void find_price(int id,int n,int k,int *ans)
{
int p=0;
for(int i=1;i<n;i++)
{
s[0]=p,t[0]=i;
if(query(s,1,t,1)) p=i;
}
k^=1,ans[p]=1;
for(int i=0;i<n;i++) if(i!=p) q.push(i);
while(q.size()>=2)
{
int u=q.front();
q.pop();
int v=q.front();
q.pop();
s[0]=u,t[0]=v;
if(query(s,1,t,1)) swap(u,v);
s[0]=u,s[1]=v,t[0]=p;
if(query(s,2,t,1)) ans[v]=0,q.push(u);
else k^=1,ans[u]=1,q.push(v);
}
if(!q.empty()) ans[q.front()]=k,q.pop();
}
算法五
从 \(7n\) 到 \(5n\) ,很明显,我们不能直接把最大值求出来了。
任选一个数 \(v\) 代替最大值,然后比较 \(\{x,y\}\) 和 \(v\) 的大小(不妨 \(x\ge y\) ):
- 若 \(ans=0\) ,即 \(x+y\ge v\) ,因此 \(x\ge v\) 。记录这组偏序关系,然后用 \(x\) 替换 \(v\) ,再找一个新数放在 \(x\) 的位置继续比较。
- 若 \(ans=1\) ,则 \(x+y\le v\) ,因此 \(y=0\) ,再找一个新数放在 \(y\) 的位置继续比较。
这样我们能得到什么?能得到一堆 \(0\) ,一条偏序关系构成的链,和一个孤立点 \(p\) (如果有 \(\ge 2\) 个孤立点就可以继续比较)。
记 \(x\) 为链顶,注意我们还没用到最大值为 \(1\) 这个条件,而最大值只可能在 \(x\) 和 \(p\) 之间产生。
对 \(x\) 和 \(p\) 做一次比较,可以得到最大值。
-
如果 \(p=1\) ,把 \(p\) 接在链顶,然后套用子任务 \(3\) 的做法。
-
如果 \(x=1\) ,根据子任务 \(3\) ,二分后链上最多只有一个点 \(q\) 的值不确定。
回顾子任务 \(4\) ,拿 \(\{p,q\}\) 和 \(1\) 比较可以确定其中一个数,另一个用奇偶性解决。
总代价 \(5n+3\log n+\mathcal O(1)\) 。
#include<bits/stdc++.h>
using namespace std;
const int maxn=1e5+5;
int c[maxn],s[maxn],t[maxn];
int query(int *,int ,int *,int );
int cmp(int x,int y)
{
s[0]=x,t[0]=y;
return query(s,1,t,1);
}
int calc(int x,int y,int v)
{
s[0]=x,s[1]=y,t[0]=v;
return query(s,2,t,1);
}
void find_price(int id,int n,int k,int *ans)
{
if(n<=2||id==3)
{
for(int i=0;i<n;i++) c[i]=i;
if(cmp(0,n-1)) reverse(c,c+n);
int l=0,r=n-1;
while(r-l>1)
{
int mid=(l+r)/2;
if(calc(c[mid],c[mid+1],c[0])) r=mid;
else l=mid;
}
for(int i=0;i<=l;i++) ans[c[i]]=1;
for(int i=r+1;i<n;i++) ans[c[i]]=0;
ans[c[r]]=k-r&1;
return ;
}
int m=1,x=-1,y=0,v=1;
c[1]=v;
for(int i=2;i<n;i++)
{
if(x==-1) x=i;
if(y==-1) y=i;
if(cmp(x,y)) swap(x,y);
if(calc(x,y,v)) ans[y]=0,y=-1;
else c[++m]=x,v=x,x=-1;
}
int p=max(x,y);
if(cmp(v,p)) c[++m]=p,p=-1;
reverse(c+1,c+m+1);
int l=1,r=m;
while(r-l>1)
{
int mid=(l+r)/2;
if(calc(c[mid],c[mid+1],c[1])) r=mid;
else l=mid;
}
for(int i=1;i<=l;i++) ans[c[i]]=1;
for(int i=r+1;i<=m;i++) ans[c[i]]=0;
if(r!=1)
{
int q=c[r];
if(p==-1) p=q;
else
{
if(cmp(p,q)) swap(p,q);
if(calc(p,q,c[1])) ans[q]=0;
else ans[p]=1,p=q;
}
}
if(p!=-1)
{
for(int i=0;i<n;i++) if(i!=p) k^=ans[i];
ans[p]=k;
}
}
本文来自博客园,作者:peiwenjun,转载请注明原文链接:https://www.cnblogs.com/peiwenjun/p/16391933.html
浙公网安备 33010602011771号