2023.8.31解题报告
- @Author: Aisaka_Taiga
- @Date: 2023-08-31 15:37:31
- @LastEditTime: 2023-08-31 15:48:02
- @LastEditors: Aisaka_Taiga
- @FilePath: \Desktop\2023.8.31解题报告.md
- 心比天高,命比纸薄。
2023.8.31 解题报告
T1 比较简单,考试的时候 1h 左右就切了,T2 推了半天没看出来是个变异的杨辉三角,T3 打了最低档的暴力,挂了20分,T4 大模拟没想下手,直接判的无解输出的 -1,没分。
T1
期望得分: 100
实际得分: 100
简单的 DP。
我们考虑设 \(f[i][j][1/0]\) 表示当前枚举到第 \(i\) 个数,选了 \(j\) 个数当山谷点,且 \(1\) 表示第 \(i\) 个点被选为山谷点,反之则为 \(0\)。
我们枚举 \(i,j\),考虑如何转移,我们发现连续的两个点是肯定不能都是山谷点,所以有以下三种转移:
- 当前点不选为山谷点,\(f[i][j][0] = max(f[i-1][j][1], f[i-1][j][0])\)。
- 当前点本来就是山谷点,可以选为山谷点,\(f[i][j][1] = f[i - 1][j][0] + a[i]\)。
- 当前点本来不是山谷点,我们要把他变成山谷点,\(f[i][j][1] = f[i - 1][j - 1][0] + min(a[i - 1], a[i + 1]) - 1\)。
然后在枚举的过程中取 \(\max\) 就好了。
/*
* @Author: Aisaka_Taiga
* @Date: 2023-08-31 08:09:11
* @LastEditTime: 2023-08-31 11:12:52
* @LastEditors: Aisaka_Taiga
* @FilePath: \Desktop\张潇涵\T1.cpp
* 心比天高,命比纸薄。
*/
#include <bits/stdc++.h>
#define int long long
#define N 2100
using namespace std;
inline int read(){int x=0,f=1;char ch=getchar();while(!isdigit(ch)){f=ch!='-';ch=getchar();}while(isdigit(ch)){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}return f?x:-x;}
int n, a[N], m, ans, f[N][N][2];
signed main()
{
freopen("t1.in", "r", stdin);
freopen("t1.out", "w", stdout);
n = read(), m = read();
for(int i = 1; i <= n; i ++) a[i] = read();
for(int i = 2; i <= n - 1; i ++)
{
for(int j = 0; j <= m; j ++)
{
f[i][j][0] = max(f[i - 1][j][1], f[i - 1][j][0]);
if(a[i] < a[i - 1] && a[i] < a[i + 1]) f[i][j][1] = f[i - 1][j][0] + a[i];
else if(j) f[i][j][1] = f[i - 1][j - 1][0] + min(a[i - 1], a[i + 1]) - 1;
ans = max(max(f[i][j][1], f[i][j][0]), ans);
}
}
cout << ans << endl;
return 0;
}
/*
3 1
5 6 7
5 5
2 7 8 9 2
5 1
8 7 6 7 9
*/
T2
预估得分:40
实际得分:40
考试的时候肝了一个多小时,没看出来是个杨辉三角。
我们先假设一开始给的都是 \(1, 2, 3,\dots ,n\)。
然后我们从上往下推第一项是由那些加减得到的,我是选的 \(n = 7\)。
然后我们能得到下面的一些序列:
1 0 -3 0 3 0 -1
1 1 -2 -2 1 1
1 0 -2 0 1
1 1 -1 -1
1 0 -1
1 1
1
我们把它倒过来:
1
1 1
1 0 -1
1 1 -1 -1
1 0 -2 0 1
1 1 -2 -2 1 1
1 0 -3 0 3 0 -1
我们可以发现每两行对应了杨辉三角的一行,也就是说,我们求得的序列里面,第 \(i\) 行对应的是杨辉三角的第 \((i + 1) / 2\) 行。
但是我们发现其实有一些地方是不一样的,比如,偶数行的地方,每一个杨辉三角的值都是重复了一次,然后得到的,而且前两列系数为正,后两列系数为负,再后两列为正,再后两列为负……
对于奇数行,我们看到里面的第偶数列都是 \(0\),然后就是正常的杨辉三角,系数的正负同上。
写个程序验证一下我们的猜想,然后就可以写代码了。
/*
* @Author: Aisaka_Taiga
* @Date: 2023-08-31 14:22:35
* @LastEditTime: 2023-08-31 15:27:40
* @LastEditors: Aisaka_Taiga
* @FilePath: \Desktop\T2.cpp
* 心比天高,命比纸薄。
*/
#include <bits/stdc++.h>
#define int long long
#define P 1000000007
#define N 200010
using namespace std;
int F[N], Finv[N], inv[N];
inline void init()
{
inv[1] = 1;
for(int i = 2; i < N; i ++)//线性求逆元
inv[i] =(P - P / i) * 1ll * inv[P % i] % P;
F[0] = Finv[0] = 1;
for(int i = 1; i < N; i ++)
{
F[i] = F[i - 1] * 1ll * i % P;//求阶乘
Finv[i] = Finv[i - 1] * 1ll * inv[i] % P;//求阶乘的逆元
}
}
inline int C(int n, int m)
{
if(m < 0 || m > n) return 0;//特判方案为零的情况
return F[n] * 1ll * Finv[n - m] % P * Finv[m] % P;
}
inline int cal(int n, int m)
{
if(n & 1 && !(m & 1)) return 0;//如果n是奇数,第偶数个的项的系数为0
m = (m + 1) / 2;//计算求的组合数的参数
n = (n + 1) / 2;
int now = C(n - 1, m - 1);//系数为C(n-1,m-1)
if(m % 2 == 0) return now * -1;//如果m是偶数就取反
return now;//返回系数
}
signed main()
{
init();
int n, a, ans = 0;
cin >> n;
for(int i = 1; i <= n; i++)
{
cin >> a;
ans = (ans + a * cal(n, i)) % P;//计算系数,相乘求和
}
cout << (ans + P) % P << endl;
return 0;
}
/*
10
1 2 3 4 5 6 7 8 9 10
1
1 1
1 0 -1
1 1 -1 -1
1 0 -2 0 1
1 1 -2 -2 1 1
1 0 -3 0 3 0 -1
*/
T3
期望得分:20
实际得分:0
T3 有点难了啊。。。
我们先考虑 $k = 1 $ 的情况。
在这种情况下,我们只需要让叶子节点 \(1\) 到根节点的路径上所有的点都被已经收买的人占了,且满足这些点都是其子树的最大值。
将被收买的人按实力值从小到大排序。设 \(f[i][j]\)
表示已经处理了前 \(i\) 个被收买的人,\(j\) 的二进制的每一位表示这个位置上是否有被收买的人占据。那么转移式如下
其中 \(k\) 为 \(j\) 的二进制位中为 \(0\) 的位。
\(C_{a_{i} - j -2}^{2^k-1}\) 表示在实力在比 \(a_{i}\) 小的没有选过且不为 \(1\) 的 \(a_{i}-j-2\) 个选手中选 \(2^k-1\)(因为已经确定了要有 \(a_{i}\) ,所以要减 \(1\))个来组成 \(k\) 位置的子树。
因为子树内部可以任意排序,所以要乘上 \((2^k)!\)。
输出答案时,因为 \(1\) 的位置任意,所以要乘上 \(2^n\)。
再来考虑 \(k\ge 1\) 的情况。
我们可以考虑对 LIS(最长上升子序列)的维护方法。
从小到大依次来维护每个数字的 LIS,这个数字的 LIS 等于在其之前的所有数字的 LIS 的最大值 \(+1\)。
我们可以暴力求出所有经过 LIS 过程后最大的 LIS 值能够 \(\ge k\) 的状态。状态的数量并不大,\(n=9\) 的时候才不到 \(120000\)。用这些状态来当之前状压的状态,这样即可求出答案。
改的标程。
#include<bits/stdc++.h>
#define int long long
#define N 120010
using namespace std;
inline int read(){int x=0,f=1;char ch=getchar();while(!isdigit(ch)){f=ch!='-';ch=getchar();}while(isdigit(ch)){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}return f?x:-x;}
int n, m, k, tot = 0, t1 = 0, a[25], cnt[N], mod;
int ans = 0, yh[1005][1005], jc[1005], f[25][N];
string pt[N], to[N];
map<string, int> z, re;
inline void dfs(string s, int now)//now是已经填了的数的个数
{
if(!z[s]) z[s] = 1, pt[++ tot] = s;//如果当前的状态未出现过,标记,存下来
else return;//出现过了就没必要搜了
if(now == n) return;//如果到了n就直接退出
char c = '0';
for(int i = 0; i < n; i ++)//遍历s的每一个字符
{
if(s[i] == '0')//如果要是当前字符没填
{
s[i] = c + 1;//填上
// cout << c << endl;
dfs(s, now + 1);//继续往下搜
s[i] = '0';//回溯
}
else c = max(c, s[i]);//否则就给c取较大值
}//保证是最长上升子序列
}
inline void dd()
{
for(int i = 1; i <= tot; i ++)//枚举所有状态
{
string s = pt[i];//取出状态串
char c = '0';
for(int j = 0; j < n; j ++)//填串
{
if(s[j] == '0') s[j] = c + 1, c ++;//等于0就填当前的c
else c = max(c, s[j]);//否则就更新c的值
}
if(c >= '0' + k) to[++ t1] = pt[i], re[pt[i]] = t1;//如果要是当前c大于k了,就是可以的方案存进to,re标记在to中的序号
}
for(int i = 1; i <= t1; i ++)//枚举所有可行的状态
for(int j = 0; j < n; j ++)//枚举每一位
if(to[i][j] != '0') cnt[i] |= (1 << j);//cnt存下当前位置是否有值
return ;
}
inline void init()
{
yh[0][0] = 1;
for(int i = 1; i <= 1000; i ++)
{
yh[i][0] = yh[i][i] = 1;//处理组合数
for(int j = 1; j < i; j ++) yh[i][j] = (yh[i - 1][j - 1] + yh[i - 1][j]) % mod;
}
jc[0] = 1;
for(int i = 1; i <= 1000; i ++) jc[i] = jc[i - 1] * i % mod;//处理阶乘
return ;
}
inline int ksm(int x, int y)//快速幂
{
int res = 1;
while(y)
{
if(y % 2 == 1) res = (res * x) % mod;
x = (x * x) % mod;
y >>= 1;
}
return res % mod;
}
inline void gt(string &s, int t)//找到下一个该是多少
{
char c = '0';
for(int i = 0; i < t; i ++) c = max(c, s[i]);
s[t] = c + 1;
}
signed main()
{
n = read(), m = read(), k = read(), mod = read();
for(int i = 1; i <= m; i ++) a[i] = read();
sort(a + 1, a + m + 1);//? 将输入的战力从小到大排序。
string s;
for(int i = 1; i <= n; i ++) s = s + '0';//一开始全是0
dfs(s, 0);//从当前的状态开始搜索1号叶子节点到根节点的LIS
dd();//处理cnt
init();//处理组合数,阶乘
f[0][1] = 1;//初始值
for(int i = 1; i <= m; i ++)//枚举每一个被收买的人。
{
for(int j = 1; j <= t1; j ++)//枚举每一个合法状态
{
if(f[i - 1][j])//如果前一个有值
{
f[i][j] = (f[i][j] + f[i - 1][j]) % mod;//先加上前面的
string now = to[j], nxt;//取出状态,nxt是下一个
for(int t = 0; t < n; t ++)//枚举每一个字符
{
if(now[t] == '0' && a[i] >= cnt[j] + (1 << t) + 1)//当前点没有填人,子树可以填满
{
nxt = now;
gt(nxt, t);//转移。
f[i][re[nxt]] = (f[i][re[nxt]] + f[i - 1][j] * yh[a[i] - cnt[j] - 2][(1 << t) - 1] % mod * jc[1 << t] % mod) % mod;
}
}
}
}
}
for(int i = 1; i <= t1; i ++)
if(cnt[i] == (1 << n) - 1)
ans = (ans + f[m][i]) % mod;
ans = ans * ksm(2, n) % mod;//1的位置任意,乘2^n
cout << ans << endl;
return 0;
}
本文来自博客园,作者:北烛青澜,转载请注明原文链接:https://www.cnblogs.com/Multitree/p/17670581.html
The heart is higher than the sky, and life is thinner than paper.