1. 程式人生 > 其它 >RMI 2021【雜題】

RMI 2021【雜題】

傳送門:Day 1Day 2

present

將所有滿足 \(x,y\in A\implies\gcd(x,y)\in A\)\(A\subset\mathbb N_+\)\(\sum_{a\in A}\omega^a\) 排序求第 \(k\) 小。

\(T\le 5\)\(k\le 1.5\cdot 10^9\)

solution

猜測 \(m:=\max A\) 不會很大,將 \([m]\) 劃分為奇數和偶數兩部分,奇數對偶數的限制為只能選某個子集內的數,於是 mitm,求答案按位貪心即可,時間複雜度 \(\mathcal O(Tm2^{m/2})\)

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 1 << 19, biao[] = {0,2,4,7,13,22,38,67,121,208,346,663,1067,2084,3650,5621,10187,20228,33960,67673,106919,167302,316644,632549,988585,1672754,3243116,5502723,9032101,18060326,26876518,53747047,97409341,162001788,320354230,488138971,761529731,1523024388};
int T, k[5], _[38][38], mzk[N], sum[N];
vector<int> ans[5];
bitset<N> oke, okl;
int main(){
	ios::sync_with_stdio(false);
	cin >> T;
	for(int i = 0;i < T;++ i) cin >> k[i];
	for(int i = 1;i < 38;++ i)
		for(int j = 1;j < 38;++ j)
			_[i-1][j-1] = __gcd(i, j);
	for(int m = 1;m < 38;++ m){
		bool flg = false;
		for(int i = 0;i < T;++ i) flg |= k[i] >= biao[m-1] && k[i] < biao[m];
		if(!flg) continue;
		int md = m>>1, le = 1<<md, lo = 1<<m-md;
		memset(mzk, 0, lo<<2);
		oke.reset(); okl.reset();
		for(int S = 0;S < le;++ S){
			bool flg = true;
			for(int i = 0;i < md && flg;++ i) if(S >> i & 1)
				for(int j = i+1;j < md && flg;++ j)
					if((S >> j & 1) && !(S >> _[i][j]-1 & 1))
						flg = false;
			if(flg) oke.set(S);
		}
		for(int S = 0;S < lo;++ S){
			bool flg = true;
			for(int i = 0;i < m-md && flg;++ i) if(S >> i & 1)
				for(int j = i+1;j < m-md && flg;++ j)
					if((S >> j & 1) && !(S >> (_[i<<1][j<<1]>>1) & 1))
						flg = false;
			if(!flg) continue;
			okl.set(S);
			for(int j = 0;j < md;++ j){
				flg = true;
				for(int i = 0;i < m-md && flg;++ i)
					if((S >> i & 1) && !(S >> (_[i<<1][j]>>1) & 1))
						flg = false;
				if(flg) mzk[S] |= 1 << j;
			}
		}
		auto calc = [&](int od0, int od1, int ev0, int ev1){
			vector<int> tmp; tmp.clear();
			for(int i = 0;i < md;++ i)
				if(ev1 >> i & 1) tmp.push_back(i);
			int L = tmp.size(), lim = 1<<L;
			for(int i = 0;i < lim;++ i){
				int S = 0;
				for(int j = 0;j < L;++ j)
					if(i >> j & 1) S |= 1 << tmp[j];
				sum[i] = oke[ev0 | S];
			}
			for(int md = 1;md < lim;md <<= 1)
				for(int i = 0;i < lim;i += md<<1)
					for(int j = 0;j < md;++ j)
						sum[i | j | md] += sum[i | j];
			int res = 0;
			for(int i =	od1;;i = i-1 & od1){
				if(okl[od0 | i] && (mzk[od0 | i] & ev0) == ev0){
					int hah = 0;
					for(int j = 0;j < L;++ j)
						if(mzk[od0 | i] >> tmp[j] & 1) hah |= 1 << j;
					res += sum[hah];
				}
				if(!i) break;
			}
			return res;
		};
		for(int i = 0;i < T;++ i) if(k[i] >= biao[m-1] && k[i] < biao[m]){
			int od0 = 0, od1 = lo-1, ev0 = 0, ev1 = le-1;
			k[i] -= biao[m-1];
			(m & 1 ? od0 : ev0) |= 1 << (m-1>>1);
			(m & 1 ? od1 : ev1) &= ~(1 << (m-1>>1));
			ans[i].push_back(m);
			for(int j = m-1;j;-- j){
				(j & 1 ? od1 : ev1) &= ~(1 << (j-1>>1));
				int res = calc(od0, od1, ev0, ev1);
				if(k[i] >= res){
					k[i] -= res;
					(j & 1 ? od0 : ev0) |= 1 << (j-1>>1);
					ans[i].push_back(j);
				}
			}
			reverse(ans[i].begin(), ans[i].end());
			k[i] = 0;
		}
	}
	for(int i = 0;i < T;++ i){
		cout << ans[i].size();
		for(int u : ans[i]) cout << ' ' << u;
		cout << '\n';
	}
}

NoM

給定正整數 \(n,m\),求長為 \(2n\) 的排列 \(p\) 滿足 \(|p_{2i}-p_{2i-1}|\ne m\) 的數量 \(\bmod(10^9+7)\)

\(m\le n\le 2000\)

solution

直接容斥,列舉有 \(i\) 個限制不滿足,設 \(f_i\) 表示在 \((2n\bmod m)\) 個長為 \(\lceil 2n/m\rceil\) 的鏈和 \(m-(2n\bmod m)\) 個長為 \(\lfloor 2n/m\rfloor\) 的鏈中選 \(i\) 個匹配的方案數,則答案為 \(\displaystyle\sum_{i=0}^n(-1)^i\binom ni2^i(2n-2i)!f_i\)

,多項式求冪即可,時間複雜度 \(\mathcal O(n^2)\)

WeirdTree

給定長為 \(n\) 的整數序列 \(a_1,\cdots,a_n\)\(q\) 次詢問形如:

  • 給定 \(l,r,k\)\(k\) 次”將最靠左的最大值減 \(1\)“;
  • 給定 \(i,x\),令 \(a_i:=x\)
  • 給定 \(l,r\),求 \(\sum_{i=l}^ra_i\)

\(n,q\le 3\cdot 10^5\)\(x,k,a_i\le 10^9\),強制線上。

solution

Segbeats 板子。

Paths

給定 \(n\) 個點的樹和正整數 \(k\),邊帶非負權,設 \(\text{path}(u,v)\)

表示 \(u\)\(v\) 簡單路徑的邊集,對所有 \(r\in [n]\)\(\bigcup_{i=1}^k\text{path}(r,v_i)\) 的權值和的最大值,其中 \(v_1,\cdots,v_k\) 是任意樹上的點。

\(k,n\le 10^5\)\(w_i\le 10^9\)

solution

經典結論是貪心選 \(k\) 次使得答案增加最多的路徑,暴力即得 \(\mathcal O(n^2\log n)\) 的做法。

需要仔細觀察,設 \(\text{val}(x)\) 表示對於葉子 \(x\),選擇它時答案的增量;以 \(1\) 為根,設 \(d_x,u_x\) 分別表示 \(x\) 子樹內/外距離 \(x\) 最遠的葉子,則當 \(r=1\)\(\text{val}(x)\) 即為最深的祖先 \(v\) 使得 \(d_v\ne x\)(若沒有則 \(v=r\))的 \(\text{dis}(x,v)\)

再觀察一下,根從 \(r\) 移向兒子 \(q\) 時只有 \(d_q\)\(u_q\)\(\text{val}\) 值會改變,所以支援單點修改、求全域性前 \(k\) 大和即可,時間複雜度 \(\mathcal O(n\log n)\)

#include<bits/stdc++.h>
#define MP make_pair
#define fi first
#define se second
using namespace std;
typedef long long LL;
typedef pair<LL, int> pii;
const int N = 300003;
template<typename T>
bool chmax(T &a, const T &b){if(a < b) return a = b, 1; return 0;}
int n, k, cnt, hd[N], to[N], nxt[N], w[N], fa[N];
void add(int a, int b, int c){to[++cnt] = b; nxt[cnt] = hd[a]; hd[a] = cnt; w[cnt] = c;}
pii down[N], down2[N], up[N];
LL val[N], ans[N];
pii operator + (const pii &a, int b){return MP(a.fi + b, a.se);}
void dfs1(int x){
	down[x] = MP(0, x);
	for(int i = hd[x];i;i = nxt[i]) if(to[i] != fa[x]){
		fa[to[i]] = x; dfs1(to[i]);
		pii tmp = down[to[i]] + w[i];
		if(down[x] <= tmp){down2[x] = down[x]; down[x] = tmp;}
		else chmax(down2[x], tmp);
	}
	for(int i = hd[x];i;i = nxt[i])
		if(to[i] != fa[x] && down[x].se != down[to[i]].se)
			val[down[to[i]].se] = down[to[i]].fi + w[i];
}
void dfs2(int x){
	for(int i = hd[x];i;i = nxt[i]) if(to[i] != fa[x]){
		up[to[i]] = max(up[x], down[x] == down[to[i]] + w[i] ? down2[x] : down[x]) + w[i];
		dfs2(to[i]);
	}
}
LL hah[N], tot;
void dfs3(int x){
	for(int i = hd[x];i;i = nxt[i]) if(to[i] != fa[x]){
		hah[tot++] = val[down[to[i]].se] -= w[i];
		hah[tot++] = val[up[to[i]].se] += w[i];
		dfs3(to[i]);
		val[down[to[i]].se] += w[i];
		val[up[to[i]].se] -= w[i];
	}
}
LL trs[N];
int trc[N];
void upd(LL s, int c){
	int p = tot - (lower_bound(hah, hah + tot, s) - hah); s *= c;
	while(p <= tot){trs[p] += s; trc[p] += c; p += p & -p;}
}
LL qry(){
	int p = 0, now = k; LL sum = 0;
	for(int i = 18;~i;-- i)
		if((p | 1<<i) < tot && now >= trc[p | (1<<i)]){
			now -= trc[p |= (1<<i)]; sum += trs[p];
		}
	return sum + hah[tot - p - 1] * now;
}
void dfs4(int x){
	ans[x] = qry();
	for(int i = hd[x];i;i = nxt[i]) if(to[i] != fa[x]){
		upd(val[down[to[i]].se], -1);
		upd(val[down[to[i]].se] -= w[i], 1);
		upd(val[up[to[i]].se], -1);
		upd(val[up[to[i]].se] += w[i], 1);
		dfs4(to[i]);
		upd(val[down[to[i]].se], -1);
		upd(val[down[to[i]].se] += w[i], 1);
		upd(val[up[to[i]].se], -1);
		upd(val[up[to[i]].se] -= w[i], 1);
	}
}
int main(){
	ios::sync_with_stdio(false);
	cin >> n >> k;
	for(int i = 1, a, b, c;i < n;++ i){
		cin >> a >> b >> c;
		add(a, b, c); add(b, a, c);
	}
	dfs1(1); val[down[1].se] = down[1].fi; dfs2(1);
	for(int i = 1;i <= n;++ i) if(val[i]) hah[tot++] = val[i];
	dfs3(1);
	sort(hah, hah + tot);
	tot = unique(hah, hah + tot) - hah;
	for(int i = 1;i <= n;++ i) if(val[i]) upd(val[i], 1);
	dfs4(1);
	for(int i = 1;i <= n;++ i) printf("%lld\n", ans[i]);
}

Gardening

給定正整數 \(n,m,k\),給 \(n\times m\) 的網格圖染 \(k\) 種顏色使得每種顏色都出現過且匯出子圖是簡單環。需判斷無解。

\(\sum nm\le 2\cdot 10^5\)

solution

形如一堆矩形套起來,分類討論即可。不妨設 \(n\le m\) 則有解的條件是 \(2\mid n,m\)\(m/2\le k\le nm/4\)\(k\ne nm/4-1\)\(n=m\implies k\ne n/2+1\)

Speedrun

這是一道單向通訊+互動題

  • 給定 \(n\) 個點的樹,對每個點 \(x\) 輸出長為 \(20\)\(\texttt{01}\)\(a_x\)
  • 給定正整數 \(n\) 和當前位置 \(x\),你可以呼叫下述函式若干次,使得遍歷每個點至少一次,且 \(\texttt{goTo}\) 返回 \(\texttt{false}\) 的次數不超過 \(2000\)
    • \(\texttt{bool goTo(int y)}\),當 \(x\)\(y\) 相鄰時返回 \(\texttt{true}\) 並令 \(x:=y\),否則返回 \(\texttt{false}\)
    • \(\texttt{bool getHint(int y)}\),返回 \(a_{x,y}\)

\(n\le 1000\)

solution

記錄父親和 DFS 序下一個的編號。