前言
95+40+4+5=144 pts,wssb
NOIP 后两个小时加起来拿了 9 分的高分,不如冲 T2。
T1 Candy
题目
简单题,忘记特判钱是否够痛失 5 分。
T2 Sale
题目
考场上大概有些思路,但当时在发烧,脑子比较混乱,故去打 T3,T4 暴力,然后,就没有然后了,比赛结束了。。。
考虑容斥将其转换成总方案减去不是最优的方案。
可以先考虑 \(m=2\) 的部分分,我觉得是具有启发性的。
若 \(w_1=1\) 肯定是最优的,不符合条件。
当 \(w_1=2\) 时需要考虑什么时候会出现不优的情况。样例告诉我们:因为是按性价比排序,因此可能会出现一个 \(i\) 满足 \(w_i=1\) 且 \(a_i>\frac{a_1}{2}\),此时会先去买第 \(i\) 件物品而导致买不起第 \(1\) 件物品,从而去买第一个 \(w_j=1\) 的物品或是没有买的起的物品。若 \(a_i+a_j<a_1\),则此时不是最优的,因为可以直接去买第 \(1\) 件物品。
可以枚举满足条件的 \(i,j\),从而计算答案。此时会有 \(i\) 前面所有物品的 \(w\) 均为 \(2\),\(w_{i+1\sim j-1}=2\),此时 \(j+1\sim n\) 的值可以任意取,因此贡献为 \(2^{n-j}\)。
或是只有一个 \(i\) 此时除 \(i\) 的 \(w\) 均为 \(2\),贡献为 \(1\)。
代码长这样:
ll tp=0;
for(int i=2;i<=n;i++){if(a[i]*2<=a[1]) break;if(a[i]^a[1]) tp++;while(tp>=mod) tp-=mod;for(int j=i+1;j<=n;j++){if(a[i]+a[j]<a[1]) tp=(tp+K[n-j])%mod;}
}
ll ans=(K[n]-tp)%mod+mod;
if(ans>=mod) ans-=mod;
注意要特判 \(a_i=a_1\) 的情况。
正解:
会发现最后一定是最后买的物品出现不优的情况,就像 \(m=2\) 一样。
可以枚举 \(i,j\) 表示最后买 \(i\) 的物品是最优的,但去买了 \(j\) 物品。此时有两种情况:
-
只有 \(w_j=1\),此时可以枚举 \(w_k=2(k<i)\) 的个数,贡献为 \(\sum\limits_{p=0}^{i-1} \binom{i-1}{p}\binom{j-i-1}{m-p-i-1}=\binom{j-2}{m-i-1}\)。(范德蒙德卷积)
-
存在 \(w_k=2\),可以枚举 \(k\),有 \(w_{j+1\sim k-1}=2\),有贡献为 \(2^{n-k}\times \binom{j-2}{m-i-1}\)。
复杂度为 \(\mathcal{O(n^3)}\)。
发现可以拿指针维护最大满足条件的 \(k\),则贡献为 \(\binom{j-2}{m-i-1}+\sum\limits_{x=k}^n2^{n-x}\binom{j-2}{m-i-1}=2^{n-k+1}\binom{j-2}{m-i-1}\)。
要预处理 \(2\) 的幂次。
Code
点击查看代码
#include<bits/stdc++.h>
#define IOS cin.tie(0),cout.tie(0),ios::sync_with_stdio(0)
#define ll long long
#define db double
#define pb push_back
#define eb emplace_back
#define MS(x,y) memset(x,y,sizeof(x))
#define MC(x,y) memcpy(x,y,sizeof(x))
#define PLL pair<ll,ll>
#define PII pair<int,int>
//#define Tp template<typename T>
//#define Ts template<typename T,typename ...args>
#define lb(x) ((x)&(-x))
using namespace std;
const int N=5e3+20,M=1e5+20,MX=5000;
const ll INF=1ll<<60,mod=998244353;
namespace H_H{int n,m;ll a[N];ll pre[N],prn[N],K[N];inline ll qpow(ll x,ll y){ll Ans=1;for(;y;y>>=1,x=x*x%mod) if(y&1) Ans=Ans*x%mod;return Ans;}inline ll binom(ll x,ll y){if(x<y) return 0;return pre[x]*prn[y]%mod*prn[x-y]%mod;}inline void init(){K[0]=1;for(int i=1;i<=MX;i++) K[i]=K[i-1]*2%mod;pre[0]=prn[0]=1;for(int i=1;i<=MX;i++) pre[i]=pre[i-1]*i%mod;prn[MX]=qpow(pre[MX],mod-2);for(int i=MX-1;i;i--) prn[i]=prn[i+1]*(i+1)%mod;}inline void solve(){cin>>n>>m;for(int i=1;i<=n;i++) cin>>a[i];sort(a+1,a+1+n,greater<>());ll ans=0;for(int i=1;i<=n;i++){if(m-i-1<0) break;ll k=n+1;for(int j=i+1;j<=n;j++){if(a[i]==a[j]) continue;if(a[j]*2<=a[i]) break;while(k-1>j && a[k-1]+a[j]<a[i]) k--;ans=(ans+K[n-k+1]*binom(j-2,m-i-1)%mod)%mod;}}cout<<(K[n]-ans+mod)%mod<<"\n";}int main(){init();int C=0,T=1;cin>>C>>T;while(T--) solve();return 0;}
}
int main(){IOS;H_H::main();return 0;
}
T3
T4
\(qn^2\log n\) 跑不过 \(qn^3\)。这就是人傻常数大吗?
好题。
先求前缀和,为 \(a_{0\dots n-1}\)。
考虑固定 \(r\) 求所有 \(l\) 造成的贡献。
由于区间长度为 \([L,R]\),因此 \(l\in [r-R,r-L]\)。可以维护一个 \(a\) 递减的序列 \(q_1,\dots,q_k\)。
对于序列里的两个位置 \(q_i,q_{i+1}\)。对于答案 \((q_i,q_{i+1}]\) 的贡献为 \(a_r-a_{q_i}\),序列的最后一个数对答案 \((q_k,r]\) 的贡献为 \(a_r-a_{q_k}\)(贡献即为对 ans 取 max)。。可以分别计算贡献。
-
维护序列最后一个数的贡献:可以倒着枚举 \(r\),会有 \(q_k\) 是不增的,用 ST 表维护 \(q_k\),单调队列维护 \(q_k\) 到 \(r\) 的贡献,可以看代码。
-
维护序列中间的贡献:会有 \(q_{i+1}\) 为 \(q_i\) 右边第一个 \(a\) 比它小的数,可以单调栈预处理。对于 \(i\),设其右边第一个比它小的数的位置为 \(nxt_i\)(没有则 \(nxt_i=n\))。可以看做对于区间 \((i,nxt_i]\) 有 \(\max\limits_{r,r-L\ge nxt_i \wedge r-R\le i} a_r-a_i=\max\limits_{r=nxt_i+L}^{i+R} a_r-a_i\) 可以拿 ST 表维护。可是每一个点会被很多个区间覆盖,最简单的方法是拿线段树维护,但会 TLE。
会发现每个区间的关系为包含但不交,因此可以把区间看成编号为 \(i+1\) ,权值为这个区间的贡献的点,\(i+1\) 到 \(j+1\) 有一条边,其中 \(j\) 为最大的满足 \((j,nxt_j]\) 包含 \((i,nxt_i]\)。那么 \(i\) 点的答案为它所有祖先的权值的 \(\max\) 遍历一遍树就可以了。
Tip:可以不用建树,对于 \(i\) 点它父亲的编号一定比它小,可以用类似于 dp 的方式处理。
Code
点击查看代码
#include<bits/stdc++.h>
#define IOS cin.tie(0),cout.tie(0),ios::sync_with_stdio(0)
#define ll long long
#define db double
#define pb push_back
#define eb emplace_back
#define MS(x,y) memset(x,y,sizeof(x))
#define MC(x,y) memcpy(x,y,sizeof(x))
#define PLL pair<ll,ll>
#define PII pair<int,int>
#define fi first
#define se second
//#define Tp template<typename T>
//#define Ts template<typename T,typename ...args>
#define lb(x) ((x)&(-x))
using namespace std;
const int N=5e4+20,M=1e5+20;
const ll INF=1ll<<60,mod=998244353;
namespace H_H{int n,m,ff[N],pre[N],mn[N][17],nxt[N];ll a[N],ans[N],ans1[N],ans2[N];ll K[N],mx[N][17];set<PII> s;inline int get(int x,int y){if(x>y) swap(x,y);return a[x]<=a[y]?x:y;}inline int qmn(int l,int r){while(l>r) return -1;int k=K[r-l+1];return get(mn[l][k],mn[r-(1<<k)+1][k]);}inline ll qmx(int l,int r){while(l>r) return -INF;int k=K[r-l+1];return max(mx[l][k],mx[r-(1<<k)+1][k]);}int st[N],top;PII b[N];inline void init(){ans1[0]=-INF;for(int i=1;i<=n;i++) a[i]+=a[i-1];for(int i=2;i<=n+1;i++) K[i]=K[i>>1]+1;for(int i=1;i<=n;i++){mn[i][0]=i;mx[i][0]=a[i];}for(int j=1;j<=K[n];j++){//ST 表for(int i=1;i+(1<<j)-1<=n;i++){mx[i][j]=max(mx[i][j-1],mx[i+(1<<(j-1))][j-1]);}}for(int j=1;j<=K[n+1];j++){for(int i=0;i+(1<<j)-1<=n;i++){mn[i][j]=get(mn[i][j-1],mn[i+(1<<(j-1))][j-1]);}}st[0]=n;for(int i=n-1;~i;i--){//单调栈while(top && a[st[top]]>a[i]) top--;nxt[i]=st[top];st[++top]=i;b[i]={i+1,nxt[i]};}for(int i=n-1;~i;i--){//找到第一个把它覆盖的点int r=nxt[i];auto itt=s.upper_bound({r,n+1});for(auto it=s.begin();it!=itt;it++) ff[(*it).second]=i+1;s.erase(s.begin(),itt);s.insert({r,i+1});}}inline void Init(){for(int i=1;i<=n;i++){ans[i]=ans1[i]=ans2[i]=-INF;pre[i]=0;}}inline void get_ans(){for(int i=1;i<=n;i++){//与其祖先取 maxans1[i]=max(ans1[i],ans1[ff[i]]);}for(int i=1;i<=n;i++){ans[i]=max(ans1[i],ans2[i]);}}int fr,tl;PLL q[N];int main(){cin>>n;for(int i=1;i<=n;i++) cin>>a[i];init();cin>>m;for(int tt=1,L,R;tt<=m;tt++){cin>>L>>R;Init();fr=1,tl=0;for(int i=n;i;i--){if(i>=L){int pos=qmn(max(0,i-R),i-L);ll tp=a[i]-a[pos];while(fr<=tl && q[tl].se<=tp) tl--;//单调队列维护q[++tl]={pos+1,tp};}while(fr<=tl && q[fr].fi>i) fr++;if(fr<=tl) ans2[i]=q[fr].se;}for(int i=0;i<n;i++){ans1[i+1]=qmx(nxt[i]+L,min(n,i+R))-a[i];}get_ans();unsigned ll Ans=0;for(int i=1;i<=n;i++) Ans^=ans[i]*i;cout<<Ans<<"\n";}return 0;}
}
int main(){IOS;H_H::main();return 0;
}