1. 程式人生 > 其它 >「CEOI2016」路由器(構造)

「CEOI2016」路由器(構造)

傳送門

題意

對於常數 \(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\)

,只有 4w 多。

實現

提交記錄

#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);
}