686 字
2 分钟
NOIP2025(noi plus 2025) 简略题解
2025-12-01

我会永远记住这一天。

我们不难注意到:noip2025=noi plus 2025

P14636 NOIP2025 T2#

第一步就没想到。

按照性价比大到小排序后,发现取不到最大值的情况是:

  • 选的最后一个 w=1w=1(否则选取的一定是前缀,是最大的)。
  • 此时选的情况应该是:...i222..(已选前缀)j222..(不选)k(选)...(钱花完了),其中 wi=1,wj=2,wk=1w_i=1,w_j=2,w_k=1
  • 解释一下就是,先一直选,选到只剩 11 块钱了,跳过中间为 22 的连续段,选后面的第一个 11
  • 那么有可能不是最大的情况当且仅当:ai+ak<aja_i+a_k<a_j,即可以通过把两个 w=1w=1 性价比最低的糖果换成一个性价比高的 w=2w=2 的糖果。

然后排序 aa,枚举 i,ji,j,让 wi=1,wj=2w_i=1,w_j=2,因此要满足 ai<aj<2aia_i<a_j<2a_i(满足 ii 性价比大于 jj)。

  • 原价大于 aja_j 的,一定会选(jj 是必选前缀的后面第一个),这样的数有 njn-j 个。
  • 原价大于 aia_i 小于 aja_j 的,只有 w=1w=1 时会被选。
  • 原价大于 aj2\dfrac{a_j}{2} 小于 aia_i 的,如果 w=1w=1,那么 i,ji,j 之间就不全是 22,所以,w=2w=2。都不会被选。

那么 kk 如何求?我们找到满足 ak+ai<aja_k+a_i<a_j 的最大的 kmk_m,下面证明满足条件。 akm+ai<aj<2aiakm<ai a_{k_m}+a_i<a_j<2a_i \to a_{k_m} < a_i

所以 2akm<akm+ai<aj2a_{k_m}<a_{k_m}+a_i<a_j,即 kmk_m 的性价比比 jj 低。

那么,在 kmk_m 及以前的 aa 都可能成为 kk,这时,我们在前 kmk_m 个中任意定价 1,21,2 都满足要求,所以方案数是 2km2^{k_m}

kmk_mii 之间都是确定的,方案数为 11。到此时剩余价钱为 m2m-2,此时问题变成:

ji1j-i-1 个 zbj1 为 0/10/1,还有 njn-j 个 zbj2 为 1/21/2。满足所有 zbj 的总和为 m2m-2,求方案数。

不难想到,把 zbj2 的先扣去 1,变为 (1+0/1)(1+0/1),此时相当于在 ni1n-i-1 中选出 m2(nj)m-2-(n-j) 个数的方案数。所以方案数为 (mn+j2ni1)\binom{m-n+j-2}{n-i-1}

然后似乎就做完了。

#include <cstdio>
#include <algorithm>
using namespace std;
const int MOD = 998244353;
int n , m;
struct Num {
int a , i;
inline bool operator < (const Num b){ if(a == b.a) return i < b.i; return a < b.a; }
}a[5005];
int pw[10005] , C[10005][10005];
inline void init(int N){
pw[0] = 1; for(int i = 1;i <= N;i++) pw[i] = pw[i - 1] * 2LL % MOD;
C[0][0] = 1;
for(int i = 1;i <= N;i++){
C[i][0] = 1;
for(int j = 1;j <= i;j++)
if((C[i][j] = C[i - 1][j] + C[i - 1][j - 1]) >= MOD)
C[i][j] -= MOD;
}
}
int main(void){
int c , T; scanf("%d%d" , &c , &T);
init(10000);
while(T--){
scanf("%d%d" , &n , &m);
for(int i = 1;i <= n;i++) scanf("%d" , &a[i].a) , a[i].i = i;
sort(a + 1 , a + 1 + n);
int ans = 0;
for(int i = 1;i <= n;i++){
int km = 0;
for(int j = i + 1;j <= n;j++){
if(a[j].a >= (a[i].a << 1)) break;
while(km < i && a[km + 1].a + a[i].a < a[j].a) km++;
if(m - n + j < 2 || a[i].a == a[j].a) continue;
// printf("%d %d %lld\n" , i , j , 1LL * pw[km] * C[n - i - 1][m - n + j - 2] % MOD);
if((ans += 1LL * pw[km] * C[n - i - 1][m - n + j - 2] % MOD) >= MOD)
ans -= MOD;
}
}
printf("%d\n" , (pw[n] - ans + MOD) % MOD);
}
}

P14638 NOIP2025 T4#

#include <cstdio>
#include <algorithm>
#include <cstring>
#include <queue>
#include <vector>
using namespace std;
char buf[1 << 21] , *p1 , *p2;
#define GC (p1 == p2 && (p2 = (p1 = buf) + fread(buf , 1 , 1 << 21 , stdin) , p1 == p2)? EOF : *p1++)
inline long long read(){
long long ret = 0; bool f = 0; char c = GC;
while(c < '0' || c > '9'){
if(c == '-') f = 1;
c = GC;
}
while(c >= '0' && c <= '9'){
ret = ret * 10 + (c ^ 48);
c = GC;
}
return f? -ret : ret;
}
int n , q; long long a[50005];
long long st[50005][20]; int lg[50005];
namespace baoli {
void solve(int L , int R){
unsigned long long output = 0;
for(int i = 1;i <= n;i++){
long long ans = -1e16;
for(int l = 1;l <= i;l++) {
int rl = max(i , L + l - 1) , rr = min(n , R + l - 1);
if(rl > rr) continue;
int le = lg[rr - rl + 1];
ans = max(ans , max(st[rl][le] , st[rr - (1 << le) + 1][le]) - a[l - 1]);
}
output ^= (unsigned long long)i * ans;
}
printf("%llu\n" , output);
}
}
namespace A {
deque <pair <long long , int> > qu;
long long ans[50005];
void solve(int L , long long ans[50005]){
while(!qu.empty()) qu.pop_back();
for(int l = 1;l + L - 1 <= n;l++){
int r = l + L - 1;
while(!qu.empty() && qu.front().second < l) qu.pop_front();
while(!qu.empty() && qu.back().first < a[r] - a[l - 1]) qu.pop_back();
qu.push_back(make_pair(a[r] - a[l - 1] , r));
ans[l] = qu.front().first;
//a[r] - a[l - 1];
}
for(int i = n - L + 2;i <= n;i++){
while(!qu.empty() && qu.front().second < i) qu.pop_front();
ans[i] = qu.front().first;
}
}
void print(){
unsigned long long output = 0;
for(int i = 1;i <= n;i++) output ^= (unsigned long long)i * ans[i];
printf("%llu\n" , output);
}
}
namespace B {
long long ans[33][50005];
long long qwq[50005][33][6];
void init(){
for(int i = 1;i <= 32;i++) A::solve(i , ans[i]);
for(int i = 1;i <= n;i++) for(int j = 1;j <= 32;j++) qwq[i][j][0] = ans[j][i];
for(int o = 1;o <= n;o++){
for(int k = 1;k <= 5;k++) for(int i = 1;i + (1 << k) <= 33;i++)
qwq[o][i][k] = max(qwq[o][i][k - 1] , qwq[o][i + (1 << (k - 1))][k - 1]);
}
}
void solve(int L , int R){
unsigned long long output = 0;
for(int i = 1;i <= n;i++){
int le = lg[R - L + 1];
output ^= (unsigned long long)i * max(qwq[i][L][le] , qwq[i][R - (1 << le) + 1][le]);
}
printf("%llu\n" , output);
}
}
namespace AB {
long long tmp[50005] , ans[50005];
void solve(int L , int R){
for(int j = 1;j <= n;j++) ans[j] = -1e16;
for(int i = L;i <= R;i++){
A::solve(i , tmp);
for(int j = 1;j <= n;j++) ans[j] = max(ans[j] , tmp[j]);
}
unsigned long long output = 0;
for(int i = 1;i <= n;i++) output ^= (unsigned long long)i * ans[i];
printf("%llu\n" , output);
}
}
int main(void){
// freopen("query.in" , "r" , stdin);
// freopen("query.out" , "w" , stdout);
n = read();
for(int i = 1;i <= n;i++) a[i] = read() + a[i - 1];
for(int i = 1;i <= n;i++) st[i][0] = a[i];
for(int i = 2;i <= n;i++) lg[i] = lg[i >> 1] + 1;
for(int k = 1;k <= lg[n];k++) for(int i = 1;i + (1 << k) - 1 <= n;i++)
st[i][k] = max(st[i][k - 1] , st[i + (1 << (k - 1))][k - 1]);
q = read();
B::init();
for(int _ = 1;_ <= q;_++){
int L , R; L = read(); R = read();
if(R <= 32){ B::solve(L , R); continue; }
if(L == R){
A::solve(L , A::ans); A::print();
continue;
}
if(R - L <= 32){ AB::solve(L , R); continue; }
baoli::solve(L , R);
}
}

可以发现,如果把区间放到矩形上,查询相当于一个梯形。

对于ii ,实际上是绿色线的移动,移动是 O(n)O(n) 级别的。而红线是不变的。

如果 2LR2L\ge R,可以拆成平行于 xx 和平行于 yy 的两个平行四边形,直接用两个单调队列算,两个分别按 ll ,和 rr ,另一维可以用 st 表取区间最大值作为这个点的答案(特殊性质 D)。

#include <cstdio>
#include <algorithm>
#include <cstring>
#include <queue>
using namespace std;
typedef long long ll;
char buf[1 << 21] , *p1 , *p2;
#define GC (p1 == p2 && (p2 = (p1 = buf) + fread(buf , 1 , 1 << 21 , stdin) , p1 == p2)? EOF : *p1++)
inline int read(){
int ret = 0; char c = GC; bool f = 0;
while(c < '0' || c > '9'){ if(c == '-') f = 1; c = GC; }
while(c >= '0' && c <= '9'){
ret = (ret << 1) + (ret << 3) + (c ^ 48);
c = GC;
}
return f? -ret : ret;
}
int n , a[50005]; ll b[50005] , stmi[50005][20] , stmx[50005][20];
inline ll st_query(ll st[50005][20] , int l , int r){
l = max(l , 1); r = min(r , n);
if(l > r) return 1145141919810;
int le = __lg(r - l + 1); return max(st[l][le] , st[r - (1 << le) + 1][le]);
}
deque <pair <int , ll> > q;
ll ans[50005];
inline void solve1(int L , int R){
while(!q.empty()) q.pop_back();
for(int i = 1;i <= n;i++){
while(!q.empty() && q.front().first < i) q.pop_front();
ll x = st_query(stmx , i + L - 1 , i + R - 1);
if(x != 1145141919810){
x -= b[i - 1];
while(!q.empty() && q.back().second < x) q.pop_back();
q.push_back(make_pair(i + L - 1 , x));
}
if(!q.empty()) ans[i] = max(ans[i] , q.front().second);
}
}
inline void solve2(int L , int R){
while(!q.empty()) q.pop_back();
for(int i = n;i >= 1;i--){
while(!q.empty() && q.front().first > i) q.pop_front();
ll x = st_query(stmi , i - R + 1 , i - L + 1);
if(x != 1145141919810){
x += b[i];
while(!q.empty() && q.back().second < x) q.pop_back();
q.push_back(make_pair(i - L + 1 , x));
}
if(!q.empty()) ans[i] = max(ans[i] , q.front().second);
}
}
int main(void){
n = read();
for(int i = 1;i <= n;i++){
a[i] = read();
stmx[i][0] = b[i] = b[i - 1] + a[i];
stmi[i][0] = -b[i - 1];
}
for(int k = 1;k <= __lg(n);k++) for(int i = 1;i + (1 << k) - 1 <= n;i++)
stmx[i][k] = max(stmx[i][k - 1] , stmx[i + (1 << (k - 1))][k - 1]) ,
stmi[i][k] = max(stmi[i][k - 1] , stmi[i + (1 << (k - 1))][k - 1]);
int T = read();
while(T--){
int L , R; L = read(); R = read();
for(int i = 1;i <= n;i++) ans[i] = -1e16;
//[l + L - 1 , L + R - 1]
solve1(L , R);
solve2(L , R);
// for(int i = 1;i <= n;i++) printf("%lld " , ans[i]); putchar('\n');
unsigned long long output = 0;
for(int i = 1;i <= n;i++) output ^= (unsigned long long)i * ans[i];
printf("%llu\n" , output);
}
}

对于任意情况,我们可以把询问按照 [2i,2i+11][2^i,2^{i+1}-1] 拆成 logn\log n 段,是两散+连续整段,这样每段都满足 2LR2L\ge R,可以用上面的方法。

#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std;
typedef long long ll;
char buf[1 << 21] , *p1 , *p2;
#define GC (p1 == p2 && (p2 = (p1 = buf) + fread(buf , 1 , 1 << 21 , stdin) , p1 == p2)? EOF : *p1++)
inline int read(){
int ret = 0; char c = GC; bool f = 0;
while(c < '0' || c > '9'){ if(c == '-') f = 1; c = GC; }
while(c >= '0' && c <= '9'){
ret = (ret << 1) + (ret << 3) + (c ^ 48);
c = GC;
}
return f? -ret : ret;
}
int n , a[50005]; ll b[50005] , stmi[50005][20] , stmx[50005][20];
inline ll st_query(ll st[50005][20] , int l , int r){
l = max(l , 1); r = min(r , n);
if(l > r) return 1145141919810;
int le = __lg(r - l + 1); return max(st[l][le] , st[r - (1 << le) + 1][le]);
}
struct QWQ_Deque{
int st , ed;
pair <int , ll> _q[50005];
inline void clear(){ st = 1; ed = 0; }
inline void push_back(pair <int , ll> x){ _q[++ed] = x; }
inline bool empty(){ return st > ed; }
inline void pop_back(){ ed--; }
inline void pop_front(){ st++; }
inline pair <int , ll> back(){ return _q[ed]; }
inline pair <int , ll> front(){ return _q[st]; }
}q;
ll ans[50005];
inline void solve1(int L , int R){
q.clear();
for(int i = 1;i <= n;i++){
while(!q.empty() && q.front().first < i) q.pop_front();
ll x = st_query(stmx , i + L - 1 , i + R - 1);
if(x != 1145141919810){
x -= b[i - 1];
while(!q.empty() && q.back().second < x) q.pop_back();
q.push_back(make_pair(i + L - 1 , x));
}
if(!q.empty()) ans[i] = max(ans[i] , q.front().second);
}
}
inline void solve2(int L , int R){
q.clear();
for(int i = n;i >= 1;i--){
while(!q.empty() && q.front().first > i) q.pop_front();
ll x = st_query(stmi , i - R + 1 , i - L + 1);
if(x != 1145141919810){
x += b[i];
while(!q.empty() && q.back().second < x) q.pop_back();
q.push_back(make_pair(i - L + 1 , x));
}
if(!q.empty()) ans[i] = max(ans[i] , q.front().second);
}
}
int main(void){
n = read();
for(int i = 1;i <= n;i++){
a[i] = read();
stmx[i][0] = b[i] = b[i - 1] + a[i];
stmi[i][0] = -b[i - 1];
}
for(int k = 1 , _ = __lg(n);k <= _;k++) for(int i = 1;i + (1 << k) - 1 <= n;i++)
stmx[i][k] = max(stmx[i][k - 1] , stmx[i + (1 << (k - 1))][k - 1]) ,
stmi[i][k] = max(stmi[i][k - 1] , stmi[i + (1 << (k - 1))][k - 1]);
int T = read();
while(T--){
int L , R; L = read(); R = read();
for(int i = 1;i <= n;i++) ans[i] = -1e16;
//[l + L - 1 , L + R - 1]
for(int i = 0 , _ = __lg(R);i <= _;i++){
int l = (1 << i) , r = (1 << (i + 1)) - 1; r = min(r , n);
if(l > R || r < L) continue;
l = max(l , L); r = min(r , R);
solve1(l , r); solve2(l , r);
// if(L <= l && r <= R){
// }
// else{
// }
}
// for(int i = 1;i <= n;i++) printf("%lld " , ans[i]); putchar('\n');
unsigned long long output = 0;
for(int i = 1;i <= n;i++) output ^= (unsigned long long)i * ans[i];
printf("%llu\n" , output);
}
}

优化可以散段上面方法暴力做,整段预处理+st 表区间查询,可以做到 O(nq)O(nq)

#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std;
typedef long long ll;
char buf[1 << 23] , *p1 , *p2;
#define GC (p1 == p2 && (p2 = (p1 = buf) + fread(buf , 1 , 1 << 23 , stdin) , p1 == p2)? EOF : *p1++)
inline int read(){
int ret = 0; char c = GC; bool f = 0;
while(c < '0' || c > '9'){ if(c == '-') f = 1; c = GC; }
while(c >= '0' && c <= '9'){
ret = (ret << 1) + (ret << 3) + (c ^ 48);
c = GC;
}
return f? -ret : ret;
}
int n; ll b[50005] , chm[50005][16][5] , stmi[20][50005] , stmx[20][50005];
inline ll st_query(ll st[20][50005] , int l , int r){
int le = __lg(r - l + 1); return max(st[le][l] , st[le][r - (1 << le) + 1]);
};
int st , ed;
pair <int , ll> _q[50005];
ll ans[50005];
inline void solve(int L , int R){
st = 1; ed = 0;
for(int i = 1 , _ = n + 1 - L;i <= n;i++){
while(st <= ed && _q[st].first < i) st++;
if(i <= _){
ll x = st_query(stmx , i + L - 1 , min(i + R - 1 , n)) - b[i - 1];
while(st <= ed && _q[ed].second <= x) ed--;
_q[++ed] = make_pair(i + L - 1 , x);
}
else if(st > ed) break;
ans[i] = max(ans[i] , _q[st].second);
}
st = 1; ed = 0;
for(int i = n;i >= 1;i--){
while(st <= ed && _q[st].first > i) st++;
if(i >= L){
ll x = b[i] + st_query(stmi , max(i - R + 1 , 1) , i - L + 1);
while(st <= ed && _q[ed].second <= x) ed--;
_q[++ed] = make_pair(i - L + 1 , x);
}
else if(st > ed) break;
ans[i] = max(ans[i] , _q[st].second);
}
}
int main(void){
n = read();
for(int i = 1;i <= n;i++) {
stmx[0][i] = b[i] = b[i - 1] + read();
stmi[0][i] = -b[i - 1];
}
for(int k = 1 , _ = __lg(n);k <= _;k++) for(int i = 1;i + (1 << k) - 1 <= n;i++)
stmx[k][i] = max(stmx[k - 1][i] , stmx[k - 1][i + (1 << (k - 1))]) ,
stmi[k][i] = max(stmi[k - 1][i] , stmi[k - 1][i + (1 << (k - 1))]);
for(int i = 0 , _ = __lg(n);i <= _;i++){
int l = (1 << i) , r = (1 << (i + 1)) - 1; r = min(r , n);
for(int j = 1;j <= n;j++) ans[j] = -1e16;
solve(l , r);
for(int j = 1;j <= n;j++) chm[j][i][0] = ans[j];
}
for(int o = 1 , _ = __lg(n);o <= n;o++){
for(int i = 0;i <= _ - 1;i++)
chm[o][i][1] = max(chm[o][i][0] , chm[o][i + 1][0]);
for(int i = 0;i <= _ - 3;i++)
chm[o][i][2] = max(chm[o][i][1] , chm[o][i + 2][1]);
for(int i = 0;i <= _ - 7;i++)
chm[o][i][3] = max(chm[o][i][2] , chm[o][i + 4][2]);
for(int i = 0;i <= _ - 15;i++)
chm[o][i][4] = max(chm[o][i][3] , chm[o][i + 8][3]);
}
int T = read() , L , R;
while(T--){
L = read(); R = read();
for(int i = 1;i <= n;i++) ans[i] = -1e16;
int fszl = n , fszr = 0;
for(int i = 0 , _ = __lg(R);i <= _;i++){
int l = (1 << i) , r = (1 << (i + 1)) - 1; r = min(r , n);
if(l > R || r < L) continue;
if(L <= l && r <= R)
fszl = min(fszl , i) , fszr = max(fszr , i);
else{
l = max(l , L); r = min(r , R);
solve(l , r);
}
}
// printf("%d %d\n" , fszl , fszr);
if(fszl < n) for(int i = 1 , _ = __lg(fszr - fszl + 1) , __ = fszr - (1 << _) + 1;i <= n;i++)
ans[i] = max(ans[i] , max(chm[i][fszl][_] , chm[i][__][_]));
// for(int i = 1;i <= n;i++) printf("%lld " , ans[i]); putchar('\n');
unsigned long long output = 0;
for(int i = 1;i <= n;i++) output ^= (unsigned long long)i * ans[i];
printf("%llu\n" , output);
}
}

卡常:st 表要注意内存的连续访问。

分享

如果这篇文章对你有帮助,欢迎分享给更多人!

部分信息可能已经过时