【考試總結】2022-03-05
小 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\)
那麼我們取 \(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;
}