2022NOIP A层联测20

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

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

而且非常不长记性,longlong 的问题经常出,还是经常寄。。。。

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

A. 多项式求根

考场做法复杂度假了

考虑由 xn+yn 转移到 xn+1+yn+1

xn+1+yn+1=(xn+yn)×(x+y)xy(xn1+yn1)

于是矩阵快速幂即可

也有递归版的做法, 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. 数三角

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

如果你还记得开够 longlong 那么就能切掉了

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

一个不合法的三角形,有两个顶点会连接不同颜色的边,于是对每个顶点计算他作为其中一个不合法顶点的方案数,加起来除以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. 猜数游戏

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

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

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

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

fa,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 << " ";
	}

注意 n3 优化这里是我口胡的,正确性没有代码验证,欢迎 fake

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

而对于确定的 a,b 随着 c 的增大,该位置单调右移,于是可以优化至 n3

正解需要一个经典的操作

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

fi,m,r 表示操作 i 步,中间 1m 个, 一侧 0r 个,另一侧 0 的最大数量

转移可以对 n4 的进行变型,具体可以看代码

解释几个小点

首先左侧 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 @   Chen_jr  阅读(14)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!
点击右上角即可分享
微信分享提示