NOIP2024 前集训:NOIP2024加赛 4
前言
正确的有两天的模拟赛还没有改完,因为本来说今天没有模拟赛准备今天改的,结果突然又来了一场。
直接搬的梦熊的比赛,T1 是签,T2 是板(虽然我赛时不会),T3 是大粪讨,T4 是不可做。
T1 唐了不短的时间,但是看提交记录好像还是过得比较早的?因为不是计数题自动忽略模数,导致炸 long long,然后开 __int128 过了小数据发现大样例为啥输出这么小?发现有模数,加了模数后发现离谱的大样例错了?原来刚开始懒的处理 \(nxt\) 直接处理 \(dis\) 硬加的,有了模数就错了,遂加上 \(nxt\) 过了,其实就是没有这两个打得也不快,因为感觉太简单了不慌不忙的打的一点没着急,然后打出来正解后还是特判了所有部分分,不知道当时咋想的会正解还是先打部分分,可能之前挂分挂习惯了 qwq。
T1 第一发忘关 #define int long long
了,但是还是过了,连忙又交了一发,现在还是最优解(甚至比 \(O(n)\) 爆标做法还快)。
然后看 T2,打了所有部分分(除了正解),结果暴力的 \(10pts\) 还没有,因为 \(k\) 打成了 \(j\),发现 \(n\le 10^6\) 一眼鉴定为不可能是 DP,但我并没有想出来贪心,其实 T2 是反悔贪心板子,但我当时并不会。
看 T3,想着拿 \(25pts\) 的部分分再打一点暴力,但是发现并不会 \(25pts\) 部分分,于是摆了回去想 T2,后面就是罚坐了,T3、T4 遂没交。
T1 王国边缘
\(m\) 给定?一眼鉴定为倍增,所以只需要处理出跳一次的,这部分是可以 \(O(n)\) 扫的,然后直接倍增即可,查询直接和求 \(k\) 级祖一样跳即可。
具体的 \(n\le m\) 和 \(n>m\) 需要分讨,对于 \(n\le m\) 的,找到 \([i,i+m]\) 内最后一个 \(1\) 的位置,将原串复制一遍拼到后面从后往前开个队列扫一遍即可;对于 \(n>m\) 的,若全是 \(0\) 直接特判掉,否则一定调到一个 \(1\) 的位置,处理出每个 \(i\) 前离他最近的 \(1\) 的位置,记为 \(pre_i\),则 \(nxt_i=pre_{i+m}\),这个也可以复制一遍原串从前往后扫一遍处理出来。
这个复杂度是 \(O(n\log v)\) 的,但是常数小跑的飞快,luogu 最优解能在第一页,在我前面的全是 \(O(n)\) 的。
Pursuing_OIer 等人有 \(O(n)\) 爆标做法,因为他是一棵基环树,前面忘了中间忘了后面忘了……
然后因为他是基环树,所以还有 \(O(n\log n)\) 做法,前面忘了中间忘了后面忘了……
点击查看代码
#include<bits/stdc++.h>
#define ll long long
#define endl '\n'
#define sort stable_sort
using namespace std;
const int N=2e5+10,P=1e9+7;
template<typename Tp> inline void read(Tp&x)
{
x=0;register bool z=true;
register char c=getchar_unlocked();
for(;!isdigit(c);c=getchar_unlocked()) if(c=='-') z=0;
for(;isdigit(c);c=getchar_unlocked()) x=(x<<1)+(x<<3)+(c^48);
x=(z?x:~x+1);
}
template<typename T,typename ...Tp> inline void read(T &x,Tp &...y){read(x);read(y...);}
template<typename Tp> inline void wt(Tp x){if(x>9)wt(x/10);putchar_unlocked((x%10)+'0');}
template<typename Tp> inline void write(Tp x){if(x<0)putchar_unlocked('-'),x=~x+1;wt(x);}
template<typename T,typename ...Tp> inline void write(T x,Tp ...y){write(x);putchar_unlocked(' ');write(y...);}
int n,q,pre[N],nxt[65][N],dis[65][N]; ll m; char s[N<<1]; queue<int>que;
inline int calc(ll x,int tmp=0) {return !(tmp=x%n)?n:tmp;}
inline int mod(int x,int y) {return (x+=y)>=P?x-P:x;}
signed main()
{
freopen("kingdom.in","r",stdin),freopen("kingdom.out","w",stdout);
read(n,m,q),scanf("%s",s+1); for(int i=1;i<=n;i++) s[n+i]=s[i];
if(m<=n)
{
for(int i=(n<<1);i;i--)
{
while(!que.empty()&&que.front()-i>m) que.pop();
if(i<=n&&!que.empty())
nxt[0][i]=calc(que.front()),dis[0][i]=que.front()-i;
else if(i<=n) nxt[0][i]=calc(i+1),dis[0][i]=1;
if(s[i]=='1') que.push(i);
}
for(int i=1;i<=60;i++) for(int j=1;j<=n;j++)
{
nxt[i][j]=nxt[i-1][nxt[i-1][j]];
dis[i][j]=mod(dis[i-1][j],dis[i-1][nxt[i-1][j]]);
}
for(ll st,k,res;q;q--)
{
read(st,k),res=st%P,st=calc(st);
for(int i=60;~i;i--) if((k>>i)&1)
res=mod(res,dis[i][st]),st=nxt[i][st];
write(res),puts("");
}
return 0;
}
bool flag=0; for(int i=1;i<=n;i++) if(s[i]=='1') {flag=1; break;}
if(!flag)
{
for(ll st,k,res;q;q--)
read(st,k),write(mod(st%P,k%P)),puts("");
return 0;
}
flag=0; for(int i=1;i<=n;i++) if(s[i]=='0') {flag=1; break;}
if(!flag)
{
for(ll st,k,res;q;q--)
read(st,k),write(mod(st%P,(k%P)*(m%P)%P)),puts("");
return 0;
}
for(int i=1,now=0;i<=(n<<1);i++)
(s[i]=='1')&&(now=i),pre[calc(i)]=i-now;
for(int i=1;i<=n;i++)
{
nxt[0][i]=calc(i+m-pre[calc(i+m)]);
dis[0][i]=(m-pre[calc(i+m)])%P;
}
for(int i=1;i<=60;i++) for(int j=1;j<=n;j++)
{
nxt[i][j]=nxt[i-1][nxt[i-1][j]];
dis[i][j]=mod(dis[i-1][j],dis[i-1][nxt[i-1][j]]);
}
for(ll st,k,res;q;q--)
{
read(st,k),res=st%P,st=calc(st);
for(int i=60;~i;i--) if((k>>i)&1)
res=mod(res,dis[i][st]),st=nxt[i][st];
write(res),puts("");
}
}
T2 买东西题
反悔贪心板子,就是正常贪心加个反悔的过程。
对于一个 \((w,v)\) 给 \((a_i,b_i)\) 比给 \((a_j,b_j)\) 更优,满足 \(a_i-b_i<a_j-b_j\) 这是显然的,所以记 \(c_i=a_i-b_i\),条件允许下优先给 \(c_i\) 更小的。
现在分别按照 \(a_i,w_i\) 升序排序,那么决策集合是单调不降的,假设 \([1,i-1]\) 是已经匹配好的最优策略,考虑加入一个新的 \(i\),可以从之前已经被分配的优惠券中抢一张过来,那么显然选择已经被优惠的中 \(c_j\) 最大的抢过来,此时 \(\Delta=c_i-c_j\),也可以用之前未用过的优惠券,此时 \(\Delta=c_i-v\)。
那么就有了贪心策略,把合法的优惠券扔进堆里,若已经和物品 \(j\) 匹配则另 \(d_i=c_j\),否则 \(d_i=v_i\),每次取堆顶判是否更优即可,若 \(\Delta<0\) 说明更优。
点击查看代码
#include<bits/stdc++.h>
#define ll long long
#define endl '\n'
#define sort stable_sort
using namespace std;
const int N=1e6+10;
template<typename Tp> inline void read(Tp&x)
{
register char c=getchar_unlocked();
for(;!isdigit(c);c=getchar_unlocked());
for(;isdigit(c);c=getchar_unlocked()) x=(x<<1)+(x<<3)+(c^48);
}
template<typename T,typename ...Tp> inline void read(T &x,Tp &...y){read(x);read(y...);}
template<typename Tp> inline void wt(Tp x){if(x>9)wt(x/10);putchar_unlocked((x%10)+'0');}
template<typename Tp> inline void write(Tp x){if(x<0)putchar_unlocked('-'),x=~x+1;wt(x);}
template<typename T,typename ...Tp> inline void write(T x,Tp ...y){write(x);putchar_unlocked(' ');write(y...);}
int n,m; ll ans; priority_queue<int>q; pair<int,int>e[N],g[N];
signed main()
{
freopen("buy.in","r",stdin),freopen("buy.out","w",stdout);
read(n,m);
for(int i=1,a,b;i<=n;i++) read(e[i].first,e[i].second);
for(int i=1,w,v;i<=m;i++) read(g[i].first,g[i].second);
sort(e+1,e+1+n),sort(g+1,g+1+m);
for(int i=1,j=1;i<=n;i++)
{
while(j<=m&&g[j].first<=e[i].first) q.push(g[j++].second);
if(q.empty()||e[i].first-e[i].second>q.top()) ans+=e[i].second;
else ans+=e[i].first-q.top(),q.pop(),q.push(e[i].first-e[i].second);
}
write(ans);
}
T3 IMAWANOKIWA (Construction ver.)
大粪讨去死吧。
T4 魔法少女们
高级计数加卡常加根号分值加不会的知识点的不可做题,咕了咕了。