2024“钉耙编程”中国大学生算法设计超级联赛(4)

写在前面

补提地址:https://acm.hdu.edu.cn/listproblem.php?vol=65,题号 7469~7480

以下按个人难度向排序。

妈的老东西 Claris 怎么还在出题全是 BZOJ 工业题你妈的被干烂了

1005

模拟。

没啥能说的呃呃,求最后一管血和倒数第二管血的范围,暴力模拟第 hp − m + 1 至第 hp 点血量即可。

//
/*
By:Luckyblock
*/
#include <bits/stdc++.h>
#define LL long long
//=============================================================
int n, m, hp, dmg; 
//=============================================================
std::string output() {
  std::string ret;
  int nowhp = hp - dmg;
  int k = (hp - 1) / m, r = hp - k * m, kk = (hp - m - 1) / m;
  
  for (int i = hp - r + 1; i <= hp; ++ i) {
    if (i > nowhp) ret.push_back('.');
    else ret.push_back(k % 5 + 'A');
  }
  for (int i = hp + 1 - m; i <= hp - r; ++ i) {
    if (i <= 0) continue;
    if (i > nowhp) ret.push_back('.');
    else ret.push_back(kk % 5 + 'A');
  } 
  while ((int)ret.length() < m) ret.push_back(' ');
  return ret;
}
//=============================================================
int main() {
  // freopen("1.txt", "r", stdin);
  std::ios::sync_with_stdio(0), std::cin.tie(0);
  int T; std::cin >> T;
  while (T --) {
    std::cin >> n >> m >> hp >> dmg;
    std::cout << "+";
    for (int i = 1; i <= m; ++ i) std::cout << "-";
    std::cout << "+\n";
    
    std::string s = output();
    for (int i = 1; i <= n; ++ i) std::cout << "|" << s << "|\n";

    std::cout << "+";
    for (int i = 1; i <= m; ++ i) std::cout << "-";
    std::cout << "+\n";
  }
  return 0;
}
/*
*/
/*
5
2 5 4 0
1 10 49 1
1 10 52 0
1 10 52 5
1 10 52 50
*/

1009

枚举。

妈的今天签到怎么都有点码量。

显然对于每一种名字构成的昵称,名字的位置选择其在 \(S\) 中出现的第一个位置是最优的,使用子序列自动机即可求得其第一次出现位置,然后考虑该位置之后的后缀中可以构成多少种合法的月份和日期。

我的做法是把月份扔到名字后面,求名字+月份的第一次出现位置并在该位置上打个标记,然后枚举后缀并考虑后缀中出现了多少日期即可。

觉得可能有点慢写了个 bitset,实际上没啥必要。

//
/*
By:Luckyblock
*/
#include <bits/stdc++.h>
#define LL long long
const int kN = 1e6 + 10;
const int months[15] = {0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
//=============================================================
int n, m, tag[kN][13], next[kN][36], last[36];
std::string s;
std::bitset<100> have[2], mon[13];
//=============================================================
int count(int month_) {
  return (have[1] & mon[month_]).count();
}
void init() {
  have[0].reset(), have[1].reset();
  for (int j = 'a'; j <= 'z'; ++ j) last[j - 'a'] = -1;
  for (int j = '0'; j <= '9'; ++ j) last[j - '0' + 26] = -1;

  for (int i = m - 1; i >= 0; -- i) {
    for (int j = 1; j <= 12; ++ j) tag[i][j] = 0;
    for (int j = 0; j < 36; ++ j) next[i][j] = last[j];
    
    if (isdigit(s[i])) last[(int)s[i] - '0' + 26] = i;
    else last[(int) s[i] - 'a'] = i;
  }

  for (int i = 1; i <= n; ++ i) {
      std::string t; std::cin >> t;
      int p = last[(int)t[0] - 'a'];
      for (int j = 1, len = t.length(); p != -1 && j < len; ++ j) {
        p = next[p][(int)t[j] - 'a'];
      }
      if (p == -1) continue;

      for (int j = '1'; j <= '9'; ++ j) {
        int p0 = next[p]['0' - '0' + 26];
        if (p0 == -1) continue;
        p0 = next[p0][j - '0' + 26];
        if (p0 == -1) continue;
        ++ tag[p0][j - '0'];
      }
      for (int j = '0'; j <= '2'; ++ j) {
        int p1 = next[p]['1' - '0' + 26];
        if (p1 == -1) continue;
        p1 = next[p1][j - '0' + 26];
        if (p1 == -1) continue;
        ++ tag[p1][10 + j - '0'];
      }
    }
}
//=============================================================
int main() {
  // freopen("1.txt", "r", stdin);
  std::ios::sync_with_stdio(0), std::cin.tie(0);
  for (int i = 1; i <= 12; ++ i) {
    for (int j = 1; j <= months[i]; ++ j) {
      mon[i].set(j, 1);
    }
  }

  int T; std::cin >> T;
  while (T --) {
    std::cin >> n >> m;
    std::cin >> s;
    init();

    LL ans = 0;
    for (int i = m - 1; i; -- i) {
      if (isdigit(s[i])) {
        int c = s[i] - '0';
        have[1] |= (have[0] << (10 * c));
        have[0].set(c, 1);
      }
      for (int j = 1; j <= 12; ++ j) {
        ans += 1ll * tag[i - 1][j] * count(j);
      }
    }
    std::cout << ans << "\n";
  }
  return 0;
}

1007

随机数据,暴力。

dztlb 大神写的,我看都没看。

赛时跑了 5900ms 哈哈差点没过

#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N=410000;
int T,n,q,a[N],b[N],k,sum;
struct node{
	int b,id;
}p[N];
bool cmp(node x,node y){
	return x.b>y.b;
}
void change(int i){
	int x=p[i].id-k;
	if(x<0) x+=n;
	sum-=a[x];
	a[x]=max(a[x],p[i].b);
	sum+=a[x];
}
signed main(){
	cin>>T;
	while(T--){
		sum=0;
		cin>>n>>q;
		int minn=2000000000ll;
		for(int i=0;i<n;++i){
			cin>>a[i];
			minn=min(minn,a[i]);
			sum+=a[i];
		}
		for(int i=0;i<n;++i){
			cin>>p[i].b;
			p[i].id=i;
		}
		sort(p,p+n,cmp);
		int num=n-1;
		while(q--){
			cin>>k;
			if(num<=500){
				for(int i=0;i<min(n,num);++i){
					change(i);
				}
			}else{
				for(int i=0;i<n;++i){
					if(p[i].b<=minn){
						num=i-1;
						break;
					}
					change(i);
				}
				minn=a[0];
				for(int i=0;i<n;++i){
					minn=min(a[i],minn);
				}
			}
			cout<<sum<<'\n';
		}
	}
	return 0;
}

1003

随机数据,暴力

最小值最大显然转成二分答案,仅需检查子段和不小于 \(\operatorname{lim}\) 的长度为质数的子段数量是否不小于 \(k\)

赛时的做法是考虑到数据随机,较长子段的和期望为 0,于是考虑仅使用前 200 个质数(大约 1300 左右),记 \(f_{i}\) 表示前缀 \(1\sim i\) 中可划分出的合法子段的数量,然后枚举区间长度并检查。然而一直 WA 说明前 200 个质数区间长度还是不够,说明数据还不够随机呃呃呃呃!

官方做法是考虑贪心地划分子段,考虑划分右端点为 \(r\) 的合法子段,维护数据结构用于存左端点 \(l\) 的所有取值。显然合法的左端点应当满足 \(\operatorname{sum}_{r} - \operatorname{sum}_{l - 1}\ge \operatorname{lim}\)\(r-l+1\) 为质数。考虑按照 \(\operatorname{sum}_{l - 1}\) 升序枚举左端点直至第一个 \(r-l+1\) 为质数的即可。由于权值有正有负且保证数据随机,则前缀和的值是随机的,升序排序后左端点 \(l\) 的值也是随机的,有素数定理 \(\pi(n) \approx \frac{n}{\ln n}\),则每次枚举使得 \(i-j\) 为质数的概率即 \(p = \frac{\frac{i}{\ln i}}{i} = \frac{1}{\ln i}\),由几何分布可知,期望枚举 \(\frac{1}{p} = \ln n\) 次就能使得 \(i-j\) 为质数从而找到合法的左端点。

总时间复杂度大概是 \(O(n\log v\ln n)\) 级别。

#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N=2e5+5;

int T,n,k,a[N];
bool vis[N];

bool check(int x){
	int ret = 0;
	set <pair<int, int> > s;
	for (int r = 1; r <= n; ++ r) {
		int fl = 0;
		for (auto [v, l]: s) {
			if (a[r] - v < x) break;
			else if (!vis[r - l + 1]) {
				fl = 1;
				break;
			}
		}
		if (fl) s.clear(), ++ ret;
		else s.insert(make_pair(a[r - 1], r));
		if (ret >= k) return true;
	}
	return false;
}
signed main(){	
	std::ios::sync_with_stdio(0), std::cin.tie(0);
    for (int i = 2; i < N; ++ i) {
        for (int j = 2; i * j < N; ++ j) {
            vis[i * j] = 1;
        }
    }

	cin>>T;
	while(T--){
		cin>>n>>k;
		for(int i=1;i<=n;++i){
			cin>>a[i];
			a[i]+=a[i-1];
		}
		if(n<2*k){
			cout << "impossible"; continue;
		}
		int l=-2000ll,r=1e7;
		int ans=l;
		while(l<=r){
			int mid=(l+r)/2;
			if(check(mid)){
				l=mid+1;
				ans=max(ans,mid);
			}else r=mid-1;
		}
		cout<<ans<<'\n';
	}
	return 0;
}
/*
5
2 5 4 0
1 10 49 1
1 10 52 0
1 10 52 5
1 10 52 50
*/

1006

DP。

纯粹恶心人的题妈的

记玩家和敌人初始坐标为 \((px, py), (ex, ey)\)。先考虑 \(k=0\),此时两人同步行动,很容易设出状态 \(f_{i, x, y, x', y', h}\) 表示第 \(i\) 轮时玩家位于 \((x, y)\),敌人位于 \((x', y')\),敌人还剩血量 \(h\) 时的方案数,初始化 \(f_{0, px, py, ex, ey, hp} = 1\),大力枚举下一步的动作转移即可,对于所有令 \(hp=0\) 的状态求和即为答案。

然后考虑 \(k>0\),由于玩家是一直不撞墙的,发现仅需在计算答案时,额外乘上一个玩家从当前位置出发走 \(k\) 步不撞墙的方案数即可。此时状态 \(f_{i, x, y, x', y'}\) 表示第 \(i(i>k)\) 轮时敌人玩家位于 \((x', y')\),玩家在第 \(i-k\) 轮时位于 \((x, y)\) 的方案数。玩家从某位置出发走 \(k\) 步不撞墙的方案数同样可以 DP 计算,记 \(f1_{i, x, y}\) 表示从 \((x, y)\) 出发走 \(k\) 步不碰到障碍的方案数,枚举上一位置大力转移即可。

还有一个问题是上述状态是 \(O(mn^4 hp)\) 的,铁过不了,但是上述状态中很多状态是完全无用的,考虑优化状态设计。发现若敌人一直不撞墙,则玩家和敌人在行动后坐标改变量是相同的,知道一方的坐标即可推导另一方的坐标,此时仅需记录一方的坐标即可。又敌人碰到障碍等价于少了一次行动机会,即少了 1 的某维坐标改变量,考虑到有 \(hp\le 5\),则坐标改变量并不会超过 5,于是考虑修改状态为 \(f_{i, x, y, dx, dy, h}\) 表示第 \(i-k\) 轮时玩家位于 \((x, y)\),第 \(i\) 轮时敌人和玩家的坐标改变量两维差值分别为 \(dx, dy\),敌人还剩血量 \(h\) 时的方案数。此时敌人的坐标即为 \((ex + (x - px) + dx, ey + (y - py) + dy)\),同样大力枚举下一步动作转移即可,若敌人撞墙则更新血量和坐标改变量的差值。

因为太傻逼了转移方程详见代码。

总时间复杂度 \(O(mn^2hp^2)\) 级别,可以通过本题。

//
/*
By:Luckyblock
*/
#include <bits/stdc++.h>
#define LL long long
const int kN = 51;
const LL p = 1e9 + 7;
const int ex[4] = {0, 0, 1, -1};
const int ey[4] = {1, -1, 0, 0};
//=============================================================
std::string map[kN];
int n, m, k, hp;
int psx, psy, esx, esy;
LL f1[kN][kN][kN], f2[2][kN][kN][11][11][6], ans;
//=============================================================
void init() {
  std::cin >> n >> m >> k >> hp;
  for (int i = 1; i <= n; ++ i) {
    std::cin >> map[i];
    map[i] = "$" + map[i];
    for (int j = 1; j <= n; ++ j) {
      if (map[i][j] == 'P') psx = i, psy = j;
      if (map[i][j] == 'E') esx = i, esy = j;
    }
  }
  ans = 0;
}
bool legal(int x_, int y_) {
  return (1 <= x_ && x_ <= n && 1 <= y_ && y_ <= n && map[x_][y_] != '#');
}
void dp1() {
  for (int round = 0; round <= k; ++ round) {
    for (int i = 1; i <= n; ++ i) {
      for (int j = 1; j <= n; ++ j) {
        f1[round][i][j] = 0;
      }
    }
  }
  for (int i = 1; i <= n; ++ i) {
    for (int j = 1; j <= n; ++ j) {
      if (legal(i, j)) f1[0][i][j] = 1;
    }
  }

  for (int round = 0; round < k; ++ round) {
    for (int i = 1; i <= n; ++ i) {
      for (int j = 1; j <= n; ++ j) {
        if (!legal(i, j)) continue;
        if (!f1[round][i][j]) continue;

        for (int d = 0; d < 4; ++ d) {
          int nx = i + ex[d], ny = j + ey[d];
          if (!legal(nx, ny)) continue;
          f1[round + 1][nx][ny] += f1[round][i][j];
          f1[round + 1][nx][ny] %= p;
        }
      }
    }
  }

  // for (int i = 1; i <= n; ++ i) {
  //   for (int j = 1; j <= n; ++ j) {
  //     std::cout << f1[k][i][j] << " ";
  //   }
  //   std::cout << "\n";
  // }
}
void dp2() {
  for (int i = 1; i <= n; ++ i) {
    for (int j = 1; j <= n; ++ j) {
      if (!legal(i, j)) continue;
      for (int deltax = -hp; deltax <= hp; ++ deltax) {
        for (int deltay = -hp; deltay <= hp; ++ deltay) {
          for (int h = 0; h <= hp; ++ h) {
            f2[0][i][j][deltax + 5][deltay + 5][h] = 0;
          }
        }
      }
    }
  }
  f2[0][psx][psy][0 + 5][0 + 5][hp] = 1;
  
  int now = 0;
  for (int round = 0; round < m - k; ++ round, now ^= 1) {
    for (int i = 1; i <= n; ++ i) {
      for (int j = 1; j <= n; ++ j) {
        if (!legal(i, j)) continue;
        for (int deltax = -hp; deltax <= hp; ++ deltax) {
          for (int deltay = -hp; deltay <= hp; ++ deltay) {
            for (int h = 0; h <= hp; ++ h) {
              f2[now ^ 1][i][j][deltax + 5][deltay + 5][h] = 0;
            }
          }
        }
      }
    }

    for (int i = 1; i <= n; ++ i) {
      for (int j = 1; j <= n; ++ j) {
        if (!legal(i, j)) continue;
        for (int deltax = -hp; deltax <= hp; ++ deltax) {
          for (int deltay = -hp; deltay <= hp; ++ deltay) {
            for (int h = 1; h <= hp; ++ h) {
              if (!f2[now][i][j][deltax + 5][deltay + 5][h]) continue;
              int nowex = esx + (i - psx) + deltax, nowey = esy + (j - psy) + deltay;

              for (int d = 0; d < 4; ++ d) {
                int npx = i + ex[d], npy = j + ey[d];
                int nex = nowex + ex[d], ney = nowey + ey[d];
                if (!legal(npx, npy)) continue;
                if (legal(nex, ney)) {
                  f2[now ^ 1][npx][npy][deltax + 5][deltay + 5][h] += f2[now][i][j][deltax + 5][deltay + 5][h];
                  f2[now ^ 1][npx][npy][deltax + 5][deltay + 5][h] %= p;
                } else {
                  f2[now ^ 1][npx][npy][deltax - ex[d] + 5][deltay - ey[d] + 5][h - 1] += f2[now][i][j][deltax + 5][deltay + 5][h];
                  f2[now ^ 1][npx][npy][deltax - ex[d] + 5][deltay - ey[d] + 5][h - 1] %= p;
                }
              }
            }
          }
        }
      }
    }

    for (int i = 1; i <= n; ++ i) {
      for (int j = 1; j <= n; ++ j) {
        if (!legal(i, j)) continue;
        for (int deltax = -hp; deltax <= hp; ++ deltax) {
          for (int deltay = -hp; deltay <= hp; ++ deltay) {
            ans += 1ll * f2[now ^ 1][i][j][deltax + 5][deltay + 5][0] * f1[k][i][j] % p;
            ans %= p;
          }
        }
      }
    }
  }
}
//=============================================================
int main() {
  // freopen("1.txt", "r", stdin);
  std::ios::sync_with_stdio(0), std::cin.tie(0);
  int T; std::cin >> T;
  while (T --) {
    init();
    dp1();
    dp2();
    std::cout << ans << "\n";
  }
  return 0;
}
/*
3
5 5 2 1
.....
...P.
.....
.E...
#####
2 3 0 2
.#
PE
5 5 2 1
.....
...P.
.....
.E...
.....
*/

写在最后

学到了什么:

  • 1006:DP 转化

哎呦我草这个【中文字幕/便利屋68小剧场】老师,借用您一点时间哦#5看完一秒不昏过去的都是神人了

posted @ 2024-07-30 00:36  Luckyblock  阅读(215)  评论(0编辑  收藏  举报