「CEOI2016」路由器(構造)
阿新 • • 發佈:2022-01-25
題意
對於常數 \(n,m,p\),構造一個點集為 \(\{1,2,\cdots,2n+k\}\) 且邊數不超過 \(m\) 的 DAG,滿足:
- \(1,2,\cdots,n\) 只有出邊,且 \(n+1,n+2,\cdots,2n\) 只有入邊。
- \(1,2,\cdots,n\) 中每個點均可到達 \(n+1,n+2,\cdots,2n\)。
- 對於任意兩點 \(x,y\),若 \(x\) 可到達 \(y\),則 \(x\) 到 \(y\) 的路徑唯一。
- 對於任意點 \(x\),可到達 \(x\) 的點數與 \(x\) 可到達的點數的乘積不超過 \(p\)(本題中任意點可到達自己)。
\(n=9978,m=p=10^5\)。
分析
記 \(c=\frac{n}{\sqrt p}\)。把 \([1,n]\) 和 \([n+1,2n]\) 各自均分成 \(c\) 塊,然後對於兩邊的塊建完全二分圖,如下圖:
這樣對圖中的每個紅點,可到達它的點數和它可到達的點數都是 \(\frac{n}{c}=\sqrt p\)。
但總邊數 \(2nc\) 太大了。發現每個塊都向 \(c\) 個紅點連邊,考慮優化這部分。
那麼把每個塊再分 \(c\) 份,然後這樣構造:
這樣每往上一層,可到達 \(x\) 的點數減半,\(x\) 可到達的點數加倍,因此乘積不變。最後邊數是 \(2n+4c^2\log c\)
實現
#include<bits/stdc++.h> #define rep(i,a,b) for(int i=(a),_=(b);i<=_;++i) #define per(i,a,b) for(int i=(a),_=(b);i>=_;--i) #define pb push_back #define IL inline using namespace std; typedef double db; typedef vector<int> VI; //head const int narr[]={118,223,1250,5101,9934,9955,9978}; const int c=32,lim=1e5; int n,t,id; bool rev; struct E{int u,v;}; vector<E> ans; IL int sqr(int x){return x*x;} IL void add(int u,int v){ ans.pb(rev?E{v,u}:E{u,v}); } void solve(VI x,VI y){ VI z; for(int k=c>>1;k;k>>=1){ swap(y,z); y.resize(c); rep(i,0,c-1)y[i]=++t; for(int i=0;i<c;i+=k<<1)rep(j,i,i+k-1){ add(y[j],z[j]),add(y[j+k],z[j+k]); add(y[j+k],z[j]),add(y[j],z[j+k]); } } rep(i,0,c-1){ for(int j=i;j<int(x.size());j+=c){ add(x[j],y[i]); } } } int main(int argc,char *argv[]){ id=atoi(argv[1]); n=narr[id-1]; t=n*2; VI x[40],z[40],y[40],w[40]; rep(i,1,c)for(int j=i;j<=n;j+=c){ x[i].pb(j),z[i].pb(j+n); } rep(k,0,c-1)rep(i,1,c){ y[i].pb(++t); w[(i+k-1)%c+1].pb(t); } rep(i,1,c)solve(x[i],y[i]); rev=1; rep(i,1,c)solve(z[i],w[i]); printf("%d %d\n",t,int(ans.size())); for(E e:ans){ printf("%d %d\n",e.u,e.v); } exit(0); }