1. 程式人生 > 其它 >【考試總結】2022-03-05

【考試總結】2022-03-05

A.小 G 的約數 B.小 G 的連通圖 C.小 G 的DAG

小 G 的約數

\(\rm LCM(i,j)|n\) 等價於 \(i,j\)\(n\) 的約數,而第三個條件可以匯出我們所求就是約數偏序集合中最長反鏈長度

直接使用 \(\rm Dilworth\) 定理轉化成最小鏈劃分,可以直接使用貪心求解,也就是找到最小元之後掃描沒有被覆蓋的所有元素,如果是當前鏈尾的倍數就新增到鏈裡面

證明就是如果劃分新鏈一定不優秀,而不劃分新鏈跟誰都一樣

Code Display
const int inf=0x3f3f3f3f3f3f3f3f,N=1e6+10;
int Div[N],cnt,n,num[N],tim[N],m;
int pri[N],pri_cnt;
bool fl[N],vis[N];
inline void get_div(int stp,int x){
    if(stp>m) return Div[++cnt]=x,void();
    for(int i=0,pw=1;i<=tim[stp];++i) get_div(stp+1,x*pw),pw*=num[stp];
    return ;
}
signed main(){
    freopen("divisor.in","r",stdin); freopen("divisor.out","w",stdout);
    n=1e6;
    for(int i=2;i<=n;++i){        
        if(!fl[i]) pri[++pri_cnt]=i;
        for(int j=1;j<=pri_cnt&&pri[j]*i<=n;++j){
            fl[i*pri[j]]=1;
            if(i%pri[j]==0) break; 
        }
    }
    n=read();
    for(int i=1;pri[i]*pri[i]<=n;++i){
        if(n%pri[i]==0){
            num[++m]=pri[i];
            while(n%pri[i]==0) n/=pri[i],tim[m]++;
        }
    }
    if(n>1) num[++m]=n,tim[m]=1;
    get_div(1,1);
    sort(Div+1,Div+cnt+1);
	int ans=0;
	for(int i=1;i<=cnt;++i) if(!vis[i]){
		int now=Div[i]; vis[i]=1;
		for(int j=i+1;j<=cnt;++j) if(!vis[j]&&Div[j]%now==0) vis[j]=1,now=Div[j];
		++ans;
	}
	print(ans);
    return 0;
}

小 G 的連通圖

\(L=\prod\limits_{i\in\{{\rm{prime}}\},i\le n} i\) 那麼發現 \(L+1\)\([L,L+n-1]\) 中的其它點都不連通

此時找到一個大於 \(\sqrt n\)\(a\) 滿足 \(a\ {\text{is prime}},\exists,(k+1)a+1<n,ka+1\ {\text{is prime}}\)

推導的步驟就是發現 \(a\)\(L\) 的質因子集合拿走之後 \(L,L+a\) 可能不連通

這時候發現 \(L+(k+1)a+1\) 一定和 \(L+1\) 聯通那麼拿走 \(ka+1\)

並賦上 \((k-1)a+1\) 這個餘數就能實現了

那麼我們取 \(L\) 是其它素數的倍數,且 \(L\equiv a-1\mod a,L\equiv (k-1)a+1\mod ka+1\)

沒有嚴謹方法證明對於 \(\forall n,\exists k\) 但是在本題資料範圍下取 \(k=2\) 就都能找到了

最後要做的工作就是簡單的中國剩餘定理,寫個高精就行了

注意上面的做法只在 \(n\ge 35\) 的時候奏效,那麼較小的部分寫個暴力就行了。

我的暴力有點門道的樣子,把每個 \(n\) 的方案打印出來看看發現變化的質因子都是一個字尾,同時數量不多,那麼可以簡單搜尋字尾的調整方案就能跑過 \(n\le 200\)

Code Display
const int N=100010;
int n;
struct DSU{
    int fa[N];
    inline void init(int n){rep(i,1,n) fa[i]=i;}
    inline int find(int x){return fa[x]==x?x:fa[x]=find(fa[x]);}
    inline void merge(int x,int y){fa[find(x)]=find(y);}
}T;

inline bool ok(int x){
    for(int i=2;i*i<=x;++i) if(x%i==0) return 0;
    return 1;
}
bool fl;
int rem[N],pri[N],pri_cnt;
inline bool check(){
    T.init(n);
    for(int i=1;i<=pri_cnt;++i){
        int lst=-1,tmp=rem[i];
        for(int j=1;j<=n;++j){
            if(tmp==0){
                if(~lst) T.merge(lst,j);
                lst=j;
            }tmp=tmp+1==pri[i]?0:tmp+1;
        }
    }
    int Set=0;
    for(int i=1;i<=n;++i) if(T.find(i)==i) ++Set;
    return Set==1;
}
const int Bas=100000000;
struct node{
    vector<int> a;
    inline void adjust(){
        int siz=a.size();
        for(int i=0;i<siz-1;++i) a[i+1]+=a[i]/Bas,a[i]%=Bas;
        while(a[siz-1]>=Bas){
            a.emplace_back(a[siz-1]/Bas);
            a[siz-1]%=Bas;
            ++siz;
        }
        while(siz>1&&!a[siz-1]) a.pop_back();
        return ;
    } 
    node operator +(const node &b)const{
        node res;
        int s1=a.size(),s2=b.a.size(); res.a.resize(max(s1,s2));
        for(int i=0;i<s1;++i) res.a[i]+=a[i];
        for(int i=0;i<s2;++i) res.a[i]+=b.a[i];
        res.adjust();
        return res;
    }
    node operator *(const int &b)const{
        node res=*this; 
        for(auto &t:res.a) t*=b;
        res.adjust();
        return res;
    }
    inline void output(){
        int siz=a.size(); print(a.back());
        for(int i=siz-2;~i;--i) printf("%08lld",a[i]);
        return ;
    }
};
inline void get_ans(){
    node ans; ans.a.push_back(0);
    for(int i=1;i<=pri_cnt;++i) if(rem[i]){
        int t=1;
        for(int j=1;j<=pri_cnt;++j) if(i!=j) t=mul(t,pri[j],pri[i]);
        t=ksm(t,pri[i]-2,pri[i]);
        node tmp; tmp.a.push_back(1);
        for(int j=1;j<=pri_cnt;++j) if(i!=j) tmp=tmp*pri[j];
        ans=ans+tmp*t*rem[i];
    }
    ans.output();
    return ;
}
inline void dfs(int stp){
    if(fl) return ;
    if(stp>pri_cnt){
        if(!check()) return ;
        fl=1;
        return ;
    }
   for(int i=0;i<pri[stp];++i){
        int now=i,cnt0=0;
        rem[stp]=i;
        for(int j=1;j<=n;++j){
            cnt0+=!now;
            now=now+1==pri[stp]?0:now+1;
        }
        if(cnt0==1) continue;
        dfs(stp+1);
        if(fl) return ;
    }
    return ;
}
inline void adj(int stp){
    if(fl) return ;
    if(stp>pri_cnt){
        if(!check()) return ;
        fl=1;
        return ;
    }
    adj(stp+1);
    if(fl) return ;
    int lst=rem[stp];
    for(int i=0;i<pri[stp];++i){
        int now=i,cnt0=0; rem[stp]=i;
        for(int j=1;j<=n;++j){
            cnt0+=!now;
            now=now+1==pri[stp]?0:now+1;
        }
        if(cnt0==1) continue;
        adj(stp+1);
        if(fl) return ;
    }
    rem[stp]=lst;
    return ;
}
bool isp[N];
signed main(){
    freopen("graph.in","r",stdin); freopen("graph.out","w",stdout);
    int Q=read(); 
    if(Q==1) puts("1"),exit(0);
    if(Q<=16) puts("No solution"),exit(0);
    if(Q<=200){
        n=17;
        for(int i=2;i<n;++i) if(ok(i)) pri[++pri_cnt]=i;
        dfs(1);
        for(int i=18;i<=Q;++i){
            n=i; fl=0;
            if(ok(i-1)) pri[++pri_cnt]=i-1;
            adj(1);
        }
        get_ans();   
    }else{
        n=Q;
        int pid[N]={};
        for(int i=2;i<=n;++i){
            if(!isp[i]) pri[++pri_cnt]=i,pid[i]=pri_cnt;
            for(int j=1;j<=pri_cnt&&i*pri[j]<=n;++j){
                isp[i*pri[j]]=1;
                if(i%pri[j]==0) break;
            }
        }
        for(int i=sqrt(n)+1;i<=n;++i){
            if(!isp[i]&&!isp[i<<1|1]){
                rem[pid[i]]=i-1;
                rem[pid[i<<1|1]]=i+1;
                break;
            }
        }
        get_ans();
    }
    return 0;
}

小 G 的 DAG

考慮每 \(\sqrt Q\) 個詢問統一回答,那麼只對這 \(\sqrt Q\) 個點做其他點到其的可達性統計

每個塊裡面的覆蓋操作可以在結束這個塊的回答之後使用 \(\Theta(n)\) 的拓撲排序來維護,當前塊的覆蓋操作可以暴力

那麼在回答這個塊裡面的詢問的時候先找到當前詢問的點 \(qx\) 最後一次覆蓋操作發生的時間

再對 \(2\) 操作維護 \(Mn_{i,j}\) 表示第 \(j\) 個點在第 \(i\) 個塊中的所有 \(2\) 操作取的最小的 \(\min\) 是幾,這也可以在每個塊結束之後使用拓撲排序維護

那麼找到 \(qx\) 最後一次覆蓋操作之後本質上就是對 \(2\) 操作的區間求 \(\min\),使用上面維護的 \(Mn_{i,j}\) 算整塊,小塊直接暴力掃

Code Display
const int N=1e5+10,inf=0x3f3f3f3f;
bitset<600>to[100010];
int Tim,mark[N],n,m,Q;
vector<int> G[N];
int opt,v;
int in[N],deg[N],id[N];

inline void get_ava(int x){
    if(mark[x]==Tim) return ; mark[x]=Tim;
    if(id[x]) to[x][id[x]]=1;
    for(auto t:G[x]) get_ava(t),to[x]|=to[t];
    return ;
}
int typ[N],qu[N],qv[N],block;
int tim[N],val[N],bel[N];
struct node{int p,v,t;};
int Mn[N/300][N];

inline int ask(int x,int L,int R){
    assert(id[x]);
    int ans=inf;
    if(bel[L]>=bel[R]-1){
        for(int i=L;i<=R;++i) if(typ[i]==2&&to[qu[i]][id[x]]) ckmin(ans,qv[i]);
    }else{
        int bed=bel[L]*block,bst=(bel[R]-1)*block+1;
        for(int i=L;i<=bed;++i) if(typ[i]==2&&to[qu[i]][id[x]]) ckmin(ans,qv[i]);
        for(int i=bst;i<=R;++i) if(typ[i]==2&&to[qu[i]][id[x]]) ckmin(ans,qv[i]);
        for(int i=bel[L]+1;i<bel[R];++i) ckmin(ans,Mn[i][x]);
    }
    return ans;
}

signed main(){
    freopen("dag.in","r",stdin); freopen("dag.out","w",stdout);
    n=read(); m=read(); Q=read();
    memset(Mn,0x3f,sizeof(Mn));
    for(int i=1;i<=m;++i){
        int u=read(),v=read();
        G[u].push_back(v);
        in[v]++;
    }
    vector<int> sts;
    for(int i=1;i<=n;++i) if(!in[i]) sts.push_back(i);
    block=2*sqrt(Q);
    for(int i=1;i<=Q;++i){
        bel[i]=(i-1)/block+1;
        typ[i]=read(),qu[i]=read();
        if(typ[i]<=2) qv[i]=read();
    }
    int qcnt=0;
    for(int bb=1;bb<=bel[Q];++bb){
        int L=(bb-1)*block+1,R=min(Q,bb*block);
        int num=0;
        vector<int> nodes;
        vector<node> cov;
        for(int i=L;i<=R;++i) if(typ[i]==3&&!id[qu[i]]) id[qu[i]]=++num,nodes.push_back(qu[i]);
        ++Tim;
        for(auto t:sts) get_ava(t);
        for(int i=L;i<=R;++i){
            if(typ[i]==1) cov.push_back({qu[i],qv[i],i});
            else if(typ[i]==3){
                int lsttim=tim[qu[i]],ans=val[qu[i]];
                int siz=cov.size();
                for(int j=siz-1;~j;--j) if(to[cov[j].p][id[qu[i]]]){
                    lsttim=cov[j].t;
                    ans=cov[j].v;
                    break;
                }
                ckmin(ans,ask(qu[i],lsttim+1,i));
                print(ans);
            }else ckmin(Mn[bb][qu[i]],qv[i]);
        }
        for(auto t:cov) tim[t.p]=t.t,val[t.p]=t.v;
        queue<int> q;
        for(int i=1;i<=n;++i){
            deg[i]=in[i];
            if(!deg[i]) q.push(i);
        }
        while(q.size()){
            int fr=q.front(); q.pop();
            for(auto t:G[fr]){
                if(tim[fr]>tim[t]) tim[t]=tim[fr],val[t]=val[fr];
                ckmin(Mn[bb][t],Mn[bb][fr]);
                if(!(--deg[t])) q.push(t);
            }
        }
        for(auto t:nodes) id[t]=0;
        rep(i,1,n) to[i].reset();
    }
    return 0;
}