2025-03-24~04-06 hetao1733837 的刷题记录
03-24
LGP1736 创意吃鱼法
原题链接:创意吃鱼法
分析
第一反应是搜索......但是,怎么搜呢?爆搜必然超时,难道记忆化一下变成 DP?咦,确实比较可以,因为我们额外记录一维方向似乎比较可以做。但是,我怎么感觉还是会超时呢?写了一下,16pts ......AI 给出了每个方向处理一下的方法,但是我不知道区别在哪......
正解
cpp
#include <bits/stdc++.h>
using namespace std;
const int N = 2505;
int n, m;
int a[N][N];
int up[N][N], l[N][N], r[N][N];
int dp1[N][N], dp2[N][N];
int main(){
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
cin >> n >> m;
for (int i = 1; i <= n; i++){
for (int j = 1; j <= m; j++){
cin >> a[i][j];
if (a[i][j] == 0)
up[i][j] = up[i - 1][j] + 1;
else
up[i][j] = 0;
if (a[i][j] == 0)
l[i][j] = l[i][j - 1] + 1;
else
l[i][j] = 0;
}
}
for (int i = 1; i <= n; i++){
for (int j = m; j >= 1; j--){
if (a[i][j] == 0)
r[i][j] = r[i][j + 1] + 1;
else
r[i][j] = 0;
}
}
int ans = 0;
for (int i = 1; i <= n; i++){
for (int j = 1; j <= m; j++){
if (a[i][j] == 1){
dp1[i][j] = min({dp1[i - 1][j - 1], up[i - 1][j], l[i][j - 1]}) + 1;
ans = max(ans, dp1[i][j]);
}
}
}
for (int i = 1; i <= n; i++){
for (int j = m; j >= 1; j--){
if (a[i][j] == 1){
dp2[i][j] = min({dp2[i - 1][j + 1], up[i - 1][j], r[i][j + 1]}) + 1;
ans = max(ans, dp2[i][j]);
}
}
}
cout << ans;
}
03-27
LGP10785 [NOI2024] 集合
原题链接:[NOI2024] 集合
分析
你别说,还真是 hash 。难道说只要在短时间内判断......好吧,似乎不是那么简单的。
云浅是怎么场切的/ll
并未理解。
正解
cpp
#include <bits/stdc++.h>
using namespace std;
const int N = 200005, M = 600005;
int n, m, q;
int a[N][3], b[N][3];
mt19937_64 rnd(chrono::system_clock::now().time_since_epoch().count());
int buc[N], ap[M], bp[M], suma, sumb, base;
int c[N];
int gethash(int x){
if (!x)
return x;
x ^= base;
x ^= x << 7;
x ^= x >> 11;
x ^= 13;
return x;
}
void Hash(int id, int d){
for (auto i : a[id]){
suma -= gethash(ap[i]);
if (d > 0)
ap[i] += buc[id];
else
ap[i] -= buc[id];
suma += gethash(ap[i]);
}
for (auto i : b[id]){
sumb -= gethash(bp[i]);
if (d > 0)
bp[i] += buc[id];
else
bp[i] -= buc[id];
sumb += gethash(bp[i]);
}
}
signed main(){
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
cin >> n >> m >> q;
for (int i = 1; i <= n; i++){
cin >> a[i][0] >> a[i][1] >> a[i][2];
}
for (int i = 1; i <= n; i++){
cin >> b[i][0] >> b[i][1] >> b[i][2];
}
for (int i = 1; i <= n; i++){
c[i] = n;
}
int tmp = 10;
while (tmp--){
base = rnd();
for (int i = 1; i <= n; i++)
buc[i] = rnd();
Hash(1, 1);
for (int l = 1, r = 1; l <= n; Hash(l, -1), l++){
while (r <= n && suma == sumb){
r++;
if (r <= n)
Hash(r, 1);
}
c[l] = min(c[l], r - 1);
}
}
for (int cs = 1, l, r; cs <= q; cs++){
cin >> l >> r;
if (r <= c[l])
cout << "Yes" << '\n';
else
cout << "No" << '\n';
}
}
04-06
LGP4186 [USACO18JAN] Cow at Large G
原题链接:[USACO18JAN] Cow at Large G
分析
多久没刷题了自己心里清楚。
闲下来的时候想想 BO 停课的时候 OI 怎么办。
还有,你的求导和微积分打算什么时候学?
我绝对写过类似的东西!
咦,现在居然只有一个暴露的位置了吗?
为啥我感觉......这么微妙呢?为啥我感觉封住所有出口即可?怎么证明呢?不,可以提出反例。
所以,我们设 d p i dp_{i} dpi 表示 Bessie 在 i i i 子树的根节点位置暴露所需的最少农夫数。
思考一下,似乎 Bessie 向上走是不优的,因为我们可以把另一棵子树封住,这就是另一个状态了。
考虑把 K K K 当作整棵树的根节点。
考虑转移。
是......所有子树之和吗?
这么简单?我不信。
好吧,23 pts 。
思索一下题解......显然,一个农夫可以控制的点是确定的,所以,我们把这些点标出来,最终使得牛的所有路径被封死即可。
正解
cpp
#include <bits/stdc++.h>
using namespace std;
const int N = 100005;
int n, k, fa[N], de[N], ans;
bool son[N], tag[N];
vector<int> e[N];
void dfs1(int u, int pa){
son[u] = true;
fa[u] = pa;
de[u] = de[pa] + 1;
for (int i = 0; i < (int)e[u].size(); i++){
int v = e[u][i];
if (v != pa){
son[u] = false;
dfs1(v, u);
}
}
}
void dfs2(int u, int pa){
if (tag[u]){
ans++;
return ;
}
for (int i = 0; i < (int)e[u].size(); i++){
int v = e[u][i];
if (v != pa){
dfs2(v, u);
}
}
}
signed main(){
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
cin >> n >> k;
fa[k] = 0;
de[0] = -1;
for (int i = 1, u, v; i < n; i++){
cin >> u >> v;
e[u].push_back(v);
e[v].push_back(u);
}
dfs1(k, 0);
for (int i = 1; i <= n; i++){
if (son[i]){
int acc = i;
for (int j = 1; j <= de[i] / 2; j++){
acc = fa[acc];
}
tag[acc] = 1;
}
}
dfs2(k, 0);
cout << ans;
}
LGP5307 [COCI 2018/2019 #6] Mobitel
原题链接:[COCI 2018/2019 #6] Mobitel
分析
mhh 推的一看就是好题,题面如此短小精悍!
感觉有搞头啊......就是设某一个矩形,然后转移比较好搞。
我上个厕所......
初步思路是设 d p i , j dp_{i,j} dpi,j 表示以 ( i , j ) (i,j) (i,j) 为右下角的矩形的答案数。
那么,转移显然,即
d p i , j = d p i − 1 , j + d p i , j − 1 dp_{i,j}=dp_{i-1,j}+dp_{i,j-1} dpi,j=dpi−1,j+dpi,j−1
果真如此?
那,怎么玩剩下的呢?怎么确定初始的?难道,终究还是要写 d f s dfs dfs 了吗?理论上还是要搜/ll
还要求 O ( r s ) O(rs) O(rs) ......这个很微妙,证明刚才那个思路是有可行性的。
但是,我还是不会/(ㄒoㄒ)/~~......
咋是分块!
整除分块......好像听过......但是我不会啊/ll
显然,我们之前的方程需要再加上一维得 d p i , j , k dp_{i,j,k} dpi,j,k 表示位置 ( i , j ) (i,j) (i,j) 乘积为 k k k 的路径数,基本无法转移,即使可以也会爆炸。
那么,设 d p i , j , k dp_{i,j,k} dpi,j,k 表示位置 ( i , j ) (i,j) (i,j) 再乘上 k k k,路径乘积才会超过 n n n 得方案数,状态量过于大了。
我们发现, k k k 这一维的取值很少,故整除分块,有 O ( n ) O(\sqrt{n}) O(n ) 种取值,最终复杂度 O ( r s n ) O(rs\sqrt{n}) O(rsn )。
正解
cpp
#include <bits/stdc++.h>
#define mod 1000000007
using namespace std;
const int RS = 305, N = 1000005, M = 2505;
int r, c, n, a[RS][RS];
int calc(int x, int y){
return (x + y - 1) / y;
}
int d[N];
int di[N], tp, rv[N];
int dp[2][RS][M];
signed main(){
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
cin >> r >> c >> n;
for (int i = 1; i <= r; i++){
for (int j = 1; j <= c; j++){
cin >> a[i][j];
}
}
for (int i = 1; i <= n; i++){
d[i] = calc(n, i);
if (d[i] != d[i - 1]){
di[++tp] = d[i];
rv[d[i]] = tp;
}
}
dp[1][1][rv[calc(n, a[1][1])]] = 1;
for (int i = 1; i <= r; i++){
for (int j = 1; j <= c; j++){
for (int k = 1; k <= tp; k++){
int cur = dp[i & 1][j][k];
if (cur == 0)
continue;
if (i != r){
int nxt = rv[calc(di[k], a[i + 1][j])];
dp[(i & 1) ^ 1][j][nxt] = (dp[(i & 1) ^ 1][j][nxt] + cur) % mod;
}
if (j != c){
int nxt = rv[calc(di[k], a[i][j + 1])];
dp[i & 1][j + 1][nxt] = (dp[i & 1][j + 1][nxt] + cur) % mod;
}
if (i != r || j != c || k != tp){
dp[i & 1][j][k] = 0;
}
}
}
}
cout << dp[r & 1][c][tp] << '\n';
return 0;
}