The 2021 CCPC Weihai Onsite

发布时间 2023-04-28 00:19:59作者: sz[sz]

C

挺有意思的题,但要在场上切难度还是挺大的(和前十题不在一个档次)
转成原根后变成模意义下的01背包(原本考虑了一下转生成函数,但是得保留\(np\)位,显然不靠谱;而bitset优化暴力又过不去)。
发现做这个背包的过程是或运算,也就是说有用的操作只会把某些0变成1,那么自然地会想到:每次用\(Poly(log)\)的时间找到要改变的位置,这样总的复杂度就是\(O(pPoly(log))\)的。
但这个要改变的位置的条件比较抽象,是\(f[i]=0,f[i-x]=1\)(另一种情况同理),考虑一遍跳过去的话,很难直接找到下一个满足这个条件的位置。
但通过联想一些做题经验(比如CF-R857-E),发现要找到下一个两段不同的位置是容易的(二分+哈希BIT即可),即\(f[i]=0,f[i-x]=1 或 f[i]=1,f[i-x]=0\)
那如果直接这么找,再判断一下,复杂度如何呢?感性认知上挺对的;实际上:转移形成的有向图形成若干置换环,那么这两种边的个数是一样的。所以总复杂度就是\(O(plog^2p+n)\)了!
实现有些小细节,比如转成原根之后是在\(p-1\)的环上走;不同起点的哈希判断要平移(这个判错会导致复杂度不对而TLE,然后以为其它错导致的);没有赋值操作的特判等。(其实想清楚了也都还好)

点击查看代码
#include <bits/stdc++.h>
using namespace std;
inline int fpw(int a,int x,int P){
    int s=1;
    for(;x;x>>=1,a=1ll*a*a%P) if(x&1) s=1ll*s*a%P;
    return s;
}
inline int prd(int a,int b,int P){return 1ll*a*b%P;}
#define repeat(i,a,b) for(int i=a;i<b;i++)
typedef long long ll;
ll getG(ll n){ // 求 n 最小的原根
    static vector<ll> a; a.clear();
    ll k=n-1;
    repeat(i,2,sqrt(k+1)+1)
    if(k%i==0){
        a.push_back(i); // a 存放 (n-1) 的质因数
        while(k%i==0)k/=i;
    }
    if(k!=1)a.push_back(k);
    repeat(i,2,n){ // 枚举答案
        bool f=1;
        for(auto j:a)
            if(fpw(i,(n-1)/j,n)==1){
                f=0;
                break;
            }
        if(f)return i;
    }
    return -1;
}// BSGS,a 和 mod 互质

typedef unsigned long long ul;
const int N=2e6+5;
const ul B=793999;
struct BIT{
    int n;
    ul a[N];
    void init(int m){
        n=m;
        for(int i=0;i<=n;i++) a[i]=0;
    }
    void upd(int x,ul y){
        //cout<<"upd:"<<x<<" "<<y<<endl;
        x++;
        while(x<=n) a[x]+=y,x+=x&-x;
    }
    ul cnt(int x){
        x++;
        ul s=0;
        while(x) s+=a[x],x-=x&-x;
        return s;
    }
}T;
ul pw[N];
int P,G,n,a[N],id[N];
int f[N];
ul cnt(int l,int r){
    //cout<<"cnt:"<<l<<" "<<r<<" "<<T.cnt(r)-T.cnt(l-1)<<endl;
    return (T.cnt(r)-T.cnt(l-1))*pw[P-1-r];
}
stack<int>q;
int sum;
void work(int x,int last,int end){
   // cout<<x<<" "<<last<<" "<<end<<endl;
    int cal=0;
    while(last<end){
       // cout<<"last="<<last<<endl;
        cal++;
        if(cal>P) return;
        int l=1,r=end-last;
        while(l<r){
            int mid=(l+r)>>1;
            if(cnt(last+1,last+mid)==cnt(last-x+1,last-x+mid)) l=mid+1;
            else r=mid;
        }
       // cout<<"l="<<l<<endl;
        if(cnt(last+1,last+l)==cnt(last-x+1,last-x+l)) last=end;
        else{
            //puts("!");
            last=last+l;
            //cout<<last<<endl;
            if(!f[last]) q.push(last);
        }
    }
    sum+=cal;
}
int main() {
    cin>>P>>n;
    pw[0]=1; for(int i=1;i<P;i++) pw[i]=pw[i-1]*B;
    G=getG(P);
    //if(n==100000) cout<<"G="<<G<<endl;
    int s=1;
    for(int i=0;i<P-1;i++,s=prd(s,G,P)) id[s]=i;

    T.init(P-1);
    int tot=0,ans=1,vs=0;
    for(int i=1;i<=n;i++){
        int op,x;
        scanf("%d%d",&op,&x);
        if(!x){
            ans=0;
            continue;
        }
        x=id[x];
        //cout<<"x="<<x<<endl;
        if(op==0){
            vs=1;
            if(f[x]) continue;
            f[x]=1;
            T.upd(x,pw[x]);
        }
        else a[++tot]=x;
    }
   // cout<<ans<<endl;
    if(!vs){
        cout<<P-1<<endl;
        return 0;
    }
    for(int i=1;i<=tot;i++){
        int x=a[i];
        work(x,x-1,P-2);
        work(x-(P-1),-1,x-1);
        while(!q.empty()){
            int u=q.top();
            q.pop();
            if(f[u]) continue;
            f[u]=1;
            T.upd(u,pw[u]);
        }
        //if(n==100000 && i%1000==0) cout<<"i="<<i<<" sum="<<sum<<endl;
    }
    for(int i=0;i<P-1;i++) if(!f[i]) ans++;
    cout<<ans<<endl;
    return 0;
}