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看完一秒不昏过去的都是神人了