NOIP2024集训 Day32 总结
前言
当坚冰还盖着北海的时候,我看到了怒放的梅花。
停课了,对于每天的题也是终于有时间写总结了。
我不会告诉你,以前没空写是因为颓废。
今天是,愉快的,数位DP专题~
淘金
乍一看,这个
手玩一下小数据,我们可以发现,对于每个变动到的坐标
由于
考虑对于一维,有我们刚刚的推导可以得到,每一维变动之后最终的合法状态在
这个应该是不难的,所以先直接放代码。
//num1,2,3,4是分别对应的2,3,5,7的次数,如6,num1=1,num2=1
long long dfs(int x, bool limit, int num1, int num2, int num3, int num4, bool flg)
{
if(num1 < 0 || num2 < 0 || num3 < 0 || num4 < 0) return 0;
if(x == 0) return (num1 + num2 + num3 + num4) == 0 && flg;
if(~dp[x][num1][num2][num3][num4][limit][flg]) return dp[x][num1][num2][num3][num4][limit][flg];
int up = limit ? a[x] : 9;
long long now = 0;
for (int i = ((!flg) ? 0 : 1); i <= up; ++i)
now += dfs(x - 1, limit & (i == a[x]), num1 - ::num1[i], num2 - ::num2[i], num3 - ::num3[i], num4 - ::num4[i], (flg | bool(i)));
return dp[x][num1][num2][num3][num4][limit][flg] = now;
}
然后样我们就可以得到每个最终状态上有多少个点。
问题就转化成了:有
一个简单的堆就解决了,先对于每一个
代码写的非常难评,是时间正常人的
#include <bits/stdc++.h>
using namespace std;
#define maxn 1000005
const int mod = 1e9 + 7;
long long n;
int k;
int a[20], cnt;
long long dp[15][42][30][20][15][2][2];
map<long long, int> ans;
int num1[10], num2[10], num3[10], num4[10];
long long dfs(int x, bool limit, int num1, int num2, int num3, int num4, bool flg)
{
if(num1 < 0 || num2 < 0 || num3 < 0 || num4 < 0) return 0;
if(x == 0) return (num1 + num2 + num3 + num4) == 0 && flg;
if(~dp[x][num1][num2][num3][num4][limit][flg]) return dp[x][num1][num2][num3][num4][limit][flg];
int up = limit ? a[x] : 9;
long long now = 0;
for (int i = ((!flg) ? 0 : 1); i <= up; ++i)
now += dfs(x - 1, limit & (i == a[x]), num1 - ::num1[i], num2 - ::num2[i], num3 - ::num3[i], num4 - ::num4[i], (flg | bool(i)));
return dp[x][num1][num2][num3][num4][limit][flg] = now;
}
void solve(long long x)
{
while(x) a[++cnt] = x % 10, x /= 10;
memset(dp, -1, sizeof(dp));
for (int i = 0; i <= 40; ++i)
{
for (int j = 0; j < 30; ++j)
{
for (int k = 0; k < 20; ++k)
{
for (int l = 0; l < 15; ++l)
ans[dfs(cnt, true, i, j, k, l, 0)]++;
}
}
}
}
unordered_map<long long, int> now, nxt;
int main()
{
num1[2] = 1, num2[3] = 1, num3[5] = 1, num4[7] = 1;
num1[4] = 2;
num1[6] = 1, num2[6] = 1;
num1[8] = 3;
num2[9] = 2;
cin >> n >> k;
solve(n);
long long maxx = (*(--ans.end())).first;
priority_queue<pair<long long, int> > p;
for (auto i = ans.begin(); i != ans.end(); ++i) now[(*i).first] = maxx;
for (auto i = ans.begin(); i != ans.end(); ++i) if(i != ans.begin())
{
long long now = (*i).first;
--i;
nxt[now] = (*i).first;
++i;
}
for (auto i = ans.begin(); i != ans.end(); ++i) p.push(make_pair((*i).first * maxx, (*i).first));
int sum = 0;
while(!p.empty())
{
int x = p.top().second;
long long y = p.top().first;
p.pop();
if(ans[x] * ans[now[x]] >= k)
{
sum += 1ll * y % mod * k % mod, sum %= mod;
break;
}
sum += 1ll * y % mod * ans[x] % mod * ans[now[x]] % mod, sum %= mod;
k -= ans[x] * ans[now[x]];
if(nxt[now[x]])
{
now[x] = nxt[now[x]];
p.push(make_pair(x * 1ll * now[x], x));
}
}
cout << sum << endl;
return 0;
}
数数
有一说一,这个题还是很难的,至少我一开始就走上了错误的思路。
首先把这个题转化为
看到这种子串的问题,我们先明确一个中心思路。就是说,我们可以考虑去枚举处于
其实也就是变相的对字串转化了一下,使其更贴近于数位
我们定义
显然有
首先可以直接在上一位之后乱填,也可以在前面全部首位都被顶到的情况下填写
接下来我们定义
显然有
其实挺显然的,没限制就是
然后我们定义
简单的
显然这一位只能填
那么有了
然后我们考虑定义一个
更多细节参见代码:
//牛的,看题解硬控我2h
#include <bits/stdc++.h>
using namespace std;
#define maxn 100005
const int mod = 20130427;
int n, b;
int dp[maxn][2], num[maxn], now[maxn][2], len[maxn][2];
int get(int x) {return 1ll * x * (x + 1) / 2 % mod;}
int solve(int n, int a[])
{
for (int i = 1; i <= n; ++i)
{
int j = (i > 1) * b - 1;
num[i] = (1ll * num[i - 1] * b % mod + a[i] + j) % mod;
len[i][0] = len[i - 1][0] + 1;
len[i][1] = (len[i][0] * 1ll * a[i] % mod + (num[i - 1] + len[i - 1][1]) * 1ll * b % mod + j) % mod;
now[i][0] = (now[i - 1][0] * 1ll * b % mod + len[i][0] * 1ll * a[i] % mod) % mod;
now[i][1] = (get(j) + now[i - 1][0] * 1ll * b % mod * a[i] % mod + len[i][0] * 1ll * get(a[i] - 1) % mod) % mod;
now[i][1] += (now[i - 1][1] * 1ll * b % mod * b % mod + (len[i - 1][1] + num[i - 1]) * 1ll * get(b - 1) % mod) % mod;
now[i][1] %= mod;
dp[i][0] = (dp[i - 1][0] + now[i][0]) % mod;
dp[i][1] = (dp[i - 1][0] * 1ll * a[i] % mod + dp[i - 1][1] * 1ll * b % mod + now[i][1]) % mod;
}
return (dp[n][0] + dp[n][1]) % mod;
}
int l, r;
int a[maxn], c[maxn];
int main()
{
scanf("%d", &b);
scanf("%d", &l);
for (int i = 1; i <= l; ++i) scanf("%d", &a[i]);
scanf("%d", &r);
for (int i = 1; i <= r; ++i) scanf("%d", &c[i]);
a[l]--;
for (int i = l; a[i] < 0; --i) a[i] += b, a[i - 1]--;
if(!a[1])
{
for (int i = 2; i <= l; ++i) a[i - 1] = a[i];
--l;
}
cout << (solve(r, c) - solve(l, a) + mod) % mod << endl;
return 0;
}
Beautiful numbers
前面两个题写的太认真了,这个题也没什么水平,所以写的稍微水一点。
由于所有我们需要考虑的因子都是个位数。观察到
剩下的就很简单了,我们只在乎这个数模
所以数位DP的时候,打个取模,打个对于
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 20;
const int mod = 2520;
int T, cur, a[mod + 1];
ll l, r, f[20][mod + 1][50];
vector<int> dim;
int gcd(int x, int y) { return x % y ? gcd(y, x % y) : y; }
int lcm_(int x, int y)
{
if (!y) return x;
return x / gcd(x, y) * y;
}
ll dfs(int x, int mode, int lcm, bool op)
{
if (!x) return mode % lcm == 0 ? 1 : 0;
if (!op && f[x][mode][a[lcm]]) return f[x][mode][a[lcm]];
int maxx = op ? dim[x] : 9;
ll ret = 0;
for (int i = 0; i <= maxx; i++) ret += dfs(x - 1, (mode * 10 + i) % mod, lcm_(lcm, i), op & (i == maxx));
if (!op) f[x][mode][a[lcm]] = ret;
return ret;
}
ll solve(ll x)
{
dim.clear();
dim.push_back(-1);
ll t = x;
while (t) dim.push_back(t % 10), t /= 10;
return dfs(dim.size() - 1, 0, 1, 1);
}
main()
{
for (int i = 1; i <= mod; i++) if (mod % i == 0) a[i] = ++cur;
scanf("%d", &T);
while (T--) scanf("%lld%lld", &l, &r), printf("%lld\n", solve(r) - solve(l - 1));
return 0;
}
New Year and Binary Tree Paths
挺有意思的题目,虽然是黑色,但是我实际做下来还好,至少结论都推出来了。
感觉在草稿纸上认真画一画就有了,只需要注意二进制的低位怎么加都加不到高位的性质。
首先我们先考虑这个路径是一条链的情况。
假设深度最浅的节点为
对于这条路径上的所有右儿子,假设他们在路径上的深度(假设路径最后一个节点的深度为
于是我们的路径权值是
我们发现,在知道最终路径权值的多少的情况下,我们可以通过枚举
其实是比较显然的,因为你后面的右儿子无论怎么选,都无法达到
而在
然后我们考虑这条路径是一条分叉的情况,也就是有两条链。
假设最浅的节点为
对于左链上的所有右儿子,假设他们在路径上的深度(假设路径最后一个节点的深度为
对于右链上的所有右儿子(不算
显然,这条路径的权值就是
推导一下,发现我们在枚举
问题简化成了:
我们有
我们发现这个
问题转化为:
我们有
其实是一个简单的
定义
这个转移是真心简单,注意一下要等于
复杂度比较显然,即
感觉细节还是很多的,不知道为什么
#include <bits/stdc++.h>
using namespace std;
long long n;
long long dp[65][120][2], qpow[65];
long long solve(long long x, int l, int r, int m)
{
long long lim = __lg(x);
memset(dp, 0, sizeof(dp));
dp[1][0][0] = 1;
for (int i = 1; i <= lim + 1; ++i)
{
for (int j = 0; j <= 2 * i - 2; ++j)
{
for (int k = 0; k <= 1; ++k)
{
if(!dp[i][j][k]) continue;
for (int a = 0; a <= 1; ++a)
{
if(a && i >= l) continue;
for (int b = 0; b <= 1; ++b)
{
if(b && i >= r) continue;
if((k + a + b & 1) == bool(x & (1ll << i)))
dp[i + 1][j + a + b][(a + b + k) / 2] += dp[i][j][k];
}
}
}
}
}
return dp[lim + 2][m][0];
}
int main()
{
long long ans = 0;
cin >> n;
qpow[0] = 1;
for (int i = 1; i <= 60; ++i) qpow[i] = qpow[i - 1] * 2;
for (int i = 1; i <= 60; ++i)
{
if(qpow[i] > n) break;
long long now = n / (qpow[i] - 1);
if(now == 0) continue;
long long x = n - 1ll * now * (qpow[i] - 1);
for (int j = i - 1; j >= 0; --j) if(x >= qpow[j] - 1) x -= qpow[j] - 1;
if(!x) ++ans;
}
for (int i = 1; i <= 60; ++i)
{
if(qpow[i] > n) break;
for (int j = 1; j <= 60; ++j)
{
if(qpow[j] > n) continue;
long long now = (n - qpow[j] + 1) / (qpow[i + 1] + qpow[j + 1] - 3);
if(now <= 0) continue;
long long x = (n - qpow[j] + 1) - now * 1ll * (qpow[i + 1] + qpow[j + 1] - 3);
if(!x)
{
++ans;
continue;
}
if(i == 1 && j == 1)
{
ans += (x == 5ll * now + 1);
continue;
}
for (int k = 1; k <= i + j; ++k)
{
if(~(x + k) & 1)
ans += solve(x + k, i, j, k);
}
}
}
cout << ans << endl;
return 0;
}
方伯伯的商场之旅
还是比较有意思的一道题目。
我们发现对于合并石子中的终点,从最高位到最低位一定是单峰的。
故我们考虑去枚举这个终点,从高到低,如果在数位 DP 转移的过程中他优于之前的状态,那就更新,否则不更新。
具体来说,考虑先求出在最高位的答案,然后我们依次向最低位走,每次计算要减少的量。
感觉还是挺简单的(?
#include <bits/stdc++.h>
#define int long long
using namespace std;
int l, r, k;
int a[100], f[105][10005];
int dfs(int now, int sum, int p, int lim)
{
if (!now) return max(sum, 0LL);
if (!lim && ~f[now][sum]) return f[now][sum];
int ans = 0;
int num = lim ? a[now] : k - 1;
for (int i = 0; i <= num; i++) ans += dfs(now - 1, sum + (p == 1 ? i * (now - 1) : (now < p ? -i : i)), p, lim && (i == num));
if (!lim) f[now][sum] = ans;
return ans;
}
int solve(int x)
{
int n = 0;
while (x)
{
a[++n] = x % k;
x /= k;
}
int ans = 0;
for (int i = 1; i <= n; i++)
{
memset(f, -1, sizeof(f));
ans += (i == 1 ? 1 : -1) * dfs(n, 0, i, 1);
}
return ans;
}
signed main()
{
cin >> l >> r >> k;
cout << solve(r) - solve(l - 1) << endl;
return 0;
}
Number with Bachelors
这个题我是真不想评价啊,什么时候
题意即求在
满足条件即每个数位上的数不重复出现。
额,第
这不是最关键的,关键是你的那一坨输入输出一会十六进制一会十进制又是什么鬼。。。。
不想写这个题了,直接奉上代码:
#include <bits/stdc++.h>
using namespace std;
const int maxn = (1 << 16) + 10;
typedef unsigned long long ull;
ull dp[2][22][maxn];
int typ, a[100];
ull dfs(int x, int sta, int limit)
{
if (x == -1) return 1;
int ty = (typ == 10) ? 0 : 1;
if (dp[ty][x][sta] != -1 && !limit) return dp[ty][x][sta];
int up = limit ? a[x] : typ - 1;
ull ans = 0;
for (int i = 0; i <= up; i++)
{
if ((sta >> i) & 1) continue;
if (sta == 0 && i == 0) ans += dfs(x - 1, sta, limit && i == up);
else ans += dfs(x - 1, sta | (1 << i), limit && i == up);
}
if (!limit) dp[ty][x][sta] = ans;
return ans;
}
ull solve(ull x)
{
int num = 0;
while (x)
{
a[num++] = x % typ;
x /= typ;
}
return dfs(num - 1, 0, true);
}
void input(ull &x)
{
x = 0;
char s[22];
scanf("%s", s + 1);
int len = strlen(s + 1);
for (int i = 1; i <= len; i++)
{
if (s[i] <= '9' && s[i] >= '0') x = x * typ + s[i] - '0';
else x = x * typ + s[i] - 'a' + 10;
}
}
void print(ull x)
{
if (x == 0)
{
printf("0\n");
return;
}
if (typ == 10) printf("%llu\n", x);
else
{
vector<int> ans;
while (x)
{
int p = x % typ;
x /= typ;
ans.push_back(p);
}
for (int i = ans.size() - 1; i >= 0; i--)
{
if (ans[i] >= 10) printf("%c", ans[i] - 10 + 'a');
else printf("%c", ans[i] + '0');
}
printf("\n");
}
}
void test()
{
int t;
scanf("%d", &t);
while (t--)
{
int x;
scanf("%d%d", &x, &typ);
printf("%llu", solve(x));
}
}
int main()
{
memset(dp, -1, sizeof(dp));
int T;
scanf("%d", &T);
while (T--)
{
char op[10];
scanf("%s", op);
if (op[0] == 'd') typ = 10;
else typ = 16;
int flg;
scanf("%d", &flg);
if (!flg)
{
ull a, b;
input(a), input(b);
ull ans = solve(b);
if (a > 0) ans -= solve(a - 1);
print(ans);
}
else
{
ull x;
input(x);
if (x < 10)
{
printf("%llu\n", x - 1);
continue;
}
ull l = 0, r = 0, ans = 0;
r--;
if (solve(r) < x)
{
printf("-\n");
continue;
}
while (l <= r)
{
ull mid = l + (r - l) / 2;
if (solve(mid) >= x) r = mid - 1, ans = mid;
else l = mid + 1;
}
print(ans);
}
}
return 0;
}
后记
写完了,今天也要结束了。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】