1. 程式人生 > >NOIAC 2018模擬賽第三場

NOIAC 2018模擬賽第三場

NOIAC 2018模擬賽第三場

cycle

題目傳送門

題目大意:問一張無自環重邊的有向圖,求邊數最小的正環的邊數。
T1就難度中檔了。
考慮一個 O ( n 4 ) O(n^4)

的暴力, f [ k ] [ u ] [ v
] f[k][u][v]
表示從 u u 走到 v v k
k
步的最大邊權,正環就是 x s . t . f [ x ] [ u ] [ u ] > 0 \exists x s.t.f[x][u][u]>0
用矩陣倍增優化一下就好了。
有一些小細節,就是優化的時候把矩陣對角線賦值 0 0 ,就可以把 f [ k ] [ u ] [ v ] f[k][u][v] 變成走不少於 2 k 2^k 步,統計答案的時候如果走若 2 x 2^x 幹步還是無法出現正環,直接走即可,就不需要列舉答案了。

程式碼

#include<bits/stdc++.h>
int ri() {
	char c = getchar(); int x = 0, f = 1; for(;c < '0' || c > '9'; c = getchar()) if(c == '-') f = -1;
	for(;c >= '0' && c <= '9'; c = getchar()) x = (x << 1) + (x << 3) - '0' + c; return x * f;
}
int n;
struct Maxtir {
	int m[301][301];
	int *operator [](int i) {return m[i];}
	const int *operator [](int i) const {return m[i];}
	void Init() {
		for(int i = 1;i <= n; ++i)
			for(int j = 1;j <= n; ++j)
				m[i][j] = (i == j ? 0 : -1e9);
	}
	Maxtir operator * (const Maxtir &a) const {
		Maxtir c; c.Init();
		for(int i = 1;i <= n; ++i)
			for(int j = 1;j <= n; ++j) 
				for(int k = 1;k <= n; ++k)
					c[i][j] = std::max(c[i][j], m[i][k] + a[k][j]);
		return c;
	}
	bool ck() {
		for(int i = 1;i <= n; ++i) if(m[i][i] > 0) return true;
		return false;
	}
}r, tp, f[10];
int main() {
	n = ri(); int m = ri(); f[0].Init();
	for(int i = 1;i <= m; ++i) {
		int u = ri(), v = ri();
		f[0][u][v] = ri(); f[0][v][u] = ri();
	}
	for(int x = 1;x <= 9; ++x) f[x] = f[x - 1] * f[x - 1];
	if(!f[9].ck()) return puts("0"), 0;
	int Ans = 0; r.Init();
	for(int i = 8; ~i; --i) {
		tp = r * f[i]; 
		if(!tp.ck()) Ans |= (1 << i), r = tp;
	}
	printf("%d\n", Ans + 1);
	return 0;
}

leaves

題目傳送門

題目大意:給定一顆每個非葉子節點都有兩個兒子的二叉樹,每個葉子有一個權值,所有葉子的權值構成了 1 n 1\cdots n 的全排列,可以交換左右子節點,求最終葉子節點遍歷序逆序對最小值。
強烈吐槽本題題解。
注意到一個節點換或者不換僅僅會影響兩個區間之間的逆序對個數,可以獨立統計。
然而這個統計的過程題解半句沒提!!!
標解直接謝了啟發式搜尋,每次遞迴進小的子樹暴力,把大的子樹直接查詢。複雜度 O ( n l o g n ) O(nlogn)
我直接上了線段樹合併,複雜度一樣。

程式碼

#include<bits/stdc++.h>
const int T = 4e6 + 10, N = 2e5 + 10;
int ri() {
	char c = getchar(); int x = 0, f = 1; for(;c < '0' || c > '9'; c = getchar()) if(c == '-') f = -1;
	for(;c >= '0' && c <= '9'; c = getchar()) x = (x << 1) + (x << 3) - '0' + c; return x * f;
}
int sz, n, s[T], ls[T], rs[T]; long long r, r1, r2;
int Ins(int L, int R, int w) {
	s[++sz] = 1; if(L == R) return sz; int m = L + R >> 1, nw = sz; 
	w <= m ? ls[nw] = Ins(L, m, w) : rs[nw] = Ins(m + 1, R, w);
	return nw;
}
int Merge(int u, int v) {
	if(!u || !v) return u | v;
	s[u] += s[v];
	r1 += 1LL * s[ls[u]] * s[rs[v]];
	r2 += 1LL * s[rs[u]] * s[ls[v]];
	ls[u] = Merge(ls[u], ls[v]);
	rs[u] = Merge(rs[u], rs[v]);
	return u;
}
int Build() {
	int w = ri(); 
	if(w) return Ins(1, n, w);
	int ls = Build(), rs = Build();
	r1 = r2 = 0;
	int c = Merge(ls, rs);
	r += std::min(r1, r2);
	return c;
}
int main() {
	n = ri(); Build(); printf("%lld\n", r);
	return 0;
}

sequence

題目傳送門

題目大意:給定 n n 個兩兩不同的數,求任意相鄰兩個數之差都不是 P P 的倍數的方案數。
神仙題不會寫。。。

首先可以按 m o d &ThinSpace;&ThinSpace; p \mod p 把所有數分成若干個等價類(把不可以放在一起的數合併)。
一個很妙的想法是放寬條件,考慮 f [ i ] [ j ] f[i][j] 表示前 i i 個等價類,有 j j 對等價類中的數相鄰的方案數。(也就是我們還要塞 j j 個數到那些相鄰的數的缺口之中)
考慮轉移。假設當前的等價類大小為 c n t cnt ,決定把他們分成 k k 個塊(相鄰的數捆綁起來看成一個),這樣的分法會新產生 c n t k cnt-k 個缺口。
再列舉這 k k 個塊的其中多少個要填到之前的缺口之中,假設填了 h h 個缺口,那麼這樣的填法會減少 h h 個缺口。
考慮方案數。
首先,從原來的 j j 個缺口選擇 h h 個出來填,方案數 C j h C_j^h
其次,從剩下的 s u m + 1 j sum+1-j 個正常的缺口中挑 k h k-h 個位置用來放剩餘的塊,方案數 C s u m + 1 j k h C_{sum+1-j}^{k-h}
最後,往 c n t 1 cnt-1 個缺口中插 k 1 k-1 個隔板,將其分成 k 1 k-1 塊,方案數 C c n t 1 k 1 C_{cnt-1}^{k-1}
總方程
<