686 字
2 分钟
NOIP2025(noi plus 2025) 简略题解
我会永远记住这一天。
我们不难注意到:noip2025=noi plus 2025
P14636 NOIP2025 T2
第一步就没想到。
按照性价比大到小排序后,发现取不到最大值的情况是:
- 选的最后一个 (否则选取的一定是前缀,是最大的)。
- 此时选的情况应该是:
...i222..(已选前缀)j222..(不选)k(选)...(钱花完了),其中 。 - 解释一下就是,先一直选,选到只剩 块钱了,跳过中间为 的连续段,选后面的第一个 。
- 那么有可能不是最大的情况当且仅当:,即可以通过把两个 性价比最低的糖果换成一个性价比高的 的糖果。
然后排序 ,枚举 ,让 ,因此要满足 (满足 性价比大于 )。
- 原价大于 的,一定会选( 是必选前缀的后面第一个),这样的数有 个。
- 原价大于 小于 的,只有 时会被选。
- 原价大于 小于 的,如果 ,那么 之间就不全是 ,所以,。都不会被选。
那么 如何求?我们找到满足 的最大的 ,下面证明满足条件。
所以 ,即 的性价比比 低。
那么,在 及以前的 都可能成为 ,这时,我们在前 个中任意定价 都满足要求,所以方案数是 。
在 到 之间都是确定的,方案数为 。到此时剩余价钱为 ,此时问题变成:
有 个 zbj1 为 ,还有 个 zbj2 为 。满足所有 zbj 的总和为 ,求方案数。
不难想到,把 zbj2 的先扣去 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); }}可以发现,如果把区间放到矩形上,查询相当于一个梯形。

对于 ,实际上是绿色线的移动,移动是 级别的。而红线是不变的。
如果 ,可以拆成平行于 和平行于 的两个平行四边形,直接用两个单调队列算,两个分别按 ,和 ,另一维可以用 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); }}对于任意情况,我们可以把询问按照 拆成 段,是两散+连续整段,这样每段都满足 ,可以用上面的方法。
#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 表区间查询,可以做到 。
#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 表要注意内存的连续访问。
部分信息可能已经过时