あいさか たいがblogAisaka_Taiga的博客
//https://img2018.cnblogs.com/blog/1646268/201908/1646268-20190806114008215-138720377.jpg

2023.8.31解题报告

Toretto·2023-08-31 22:03·14 次阅读

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[i1][j][1],f[i1][j][0])
  • 当前点本来就是山谷点,可以选为山谷点,f[i][j][1]=f[i1][j][0]+a[i]
  • 当前点本来不是山谷点,我们要把他变成山谷点,f[i][j][1]=f[i1][j1][0]+min(a[i1],a[i+1])1

然后在枚举的过程中取 max 就好了。

Copy
/* * @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,,n

然后我们从上往下推第一项是由那些加减得到的,我是选的 n=7

然后我们能得到下面的一些序列:

Copy
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

我们把它倒过来:

Copy
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,然后就是正常的杨辉三角,系数的正负同上。

写个程序验证一下我们的猜想,然后就可以写代码了。

Copy
/* * @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 的二进制的每一位表示这个位置上是否有被收买的人占据。那么转移式如下

f[i][j+2k]+=f[i][j]×Caij22k1×(2k)!

其中 kj 的二进制位中为 0 的位。

Caij22k1 表示在实力在比 ai 小的没有选过且不为 1aij2 个选手中选 2k1(因为已经确定了要有 ai ,所以要减 1)个来组成 k 位置的子树。

因为子树内部可以任意排序,所以要乘上 (2k)!

输出答案时,因为 1 的位置任意,所以要乘上 2n

再来考虑 k1 的情况。

我们可以考虑对 LIS(最长上升子序列)的维护方法。

从小到大依次来维护每个数字的 LIS,这个数字的 LIS 等于在其之前的所有数字的 LIS 的最大值 +1

我们可以暴力求出所有经过 LIS 过程后最大的 LIS 值能够 k 的状态。状态的数量并不大,n=9 的时候才不到 120000。用这些状态来当之前状压的状态,这样即可求出答案。

改的标程。

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