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;
}