CF1482D Playlist

也许吧,再次走进这题,是奇迹。要上紫,必须克服的困难,D题。

被压抑的想法,现在可以释放了。

千言万语,一句一句地说。错失的机会,重新抓起。双关。

一个环,从某个点,作为起点,对于当前走到的点x,设下一个点为y,如果y与x互素(即gcd(x,y)=1),那么删除y,然后从y的下一个点开始重新走,否则就走到y。

(题目的叙述,是走到y后再删除y,这个视角感觉不方便)

之前,我们想到了素因子,gcd显然是很容易想到素因子的,紧接着,我们讨论了所有数字都是形如p^k,即单个素数的幂,那么我们可以把环,按照每个数字对应的p,p相同的数字形成连续段。

我们就得到了许多连续段,我们说,当指针(我们当前的位置)重新以顺时针方向跨进或跨过起点的时候,就开启了新的一轮。

那么在同一轮中,会被删除的,就是每一个段的段头(会被上一段的段尾给删除,不论起点设在哪里,都是这些点被删除)

下一轮,如果还是这些段,只不过每个段长度减一,那么可以重复这样的操作,直到,有一个段被彻底删除完,然后可能会有两段,原先不相邻,现在相邻,如果它们对应的是同个p,我们得合并它们。

这个合并的操作用并查集就可以做。

设一开始每个段的段长度,从小到大排列为b1,b2,...,bm,那么我们一次计算,就算到剩余段数有改变为止,而不是一步一步地模拟,花费O(N),删除之前b数组中的所有最小值。

因为b1+b2+...+bm=n,如果有多个bi相同,它们会在同一次O(N)计算被删除,考虑最坏的情况,就是每个bi都不相同,因此我们要删除m次,但是每个bi都不相同,意味着

b1+b2+...+bm>=1+2+3+...+m=m*(m+1)/2,因此m是O(sqrt(n))级别的,所以最后的复杂度是O(nsqrt(n)),可以接受。(总和固定,查重后产生平方,然后反函数成为根号,这些特征常常在根号算法题中出现)

并查集的单步均摊复杂度,可以近似为O(1)。

可以认为我们已经解决了p^k的问题,对于一般的问题,我们也可以按照相邻两数是否互素,如果不互素就放在同一个连续段中,否则就分段。

但是,我们之前的做法,可以预测,除非段有消失的情况,否则每次删除的数字都是初始的段中剩余元素的头部。

但是,对于一般的情况,[...x] [y z...],我们无法保证,当y删除后,z是下一个被删除的元素,可能gcd(x,y)==1,gcd(y,z)!=1, gcd(x,z)!=1

所以,从因子来考虑,而不是从一个数字的因子来考虑,会简单点?因此一个数字的因子,可能有多个,另一个数字的,也可能有多个,或许重合,或许不重合,比较复杂。

先考虑,素因子2,我们按是否含有素因子2来分连续段,然后每次把段数减去min,在这个过程中,会被删除的(x,y)中的y,如果在一般的过程中,我们走到了x,那么y也会被删除。

问题是,在一般的过程中,可能x先被删除了,因此y之后可能是重新开始的,不会被x触发删除。

再次被拒绝于门下。双关。

「不会被x触发删除」那又如何?为什么,我会把这条信息解读成负面的讯号呢?

因为我认为正解需要这个在我眼中缺失的性质,但我并没有权利指导问题按我设想的那样发展,事实与我的想法不符合的时候,错的一定是我。

实际上,如果我们把每个环都放在它一开始的位置上,删除的时候,不要想象成空出的位置被合拢,存在的元素,还是在原来的位置。(这个想法之前做某题用到过,应该写了博客了,忘了具体哪一篇了)

这样有什么好处?我们保留了下标信息,注意到,我们不需要在一个数字被删除的时候记录当前的轮次,实际上我们只需要看数组中还未被标记删除的位置即可。

删除,总是相邻(如果中间有被删除的元素,就跳过)的两个存在的元素,前面的删除后面的,因此当(x,y)把y删除的时候,x和y,在顺时针(这是我们选取的走路方向)方向上,之间间隔的那些点,都是被删除的点,这样我们就能通过x和y的下标之差算出y被删除的轮次了。不对,除非每轮,只在里面删除一个点,实际上是可能删多个的。

也许可以讨论一下删除的形态。我总是考虑初始局面的相邻两位置的删除,但是对于中间间隔了许多已经删除点的,在下标意义上不相邻的两个点的删除,我却没研究过。

是不是,跳太快了,我们先考虑每个数字恰有两个素因子的情况——放弃这条线。

本题最艹的地方,难道不是,每个删除会造成相邻性的改变,导致情况偏离初始局面(而你又不可能枚举后续的局面,那样不就是搜索了,只有初始局面可以利用)

这种删除类的题——我似乎也不是第一次做,可我现在头脑空空如也。。

好了,一切都完结。真的艹,真的绝望。绝望溢出胸口。

这不是告别,

因为我们并没有相见

 

 

感觉可以反过来想,把相邻互素的连成连续段。

这样段与段之间就不互相干了,不会有删除关系发生。每个段处理自己内部的事务。

那么每个连续段内,间隔地删除元素!这个性质好啊。

我看到了希望。

每个连续段,一轮至少删一半,然后下一轮开始前,重新建立一遍连续段。

O(NlogN)

我艹,真水啊。果然想对了点就很简单。

细思,发现这等价于直接模拟。。吐血。。原来直接模拟的复杂度是O(NlogN)的啊

用链表模拟就行了。

嗯?还是TLE。

 

因为每次并不是严格地删一半,如果连续段只有一个元素,那么就不会删,因此,极端的情况,除了一个连续段长度为2,其余都是长度为1的连续段,一轮只删除一个元素,退化为O(N^2)

因此,还真不能直接模拟,还是得用链表处理。

 

感觉贼难写。。因为不是一个链表,每个连续段都是一个链表,合并好难写。

半成品:

#include <bits/stdc++.h>
using namespace std;
#define FOR(i,n) for (int i=1;i<=n;i++)
#define REP(i,a,b) for (int i=a;i<=b;i++)
 
#define pb push_back
#define fi first
#define se second
#define pi pair<int,int>
#define mp make_pair
 
typedef long long ll;
typedef complex<double> comp;
 
const int inf=0x3f3f3f3f;
const ll linf=1e18;
const int N=2e5+10;
const double eps=1e-10;
const ll mo=998244353;

int T;
int n;
struct node {
    int v,p,f;
} a[N];
set<int> s;
int gcd(int a,int b) {
    return (a==0)?b:gcd(b%a,a);
}
int main() {

    std::ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
 
    //freopen("in.txt","r",stdin);
    //freopen("out.txt","w",stdout);
    cin>>T;
    while (T--) {
        cin>>n;
        FOR(i,n) cin>>a[i].v;
        FOR(i,n) a[i].p=i-1,a[i].f=i+1;
        a[1].p=n,a[n].f=1;
        
    }
    return 0;
}

  

posted @ 2021-03-25 20:21  AngelKnows  阅读(48)  评论(0)    收藏  举报