2022NOIP A层联测20

今天又寄了,本来以为能切三个,结果都挂了

感觉自己太浮躁,而且对于理论上上的东西(复杂度)分析的能力太差

而且非常不长记性,\(long long\) 的问题经常出,还是经常寄。。。。

问问大家有什么好办法防挂吗?

A. 多项式求根

考场做法复杂度假了

考虑由 \(x^n+y^n\) 转移到 \(x^{n+1} + y^{n + 1}\)

\(x^{n + 1} + y^{n + 1} = (x^n + y^n) \times (x + y) - xy(x^{n - 1} + y^{n - 1})\)

于是矩阵快速幂即可

也有递归版的做法, \(chino\)大佬估计会写在他的题解里

code
#include<bits/stdc++.h>

using namespace std;

typedef long long ll;
typedef unsigned long long ull;

const int maxn = 105;
const int mod = 998244353;

ll add, mul, n;
struct matrix{
	int a[2][2];
	matrix(){memset(a, 0, sizeof(a));}
	friend matrix operator *(const matrix &x, const matrix &y){
		matrix ans;
		for(int i = 0; i < 2; ++i)
			for(int k = 0; k < 2; ++k)
				for(int j = 0; j < 2; ++j)
					ans.a[i][j] = (ans.a[i][j] + 1ll * x.a[i][k] * y.a[k][j] % mod) % mod;
		return ans;
	}
}ans, x;

int main(){
	freopen("poly.in","r",stdin);
	freopen("poly.out","w",stdout);
	cin >> add >> mul >> n; --n;
	for(int i = 0; i < 2; ++i)ans.a[i][i] = 1;
	x.a[0][0] = add; x.a[1][0] = mod - mul; x.a[0][1] = 1;
	for(; n; n >>= 1, x = x * x)if(n & 1)ans = ans * x;
	x.a[0][0] = add; x.a[0][1] = 2; x.a[1][0] = x.a[1][1] = 0;
	x = x * ans;
	printf("%d\n",x.a[0][0]); 
	return 0;
}

B. 数三角

如果你能想到正难则反,那么这个题应该会比较好做

如果你还记得开够 \(long long\) 那么就能切掉了

合法的数量难以统计,但是总数是好求的,考虑算出不合法情况减掉

一个不合法的三角形,有两个顶点会连接不同颜色的边,于是对每个顶点计算他作为其中一个不合法顶点的方案数,加起来除以\(2\)即可

code
#include<bits/stdc++.h>

using namespace std;

typedef long long ll;
typedef unsigned long long ull;

const int maxn = -1;

namespace GenHelper{
	unsigned z1, z2, z3, z4, b, u;
	unsigned get(){
		b = ((z1 << 6) ^ z1) >> 13; z1 = ((z1 & 4294967294U) << 18) ^ b;
		b = ((z2 << 2) ^ z2) >> 27; z2 = ((z2 & 4294967288U) << 2) ^ b;
		b = ((z3 << 13) ^ z3) >> 21; z3 = ((z3 & 4294967280U) << 7) ^ b;
		b = ((z4 << 3) ^ z4) >> 12; z4 = ((z4 & 4294967168U) << 13) ^ b;
		return (z1 ^ z2 ^ z3 ^ z4);
	}
	bool read(){
		while (!u) u = get();
		bool res = u & 1; u >>= 1;
		return res;
	}
	void srand(int x){
		z1 = x; z2 = (~x) ^ 0x233333333U;
		z3 = x ^ 0x1234598766U; z4 = (~x) + 51;
		u = 0;
	}
}
using namespace GenHelper;
bool edge[8005][8005];
int n, seed;
ll cnt[8005];
int main(){
	freopen("triangle.in","r",stdin);
	freopen("triangle.out","w",stdout);
	cin >> n >> seed; srand(seed);
	for (int i = 0; i < n; i++)
		for (int j = i + 1; j < n; j++)
			edge[j][i] = edge[i][j] = read();
	for(int i = 0; i < n; ++i)
		for(int j = 0; j < n; ++j)cnt[i] += edge[i][j];
	ll ans = 0;
	for(int i = 0; i < n; ++i)ans += cnt[i] * (n - cnt[i] - 1);
	ans /= 2;
	ans = 1ll * n * (n - 1) * (n - 2) / 6 - ans;
	cout << ans << endl;
	return 0;
}

C. 棋盘染色

之前有个类似的题元素周期表

这个题是类似的,按照权排序后考虑是否需要染黑当前点即可

考场没有注意值域,忘了想桶排这个东西,于是\(TLE\)

以及如果你看了我之前写的元素周期表会发现按照同样的做法会 \(TLE\)

之前的做法基本是暴力枚举,虽然复杂度好像正确,但是常数是真的大

转化一下,把每一行每一列都看成一个点,点看成边,实际上我们是在建一棵最小生成树,于是就能切了

code
#include<bits/stdc++.h>

using namespace std;

typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> pii;
const int maxn = 5005;
int  n, m;
ll a, b, c, D, p;
vector<pii>v[100001];
int f[maxn + maxn]; int fa(int x){return f[x] == x ? x : f[x] = fa(f[x]);}
int main(){
	freopen("color.in","r",stdin);
	freopen("color.out","w",stdout);
	cin >> n >> m >> a >> b >> c >> D >> p;
	for(int i = 1; i <= n; ++i)
		for(int j = 1; j <= m; ++j){
			a = (a * a  * b + a * c + D) % p;	
			v[a].push_back(pii(i, j));
		}
	ll ans = 0; 
	for(int i = 1; i <= n + m; ++i)f[i] = i;
	int cnt = n + m;
	for(int i = 0; i <= 100000; ++i)
		for(pii now : v[i]){
			int x = fa(now.first), y = fa(now.second + n);
			if(x == y)continue; ans += i; f[y] = x;
		}
	cout << ans << endl;
	return 0;
}

D. 猜数游戏

奇妙,考场不会暴力,不会模拟。。

这种题应该怎么去想,暴力如何去打,大家有什么想法吗

\(a_i\) 表示最终答案为 \(i\) 的话,还能撒谎几次,初始为 \(1\), 如果一次的回答指向了不包含 \(i\) 的一侧,那么 \(--a_{i}\), 那么当 \(a_{i} < 0\) 时,其一定不能作为当前答案,最终结束条件就是只有一个数大于等于 \(0\)

观察性质可以发现一定是左侧一些 \(0\) 中间一些 \(1\) ,右侧一些 \(0\),而如果只要步数的话,具体在哪个位置不重要,于是可以设计以下 \(DP\)

\(f_{a, b, c}\)表示 \(0 , 1, 0\) 子序列三部分的长度,转移枚举这次选择在哪里询问,以及回答 \(1 / 0\) ,由于\(A\)策略,两个回答要取 \(max\),由于\(B\)选择询问,那么位置之间是取 \(min\)

那么到这有了 \(40\)

40pts
//递归
int dfs(int x, int y, int z){
	if(f[x][y][z])return f[x][y][z]; if(x + y + z == 1)return f[x][y][z] = 1;
	int ans = 0x3f3f3f3f;
	for(int i = 1; i <= x - (z == 0 && y == 0); ++i)ans = min(ans, max(dfs(x - i, y, z), dfs(i , 0, y)) + 1);
	for(int i = 1; i <= y - (z == 0); ++i)ans = min(ans, max(dfs(i, y - i, z), dfs(x, i, y - i)) + 1);
	for(int i = 1; i < z; ++i)ans = min(ans, max(dfs(y, 0, z - i), dfs(x, y, i)) + 1);
	return f[x][y][z] = ans;
}
int main(){
	freopen("guess.in","r",stdin);
	freopen("guess.out","w",stdout);
	cin >> n;
	for(int i = 1; i <= n; ++i){
		cout << dfs(i - 1, n - i + 1, 0) - 1 << " ";
	}
	return 0;
}
//迭代,注意枚举顺序
memset(f, 0x3f, sizeof(f));
	f[1][0][0] = f[0][1][0] = f[0][0][1] = 1;
	for(int len = 2; len <= n; ++len){
		for(int y = 0; y <= len; ++y)
			for(int z = 0; z <= len - y; ++z){
				int x = len - y - z;
				for(int i = 1; i <= x - (z == 0 && y == 0); ++i)f[x][y][z] = min(f[x][y][z], max(f[x - i][y][z], f[i][0][y]) + 1);
				for(int i = 1; i <= y - (z == 0); ++i)f[x][y][z] = min(f[x][y][z], max(f[i][y - i][z], f[x][i][y - i]) + 1);
				for(int i = 1; i < z; ++i)f[x][y][z] = min(f[x][y][z], max(f[y][0][z - i], f[x][y][i]) + 1);
			}
	}
	for(int i = 1; i <= n; ++i){
		cout << f[i - 1][n - i + 1][0] - 1 << " ";
	}

注意 \(n^3\) 优化这里是我口胡的,正确性没有代码验证,欢迎 \(fake\)

优化考虑选择一个询问位置,从左到右,选择回答 \(1\) 的步数会减少, \(0\) 会增加,中间存在位置是两种方案谁更优的交界点,在这里进行转移即为最优决策

而对于确定的 \(a, b\) 随着 \(c\) 的增大,该位置单调右移,于是可以优化至 \(n^3\)

正解需要一个经典的操作

注意到答案范围较小,交换 \(DP\) 数组的一个下标和答案,转变定义

\(f_{i, m, r}\) 表示操作 \(i\) 步,中间 \(1\)\(m\) 个, 一侧 \(0\)\(r\) 个,另一侧 \(0\) 的最大数量

转移可以对 \(n^4\) 的进行变型,具体可以看代码

解释几个小点

首先左侧 \(0\) 和右侧 \(0\) 是高度对称的,所以转移中有一些可以理解成翻转左右的操作

然后对于确定的 \(m\) ,随着一侧 \(0\) 数量的减少,另外一侧 \(0\) 的最大数量会单调不降,于是可以后缀取 \(max\)

有一说一,难得\(Ly\)讲题讲的这么细(虽然我还是菜的褐代码

code
#include<bits/stdc++.h>

using namespace std;

typedef long long ll;
typedef unsigned long long ull;

int n, f[19][2005][2005];

int main(){
	// freopen("guess.in","r",stdin);
	// freopen("guess.out","w",stdout);
	cin >> n; memset(f, -0x3f, sizeof(f));
	f[0][0][1] = f[0][1][0] = 0; f[0][0][0] = 1;
	for(int i = 1; i <= 18; ++i){
		for(int m = 0; m <= n; ++m){
			for(int r = 0; r <= n - m; ++r){
				int l = min(f[i - 1][m][r] + f[i - 1][0][m], n - m);
				if(l >= 0)f[i][m][r] = max(f[i][m][r], l), f[i][m][l] = max(f[i][m][l], r);
			}//询问一侧0
			for(int p = 0; p <= m; ++p){
				int q = m - p, l = min(f[i - 1][p][q], n - m), r = min(f[i - 1][q][p], n - m);
				if(l >= 0 && r >= 0)f[i][m][r] = max(f[i][m][r], l), f[i][m][l] = max(f[i][m][l], r);
			}//询问中间1
			for(int j = n; j >= 0; --j)f[i][m][j] = max(f[i][m][j], f[i][m][j + 1]);
		}
	}
	for(int i = 1; i <= n; ++i){
		int ans = 0;
		while(f[ans][n - i + 1][i - 1] < 0)++ans;
		cout << ans << " ";
	}
	cout << endl;
	return 0;
}

posted @ 2022-11-04 20:30  Chen_jr  阅读(14)  评论(0编辑  收藏  举报