NOIAC 2018模拟赛第三场

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/lvzelong2014/article/details/83537921

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}
总方程
f [ i 1 ] [ j ] C j h C s u m + 1 j k h C c n t 1 k 1 f [ i ] [ j h + c n t k ] f[i-1][j]\cdot C_j^h\cdot C_{sum+1-j}^{k-h}\cdot C_{cnt-1}^{k-1}\to f[i][j-h+cnt-k]
复杂度 O ( n 4 ) O(n^4)
大概。。。。吧

代码

#include<bits/stdc++.h>
const int N = 31; const long long P = 1234567891;
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;
}
long long dp[2][N], c[N][N], fac[N];
int cnt[1005], n, p;
int main() {
	n = ri(); p = ri();
	for(int i = 1;i <= n; ++i) ++cnt[(ri() % p + p) % p];
	fac[0] = 1; for(int i = 1;i < N; ++i) fac[i] = fac[i - 1] * i % P;
	c[0][0] = 1;
	for(int i = 1;i < N; c[i++][0] = 1)
		for(int j = 1;j <= i; ++j)
			c[i][j] = (c[i - 1][j] + c[i - 1][j - 1]) % P;
	int s = 0; long long Fac = 1; int pr = 0, nw = 1; dp[0][0] = 1;
	for(int i = 0;i < p; ++i)
		if(cnt[i]) {
			for(int j = 0;j <= s; ++j) if(dp[pr][j])
				for(int k = 1;k <= cnt[i]; ++k)
					for(int h = 0;h <= k && h <= j; ++h)
						(dp[nw][j - h + cnt[i] - k] 
						+= dp[pr][j] * c[j][h] % P * c[s + 1 - j][k - h] % P 
						* c[cnt[i] - 1][k - 1] % P) %= P;
			s += cnt[i]; std::swap(nw, pr);
			memset(dp[nw], 0, sizeof(dp[nw]));
			(Fac *= fac[cnt[i]]) %= P;
		}
	printf("%lld\n", dp[pr][0] * Fac % P);
	return 0;
}


猜你喜欢

转载自blog.csdn.net/lvzelong2014/article/details/83537921
今日推荐