2026-04-09~12 hetao1733837 的刷题记录
04-09
LGP1941 [NOIP 2014 提高组] 飞扬的小鸟
分析
该怎么设状态呢?
DP 从某些角度而言就是说人话。
我们设 d p i , j dp_{i,j} dpi,j 表示走到 x = i , y = j x=i,y=j x=i,y=j 的位置所花费的代价。这个做一下分讨,讨论上一步是自由下降还是点击上升,对着一坨取 min \min min 即可。
但是,复杂度是不对的,考虑优化。
发现点击次数有着一定的差的关系。
所以,对于 1 ≤ j < m 1\le j<m 1≤j<m,到达 x = i , y = j x=i,y=j x=i,y=j 的代价为 d p i , j = min { d p i − 1 , j − X i − 1 , d p i , j − X i − 1 } dp_{i,j}=\min\{dp_{i-1,j-X_{i-1}},dp_{i,j-X_{i-1}}\} dpi,j=min{dpi−1,j−Xi−1,dpi,j−Xi−1}。
对于 j = m j=m j=m,枚举每个 Y Y Y 即可。
正解
cpp
#include <bits/stdc++.h>
using namespace std;
const int N = 10005, M = 1005;
const long long INF = 0x3f3f3f3f3f3f3f3f;
int n, m, k, X[N], Y[N];
int P[N], L[N], H[N];
bool buc[N];
long long dp[N][M], res[N];
signed main(){
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
cin >> n >> m >> k;
for (int i = 0; i < n; i++){
cin >> X[i] >> Y[i];
}
for (int i = 1; i <= k; i++){
cin >> P[i] >> L[i] >> H[i];
buc[P[i]] = true;
}
int L_pos[N] = {0}, H_pos[N] = {0};
for (int i = 1; i <= k; i++) {
L_pos[P[i]] = L[i];
H_pos[P[i]] = H[i];
}
for (int i = 0; i <= m; i++){
dp[0][i] = 0;
}
for (int i = 1; i <= n; i++){
dp[i][0] = INF;
}
for (int i = 1; i <= n; i++){
res[i] = INF;
for (int j = 1; j <= m; j++){
dp[i][j] = INF;
}
for (int j = X[i - 1] + 1; j < m; j++){
dp[i][j] = min(dp[i][j], min(dp[i - 1][j - X[i - 1]], dp[i][j - X[i - 1]]) + 1);
}
for (int j = m - X[i - 1]; j <= m; j++){
dp[i][m] = min(dp[i][m], min(dp[i - 1][j], dp[i][j]) + 1);
}
for (int j = 1; j + Y[i - 1] <= m; j++){
dp[i][j] = min(dp[i][j], dp[i - 1][j + Y[i - 1]]);
}
if (buc[i]){
for (int j = 1; j <= L_pos[i]; j++){
dp[i][j] = INF;
}
for (int j = H_pos[i]; j <= m; j++){
dp[i][j] = INF;
}
}
for (int j = 1; j <= m; j++){
res[i] = min(res[i], dp[i][j]);
}
if (res[i] == INF){
int cnt = 0;
for (int j = 1; j < i; j++){
if (buc[j])
cnt++;
}
cout << 0 << '\n';
cout << cnt;
return 0;
}
}
cout << 1 << '\n';
cout << res[n];
return 0;
}
今天不写了,打会文化吧。
LGP2490 [SDOI2011] 黑白棋
原题链接:[SDOI2011] 黑白棋
分析
这个特殊性质给的非常好, k = 2 k=2 k=2,那结果似乎已经注定了!哦,是一坨组合数的相加。就是使得两个棋子限定的中间区域是个奇数即可,所以,白子放的位置,剩下的里面的一半做一个 ∑ C x 1 \sum{C_{x}^{1}} ∑Cx1。
那么,没有这个限制呢?
先不考虑 d d d 的限制呢?算了,设计一个 DP 吧。
哦,难道他博弈的点在于让己方的棋子剩余奇数个离下一个棋子奇数距离?怎么说呢?就是一个奇偶性的博弈。
怎么写呢?
原来是 K --- N i m K---Nim K---Nim 游戏吗?
🐂牛福!
把黑白棋子中间的距离看作一堆石子,则题目变为取 d d d 石子的 N i m Nim Nim 游戏。
那么自然而然地考虑到异或。
其中有一个结论, S G x = x % ( d + 1 ) SG_{x}=x\%(d+1) SGx=x%(d+1)
设 f i , j f_{i,j} fi,j 表示确定异或和二进制第 1 − i 1−i 1−i 位均为 0 0 0、现有 j j j 个石子的方案数。
转移为 f i , j = f i , j × C n − j − k / 2 k / 2 f_{i,j}=f_{i,j}\times C_{n-j-k/2}^{k/2} fi,j=fi,j×Cn−j−k/2k/2
正解
cpp
#include <bits/stdc++.h>
#define mod 1000000007
using namespace std;
const int N = 10005;
int n, k, d;
int C[N][205], dp[20][N];
signed main(){
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
cin >> n >> k >> d;
C[0][0] = 1;
for (int i = 1; i <= n; i++){
C[i][0] = 1;
for (int j = 1; j <= 200; j++){
C[i][j] = (C[i - 1][j - 1] + C[i - 1][j]) % mod;
}
}
dp[0][0] = 1;
for (int i = 0; i <= 17; i++){
for (int j = 0; j <= n - k; j++){
if (!dp[i][j])
continue;
for (int x = 0; ; x++){
int add = (1 << i) * x * (d + 1);
if (j + add > n - k)
break;
if (x * (d + 1) > k / 2)
break;
dp[i + 1][j + add] = (dp[i + 1][j + add] + 1LL * dp[i][j] * C[k / 2][x * (d + 1)] % mod) % mod;
}
}
}
int ans = 0;
for (int i = 0; i <= n - k; i++){
ans = (ans + 1LL * dp[17][i] * C[n - i - k / 2][k / 2] % mod) % mod;
}
cout << (C[n][k] - ans + mod) % mod;
return 0;
}
04-12
LGP7350 「MCOI-04」Dream and Strings
原题链接:「MCOI-04」Dream and Strings
分析
让我卡 Hash 是啥阴?
毫无思路啊......只有一个 O ( 26 ∣ S ∣ ) O({26}^{|S|}) O(26∣S∣) 的暴力。我觉得应该从这个东西怎么算出来的入手。
我毫无注意力啊/ll
把 T 的前 ∣ S ∣ − 50 |S|-50 ∣S∣−50 位全部变成与 S 一样的,然后再去构造。
但是,第一,这个 50 怎么来的?第二,怎么构造后续的?
合着这个 50 就是个题目性质?那有点......好吧,OI 确实可以这样。
嗯......怎么说呢?就是要构造一个长度为 50 50 50 的字符串,使得这个串与 A 原来的后 50 50 50 为不相同,但哈希值相同。
显然,这 50 个东西可以使用取值位于 [ 0 , 25 ] [0,25] [0,25] 的序列,进一步表示,会发现可以把原序列初始为一个任意字符的东西,然后,通过 & \& & 进行位运算上的改变,控制 ASCII \operatorname{ASCII} ASCII 的增减,故,只需枚举一个 0 / 1 0/1 0/1 序列即可。
我不理解/ll
正解
cpp
#include <bits/stdc++.h>
using namespace std;
const int N = 200005;
const unsigned long long sta = (1ull << 50) - 1;
int base, mod, base_pow[N];
string s;
map<unsigned long long, unsigned long long> buc;
int target = 0, cur;
int len;
mt19937_64 rnd;
void print(unsigned long long x, unsigned long long y){
for (int i = 0; i < len - 50; i++){
cout << s[i];
}
for (int i = 49; i >= 0; i--){
cout << (char)('a' + ((x >> i) & 1) + ((y >> i) & 1));
}
}
signed main(){
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
cin >> base >> mod;
cin >> s;
len = s.length();
base_pow[0] = 1;
for (int i = 1; i < N; i++){
base_pow[i] = 1ll * base_pow[i - 1] * base % mod;
}
target = 0;
for (int i = len - 1; i >= len - 50; i--){
target = (target + 1ll * (s[i] - 'a') * base_pow[len - 1 - i] % mod) % mod;
}
while (true){
unsigned long long tmp = rnd() & sta;
cur = 0;
for (int i = 0; i < 50; i++){
cur = (cur + 1ll * ((tmp >> i) & 1) * base_pow[i] % mod) % mod;
}
buc[cur] = tmp;
if (buc.count((target - cur + mod) % mod)){
print(tmp, buc[(target - cur + mod) % mod]);
return 0;
}
}
return 0;
}
不是假设初始全是 a 被 Hack 了吗?哦,没有什么问题,我理解错了。
那么,tmp 应该就是随出来的 0/1 序列,所以,成功概率 1 m o d \frac{1}{mod} mod1,还是比较大的。