F. Magnets
题目描述
解法
不难发现机器返回的是 ((n_1-s_1)(n_2-s_2))
经典思路是找到一个未消磁的磁铁,然后挨个去验即可。
怎么找这个未消磁的磁铁呢?一开始我想的是把每个磁铁和剩下的所有磁铁验证,但是如果剩下磁铁是电中性的就判断不出来。正解是用机器去测 ([1,i-1]) 和 (i),如果第一次遇到有力那么 (i) 一定是为消磁的磁铁,这告诉我们这种一次问多个东西的题应该从少到多考虑
找到这个磁铁是 (n) 次,验证又需要 (n) 次,不是还过不了吗?设找到的磁铁是 (p),注意到 ([1,p-1]) 中有且仅有一个未消磁的磁铁,我们可以用 (log n) 的次数找到他,那么剩下的都是消磁磁铁,对于 ([p+1,n]) 我们暴力验证即可。
#include <cstdio>
#include <vector>
using namespace std;
int read()
{
int x=0,f=1;char c;
while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
return x*f;
}
int T,n,p;
int ask(int x,int y)
{
printf("? %d %d
",x,1);
for(int i=1;i<=x;i++) printf("%d ",i);
printf("
%d
",y);
fflush(stdout);
return read();
}
void work()
{
n=read();p=0;vector<int> rs;
for(int i=2;i<=n;i++)
if(ask(i-1,i)!=0)//there is a force
{
p=i;
break;
}
int l=1,r=p-1,ans=0;
while(l<=r)
{
int mid=(l+r)>>1;
if(ask(mid,p)!=0)
{
ans=mid;
r=mid-1;
}
else l=mid+1;
}
for(int i=1;i<p;i++)
if(i!=ans) rs.push_back(i);
for(int i=p+1;i<=n;i++)
if(ask(ans,i)==0) rs.push_back(i);
printf("! %d",rs.size());
for(int i=0;i<rs.size();i++) printf(" %d",rs[i]);
puts("");fflush(stdout);
}
signed main()
{
T=read();
while(T--) work();
}
G. Switch and Flip
题目描述
给你一个长度为 (n) 的硬币排列,初始 (i) 位置上的硬币是 (c_i),全部朝上,要求把这个硬币序列变成全部朝上的 ([1,n]) 的硬币排列,每次操作是交换两个硬币,然后翻转这两个硬币,最多使用 (n+1) 次操作。
(3leq nleq 2cdot 10^5)
解法
果然是 ( t tourist) 都做不出来的题,确实搅的很,我现在脑子还是痛。
排列问题可以从置换的角度思考,也就是初始有若干个置换环,要求通过操作得到 (n) 个置换环,可以把图建出来,第 (i) 个点有一个连出去的边 ((i,c[i])) ,我们设朝上为红朝下为蓝,操作就是交换两个点的边,这有助于我们思考问题的整体联系。
考虑一个置换环应该怎么做,这里需要有一个观察:如果是红点连向蓝点,那么对他们进行操作可以直接得到一个合法的大小为 (1) 的置换环,我们不妨先搞出两个蓝点(蓝点的数量一定是偶数),那么按顺序把剩下的红点消掉,最后消掉这两个蓝点,如下图:
对于一个长度为 (n) 用掉的操作是 (n+1),但是原图可能有若干个环,都这么做的话还是不行。
那么思考能不能让某些环一起操作来减少操作数,考虑两个环生成两个蓝点并且融合成一个环只需要一步,然后就可以用单个环的方法做了,所以总消耗步数是环大小之和的,如下图:
那么总操作数至多是 (n+1) 的,这里我必须提醒一点:建图只是用来帮助你思考的,但是本题却不能用图来实现代码,因为图上点代表的是权值,但是我们要操作的是位置,如果对着图硬想就凉了!还有如果环长为 (2) 的时候是不能用第一种方法的,所以我们优先找一个长度为 (1) 的置换环和他操作即可,如果整个图只有一个置换环就用第一种方法。
#include <cstdio>
#include <vector>
using namespace std;
const int M = 200005;
#define pii pair<int,int>
int read()
{
int x=0,f=1;char c;
while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
return x*f;
}
int n,p,a[M],b[M];vector<pii> r;
void work(int i,int j)//operation of swap
{
swap(a[i],a[j]);
a[i]=-a[i];a[j]=-a[j];
r.push_back(make_pair(i,j));
}
void cyc(int x,int y)//operation of two cycles
{
work(x,y);
int now=x;//start from here
while(a[-a[now]]>0) work(now,-a[now]);
//after operation,the bule position is still now
now=-a[now];//another blue one maybe is not y
while(a[-a[now]]>0) work(now,-a[now]);
work(now,-a[now]);
}
signed main()
{
n=read();p=-1;
for(int i=1;i<=n;i++)
a[i]=read();
for(int i=1;i<=n;i++)
{
if(b[i]) continue;
int x=i;
while(!b[x]) b[x]=1,x=a[x];
if(p==-1) p=x;//there is no last cycle
else cyc(p,x),p=-1;//operate with the last cycle
}
if(p>0)//there is still a cycle remain
{
int fl=0;
for(int i=1;i<=n;i++)
if(a[i]==i)
{
cyc(p,i);fl=1;//operate with a single element
break;
}
if(!fl)//operate itself
{
int t1=a[p],t2=a[a[p]];
work(p,t1);
cyc(t1,t2);
}
}
printf("%d
",r.size());
for(int i=0;i<r.size();i++)
printf("%d %d
",r[i].first,r[i].second);
}
H. Yuezheng Ling and Dynamic Tree
题目描述
解法
势能分析好题,让张老师做她肯定会
首先如果去维护数的话就太麻烦了,既然这道题给的是序列,那我们在序列上处理这个问题。
考虑对序列分块,这道题的修改是 (max(a_i-x,1)),也就是单向的修改,每个点的父亲只会往前移动,这可以自然地想到势能分析。我们考虑块内要维护什么信息,肯定要维护适合势能分析的东西,那么我们就维护每个点在块内的最小祖先,因为可以一次跳过整块所以这样维护这东西是利于询问的。
考虑如果一个块被整块修改 (sqrt n) 次那么块内所有点最小祖先都会是自己,这时候修改就没有意义了。所以每次修改整块如果有意义就 (O(sqrt n)) 暴力重构,否则就打标记,散块直接暴力重构,使用并查集即可,时间复杂度 (O(nsqrt ncdot alpha))
那么怎么用我们维护的信息查询呢?如果两个点在同一个块内就判断一下是否有同一个祖先,如果有的话暴力跳,否则跳出这个块。如果不在同一个块就直接把编号大的那个点跳出块,时间复杂度也是 (O(nsqrt ncdot alpha))
还有一种做法是维护不在块内的最大祖先,大体思路是一样的,但是时间复杂度 (O(nsqrt n))
#include <cstdio>
#include <iostream>
#include <cmath>
using namespace std;
const int M = 400005;
#define int long long
int read()
{
int x=0,f=1;char c;
while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
return x*f;
}
int n,m,bk,a[M],L[M],R[M],fa[M],pos[M],siz[M],del[M],tot[M];
int find(int x)
{
if(x!=fa[x]) fa[x]=find(fa[x]);
return fa[x];
}
void merge(int x,int y)
{
int u=find(x),v=find(y);fa[v]=u;
}
void make(int b)
{
for(int i=L[b];i<=R[b];i++) fa[i]=i;
for(int i=L[b];i<=R[b];i++)
if(pos[a[i]]==b) merge(a[i],i);
}
void add(int b,int x)
{
tot[b]+=x;
for(int i=L[b];i<=R[b];i++) a[i]=max(a[i]-x,1LL);
make(b);
}
signed main()
{
n=read();m=read();
for(int i=2;i<=n;i++)
a[i]=read();
bk=sqrt(n);
for(int i=1;i<=bk;i++)
{
L[i]=(i-1)*bk+1;R[i]=i*bk;
siz[i]=R[i]-L[i]+1;
for(int j=L[i];j<=R[i];j++) pos[j]=i;
make(i);
}
if(R[bk]!=n)
{
bk++;L[bk]=R[bk-1]+1;R[bk]=n;
siz[bk]=R[bk]-L[bk]+1;
for(int i=L[bk];i<=R[bk];i++) pos[i]=bk;
make(bk);
}
while(m--)
{
int op=read();
if(op==1)
{
int l=read(),r=read(),x=read();
if(pos[l]==pos[r])
{
for(int i=l;i<=r;i++)
a[i]=max(a[i]-x,1ll);
make(pos[l]);
continue;
}
for(int i=pos[l]+1;i<pos[r];i++)
{
if(tot[i]>siz[i]) del[i]+=x;
else add(i,x);
}
for(int i=l;i<=R[pos[l]];i++) a[i]=max(a[i]-x,1ll);
make(pos[l]);
for(int i=L[pos[r]];i<=r;i++) a[i]=max(a[i]-x,1ll);
make(pos[r]);
}
else
{
int u=read(),v=read(),ans=1;
while(u!=1 && v!=1)
{
if(pos[u]==pos[v])//in the same block
{
if(find(u)==find(v))//own the same root in the block
{
while(u!=v)
{
if(u>=v) swap(u,v);
v=max(1ll,a[v]-del[pos[v]]);
}
ans=u;
break;
}
//otherwise the answer isn't in the block
u=max(1ll,a[find(u)]-del[pos[u]]);
v=max(1ll,a[find(v)]-del[pos[v]]);
}
else//otherwise we jump the bigger one out of the block
{
if(u>=v) swap(u,v);
v=max(1ll,a[find(v)]-del[pos[v]]);
}
}
printf("%lld
",ans);
}
}
}
#include <cstdio>
#include <iostream>
#include <cmath>
using namespace std;
const int M = 200005;
int read()
{
int x=0,f=1;char c;
while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
return x*f;
}
int n,m,q,a[M],b[M],L[M],R[M],cnt[M],f[M],c[M];
void build(int w)
{
for(int i=L[w];i<=R[w];i++)
{
a[i]=max(1,a[i]-f[w]);
if(a[i]<L[w]) b[i]=a[i];
else b[i]=b[a[i]];
}
f[w]=0;
}
void upd()
{
int l=read(),r=read(),x=read();
if(c[l]==c[r])
{
for(int i=l;i<=r;i++)
a[i]=max(a[i]-x,1);
build(c[l]);
return ;
}
for(int i=l;i<=R[c[l]];i++)
a[i]=max(a[i]-x,1);
for(int i=L[c[r]];i<=r;i++)
a[i]=max(a[i]-x,1);
build(c[l]);build(c[r]);
for(int i=c[l]+1;i<c[r];i++)
{
if(cnt[i]<m)//brute
{
for(int j=L[i];j<=R[i];j++)
a[j]=max(a[j]-x,1);
build(i);
cnt[i]+=x;
}
else f[i]=min(n,f[i]+x);//tag
}
}
signed main()
{
n=read();q=read();
m=sqrt(n);a[1]=1;
for(int i=2;i<=n;i++)
a[i]=read();
for(int i=1;i<=n;i++)
{
c[i]=(i-1)/m+1;
if(!L[c[i]]) L[c[i]]=i;
R[c[i]]=i;
}
for(int w=1;w<=c[n];w++)
build(w);
while(q--)
{
int op=read();
if(op==1)
{
upd();
continue;
}
int u=read(),v=read();
while(b[u]!=b[v])
{
int t1=max(1,b[u]-f[c[u]]);
int t2=max(1,b[v]-f[c[v]]);
if(c[u]>c[v]) u=t1;
else if(c[u]<c[v]) v=t2;
else u=t1,v=t2;
}
while(u!=v)
{
if(u>v) u=max(1,a[u]-f[c[u]]);
else v=max(1,a[v]-f[c[v]]);
}
printf("%d
",u);
}
}