NOIP2024集训 Day33 总结
前言
若巅峰不在,那就重踏来时之路。
今天是
简称,暴力对了,就是有手就行。
怎么说,感觉今天状态不太好,老是细节上出现一些很逆天的错误。
例如:
for (auto i = dp.begin(); i != dp.end(); ++i)
{
pair<ll, ll> j = *i;
ans = j.first * n + j.second;//你ans这么求吗,为什么不是取max,MD调死了。
}
本质上来说,最后一道题我是没有过的,但是这个
由于今天的题目质量感觉不算特别好(?,所以我就只写一部分有意义的题的题解。
Non-equal Neighbours
难评,为什么大家都会觉得这个题很简单,为什么大家都能想到容斥,为什么大家都觉得这个题容斥很好做,我破防了。
题做少了导致的。
我们观察到第二个要求,
定义满足
- 有
个有用的点的方案 有 个有用的点的方案 有 个有用的点的方案..... 以此类推。
而实际上,这个容斥系数只跟这个有用的点的个数的奇偶性相关。
以及,本质上来说,对于一个至少有
故我们考虑一个
转移比较显然,枚举一个
优化的话考虑一个单调栈,每次用更小的弹掉队首,弹掉之后更新一下答案,加入的时候也更新一下答案,注意一下容斥系数即可。
细节见代码:
#include <bits/stdc++.h>
using namespace std;
const int mod = 998244353;
#define int long long
#define maxn 200005
int a[maxn], dp[maxn][2], sum[maxn][2];
int stk[maxn], cnt = 0;
int p1(int x)
{
return (x & 1) ? mod - 1 : 1;
}
signed main()
{
int n;
scanf("%lld", &n);
for(int i = 1; i <= n; i++) scanf("%lld", &a[i]);
dp[0][0] = sum[0][0] = 1, dp[0][1] = sum[0][1] = 0;
stk[0] = 0;
for(int i = 1; i <= n; i++)
{
while(cnt && a[stk[cnt]] >= a[i]) cnt--;
stk[++cnt] = i;
dp[i][0] = ((cnt == 1 ? 0 : dp[stk[cnt - 1]][0]) + (sum[i - 1][1] - (cnt == 1 ? 0 : sum[stk[cnt - 1] - 1][1]) + mod) * a[i]) % mod;
dp[i][1] = ((cnt == 1 ? 0 : dp[stk[cnt - 1]][1]) + (sum[i - 1][0] - (cnt == 1 ? 0 : sum[stk[cnt - 1] - 1][0]) + mod) * a[i]) % mod;
sum[i][0] = (sum[i - 1][0] + dp[i][0]) % mod;
sum[i][1] = (sum[i - 1][1] + dp[i][1]) % mod;
}
printf("%lld\n", (dp[n][0] - dp[n][1] + mod) * p1(n) % mod);
return 0;
}
Mod Mod Mod
虽然是一个比较普通的有用的状态是有限的一个优化,但是现在看来还是觉得挺神奇的。
我们考虑一个朴素的
显然有一个转移:
我们考虑对这个
首先对于
而进一步的,
考虑这些一次函数是怎么从最初的一个变为后来的多个的。
显然是因为一次取模造成的,而一次取模一定是将一个一次函数分为两个一次函数,并且一此取模只能将最多一个一次函数变为两个。
而我们观察到,对于一次有效的取模,
由于我们的答案本质上只关注的是这个一次函数的最高点。而碰巧的是,如果我们知道了一个一次函数的最高点,我们可以通过这个最高点求出他裂成两个一次函数的两个最高点和分别的答案。
于是我们可以优化一下这个
其实剩下的细节就可以直接看代码了,重点是去理解这个有用的状态只有
当然这个
可以上代码了:
#include <bits/stdc++.h>
using namespace std;
#define maxn 200005
#define ll long long
int n;
ll a[maxn];
map<ll, ll> dp;
int main()
{
scanf("%d", &n);
for (int i = 1; i <= n; ++i) scanf("%lld", &a[i]);
dp[a[1] - 1] = 0;
for (int i = 2; i <= n; ++i)
{
for (auto j = dp.lower_bound(a[i]); j != dp.end(); dp.erase(j++))
{
pair<ll, ll> k = *j;
dp[k.first % a[i]] = max(dp[k.first % a[i]], k.second + (k.first - k.first % a[i]) * (i - 1));
dp[a[i] - 1] = max(dp[a[i] - 1], k.second + (i - 1) * ((k.first + 1) / a[i] * a[i] - a[i]));
}
}
ll ans = 0;
for (auto i = dp.begin(); i != dp.end(); ++i)
{
pair<ll, ll> j = *i;
ans = max(ans, j.first * n + j.second);
}
cout << ans << endl;
}
History / 历史年份
首先对于字符串中判断
考虑到你要让最后一个数越小,而前面的数越大,我们可以考虑先确定最小的最后一个数,然后去看最大的最前面的数,分开算即可。
首先定义一个
显然对于
转移就是枚举一个
所以此时最后一个数最小就是
我们再考虑一个
当然注意一下
这样一个
大致是这样:
#include <bits/stdc++.h>
using namespace std;
#define maxn 2005
#define ull unsigned long long
char a[maxn];
ull Has[maxn], p[maxn];
int n;
ull get(int l, int r) {return Has[r] - Has[l - 1] * p[r - l + 1];}
int pos[maxn];
bool cmp(int l1, int r1, int l2, int r2)
{
l1 = min(pos[l1], r1), l2 = min(pos[l2], r2);
if(r1 - l1 + 1 != r2 - l2 + 1) return r1 - l1 + 1 < r2 - l2 + 1;
int l = 1, r = (r2 - l2 + 1), mid, ans = 0;
while(l <= r)
{
mid = (l + r) >> 1;
if(get(l1, l1 + mid - 1) == get(l2, l2 + mid - 1)) l = mid + 1, ans = mid;
else r = mid - 1;
}
if(ans == r2 - l2 + 1) return false;
return a[l1 + ans] < a[l2 + ans];
}
int dp[maxn], f[maxn];
int main()
{
p[0] = 1;
for (int i = 1; i <= 2000; ++i) p[i] = p[i - 1] * 13331;
while(~scanf("%s", a + 1))
{
n = strlen(a + 1);
for (int i = 1; i <= n; ++i) Has[i] = Has[i - 1] * 13331 + a[i];
memset(dp, 0xf3, sizeof(dp));
memset(f, 0xf3, sizeof(f));
pos[n + 1] = n + 1;
for (int i = n; i >= 1; --i)
{
if(a[i] == '0') pos[i] = pos[i + 1];
else pos[i] = i;
}
dp[1] = 1;
for (int i = 2; i <= n; ++i)
{
dp[i] = 1;
for (int j = i - 1; j >= 1; --j)
{
if(cmp(dp[j], j, j + 1, i))
{
dp[i] = j + 1;
break;
}
}
}
int now = dp[n];
f[dp[n]] = n;
for (int i = dp[n] - 1; i; --i)
{
if(a[i] != '0') break;
f[i] = n;
now = i;
}
for (int i = now - 1; i >= 1; --i)
{
for (int j = n; j >= i + 1; --j)
{
if(f[j] == 0xf3f3f3f3) continue;
if(cmp(i, j - 1, j, f[j]))
{
f[i] = j - 1;
break;
}
}
}
// for (int i = 1; i <= n; ++i) cout << i << " " << dp[i] << " " << f[i] << endl;
now = 1;
while(1)
{
for (int i = now; i <= f[now]; ++i) putchar(a[i]);
now = f[now] + 1;
if(now > n) break;
putchar(',');
}
puts("");
}
}
考虑一下如何优化,先只看
换句话说,每当我们求出一个
你可以考虑写一个线段树,当然双指针也是可以的。
而
于是这个题就有了,但是我还没有写,代码的话你可以参见
#include<bits/stdc++.h>
using namespace std;
#define N 105050
int f[N],g[N];
char s[N];
#define ull unsigned long long
const ull p=13331;
ull pw[N],h[N];
ull get(int l,int r){
return h[r]-h[l-1]*pw[r-l+1];
}
int pos0[N];
bool com(int l1,int r1,int l2,int r2){
l1=min(r1+1,pos0[l1]),l2=min(r2+1,pos0[l2]);
int len1=r1-l1+1,len2=r2-l2+1;
if(len1==len2){
if(get(l1,r1)==get(l2,r2))return 0;
int L=1,R=len1;
while(L<R){
int mid=L+R>>1;
if(get(l1,l1+mid-1)==get(l2,l2+mid-1))L=mid+1;
else R=mid;
}
return s[l1+L-1]<s[l2+L-1];
}
return len1<len2;
}
vector<int>gxf[N],gxg[N];
int q[N],ha,ti,delt[N];
signed main(){
ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
pw[0]=1;
for(int i=1;i<=2000;++i)pw[i]=pw[i-1]*p;
while(cin>>s+1){
int n=strlen(s+1);
for(int i=1;i<=n;++i){
h[i]=h[i-1]*p+s[i];gxf[i].clear();gxg[i].clear();delt[i]=0;
}pos0[n+1]=n+1;
int cnt=0;
for(int i=n;i;--i){
if(s[i]=='0')pos0[i]=pos0[i+1];
else pos0[i]=i;
cnt+=(s[i]=='0');
}
if(n-cnt<=1){
cout<<s+1<<"\n";continue;
}
for(int i=1;i<=n;++i)f[i]=g[i]=0;
int mx=0;
for(int i=1;i<=n;++i){
for(auto x:gxf[i])mx=max(mx,x);
f[i]=mx;++f[i];
if(i!=n&&com(f[i],i,i+1,n)){
int l=i+1,r=n;
while(l<r){
int mid=l+r>>1;
if(com(f[i],i,i+1,mid))r=mid;
else l=mid+1;
}
gxf[l].push_back(i);
}
}
s[0]='1';
g[f[n]]=n;
set<int>sta;
int o=f[n]-1;
for(;o&&s[o]=='0';)g[o]=n,--o;ha=1,ti=0;
for(int i=o+1;i<=f[n];++i){
if(i>1&&com(i-1,i-1,i,g[i])){
int l=1,r=i-1;
while(l<r){
int mid=l+r>>1;
if(com(mid,i-1,i,g[i]))r=mid;
else l=mid+1;
}
delt[i]=l-1;
}
else delt[i]=0x3f3f3f3f;
}
int up=f[n];
for(int i=o;i;--i){
while(delt[up]>=i)--up;
g[i]=up;--g[i];
if(i>1&&com(i-1,i-1,i,g[i])){
int l=1,r=i-1;
while(l<r){
int mid=l+r>>1;
if(com(mid,i-1,i,g[i]))r=mid;
else l=mid+1;
}
delt[i]=l-1;
}
else delt[i]=0x3f3f3f3f;
}
int p=1;
while(p<=n){
for(int i=p;i<=g[p];++i)cout<<s[i];if(g[p]!=n)cout<<",";p=g[p]+1;
}cout<<"\n";
}
}
后记
也是艰难写完总结,写完才发现怎么讨论区有一个标题跟我一模一样的啊(((
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】