This is XiaoLe_MCs blog|

XiaoLe_MC

园龄:1年2个月粉丝:3关注:9

[考试记录] 2024.11.19 noip模拟赛17

T1 选取字符串

warning❗:本题解 前缀 含量过高。

挺典的 kmp。考虑到题目中的串都是一个串的前缀,那么所选出来的串,他们的前缀一定是最短的那个串。不妨直接枚举每一个前缀,也就是枚举每一个串,看他们是否可以作为前缀出现,hash即可,复杂度 O(N2)。换个思路,考虑有多少个串包含某一个前缀,预处理 kmp 的 next 数组可以 O(N) 求出每个串的最大公共前后缀,不过前缀的前缀也是自己的前缀,所以需要做一个前缀和来统计每个前缀被多少个串包含。然后枚举前缀,令 tot 表示当前前缀的前后缀数量(包含自己),num 表示包含此前缀的串的数量,贡献就是:(2tot+1)×(numk)。至于为什么是 2tot+1,因为当前枚举的这个前缀可以和其它的前缀匹配,因为 p,q 分别为两种方案,所以乘2,加一是因为 p,q 可相同。

#include<bits/stdc++.h>
using namespace std;
constexpr int N = 1e6 + 5, M = 998244353;
int nxt[N], fac[N], inv[N], tot[N], num[N];
struct node{ int bg, nxt; }nd[N];
inline int qpow(int a, int k){
int res = 1; while(k){
if(k & 1) res = (long long)res * a % M;
a = (long long)a * a % M; k >>= 1;
} return res;
}
inline int add(initializer_list<int> Add){
int res = 0;
for(int v : Add) res = res + v >= M ? res + v - M : res + v;
return res;
}
inline int mul(initializer_list<int> Mul){
int res = 1;
for(int v : Mul) res = (long long)res * v % M;
return res;
}
inline int C(int a, int b){ return b > a ? 0 : mul({fac[a], inv[b], inv[a-b]}); }
int main(){
freopen("string.in", "r", stdin); freopen("string.out", "w", stdout);
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
int k; string str; cin>>k>>str; int n = str.size();
fac[0] = inv[0] = 1;
for(int i=1; i<=n+1; ++i) fac[i] = mul({fac[i-1], i});
inv[n+1] = qpow(fac[n+1], M-2); for(int i=n; i>=1; --i) inv[i] = mul({inv[i+1], i+1});
for(int i=1, j=0; i<n; ++i, j=nxt[i]){
tot[i] = tot[nxt[i]] + 1;
while(j && str[i] != str[j]) j = nxt[j];
nxt[i+1] = j + (str[i] == str[j]);
} tot[n] = tot[nxt[n]] + 1;
for(int i=1; i<=n; ++i) num[i] = 1;
for(int i=n; i>=1; --i) num[nxt[i]] += num[i];
int ans = C(n+1, k);
for(int i=n; i>=1; --i) ans = add({ans, mul({tot[i]*2+1, C(num[i], k)})});
return cout<<ans, 0;
}

T2 取石子

问题模型:取石子(NIM)游戏,要求每个人每次取的石子数不能超过上一个人刚刚取的,第一个人最

开始可以取不超过 K 个。

考虑策略:

  1. 如果 iai 是奇数,先手取 1 个必胜,因为每个人之后都只能取 1 个;
  2. 否则,先手最优一定取偶数个并且后面每个人能取偶数个都只会取偶数个(否则留给对手总和为奇

数的情况,自己必败),所以可以递归到 KK2,aiai2

解得先手必胜当且仅当对于某个 tlog2Ki=1nai2t2

1,也即 iai0(mod2log2K)

定义 lowbit(x) 表示整除 x 的最大的 2 的幂。先手第一步能必胜的策略必定是取

(2k+1)lowbit(iai)个。枚举取的堆,假设是第 i 堆,考虑枚举取

完之后另一个人面对的剩下的异或和的 lowbit,假设是2t,那么先手取的个数为 ai(jiaj)2t(mod2t+1)。由于必胜,后手不能取到 2t,于是自己

这次取的个数也必须小于 2t。枚举 t 依次判断即可。注意处理 t=(取完之后异或和归零)

的情况。

综上,答案至多 O(nloga) 种。

时间复杂度:O(nloga)

说点儿人话:考场上手膜样例快睡着了,于是果断放弃。不过想来这个结论还是挺妙的。对于构造,假如除了当前 ai 的异或和为 sum,那么我要构造一种取法使得 sum(aix) 的前 k 位一定没有 1。不妨直接枚举,如果这一位上 sum0ai1,那么肯定要把它减去(加到答案里),如果是 10,就要考虑借位,不过鉴于从小到大枚举,直接减掉即可,后面一定会考虑得到。

#include<bits/stdc++.h>
using namespace std;
constexpr int N = 1e6 + 5;
#define lb(x) ((x) & (-x))
int a[N];
int main(){
freopen("nim.in", "r", stdin); freopen("nim.out", "w", stdout);
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
int n, k; cin>>n>>k; int sum = 0;
for(int i=1; i<=n; ++i) cin>>a[i], sum ^= a[i];
if(lb(sum) > k) return cout<<"0", 0;
cout<<"1\n";
for(int i=1; i<=n; ++i){
int nw = a[i], tmp = sum ^ a[i];
for(int j=0; j<=30; ++j) if((1 & (tmp >> j)) ^ (1 & (nw >> j))){
nw -= 1 << j;
if(nw < 0 || a[i] - nw > k) break;
cout<<i<<' '<<(a[i] - nw)<<'\n';
}
} return 0;
}

T3 均衡区间

考虑到并不是所有的数都是有贡献的,那么考虑维护所有有贡献的数。拿 min 来说,令 r 为当前右端点,并把 ar 加到单调栈中,那么该单调栈的图像大致为这样:

那么单调栈中的每一个数都对应着一段贡献区间,即在这段区间里所有的点作为左端点到 r 的区间 min都由那个数贡献。最小值不能由左右端点贡献,可以发现,只有红色的部分是合法的。可以以同样的方式处理出维护 max 的单调栈,求这两个合法区间交的长度即可。使用线段树复杂度 O(NlogN)

代码有点小bug,应该是从严格大于、小于的点开始维护区间。

#include<bits/stdc++.h>
using namespace std;
constexpr int B = 1 << 13;
char buf[B], *p1 = buf, *p2 = buf, obuf[B], *O = obuf;
#define gt() (p1==p2 && (p2=(p1=buf)+fread(buf, 1, B, stdin), p1==p2) ? EOF : *p1++)
template <typename T> inline void rd(T &x){
x = 0; int f = 0; char ch = gt();
for(; !isdigit(ch); ch = gt()) f ^= x == '-';
for(; isdigit(ch); ch = gt()) x = (x<<1) + (x<<3) + (ch^48);
x = f ? -x : x;
}
template <typename T, typename ...TT> inline void rd(T &x, TT &...y){ rd(x), rd(y...); }
#define pt(ch) (O-obuf==B && (fwrite(obuf, 1, B, stdout), O=obuf), *O++ = (ch))
template <typename T> inline void wt(T x){
if(x < 0) pt('-'), x = -x;
if(x > 9) wt(x / 10); pt(x % 10 ^ 48);
}
#define fw fwrite(obuf, 1, O - obuf, stdout)
constexpr int N = 1e6 + 5;
int n, id, a[N];
namespace ST{
#define ls (id << 1)
#define rs (id << 1 | 1)
struct node{ int l, r, num[3], tag; }t[N<<2];
inline void pushup(int id){
t[id].num[0] = t[ls].num[0] + t[rs].num[0];
t[id].num[1] = t[ls].num[1] + t[rs].num[1];
t[id].num[2] = t[ls].num[2] + t[rs].num[2];
}
inline void build(int id, int l, int r){
t[id].l = l, t[id].r = r; t[id].tag = 0;
t[id].num[0] = t[id].num[1] = t[id].num[2] = 0;
if(l == r) return t[id].num[0] = 1, void();
int mid = (l + r) >> 1;
build(ls, l, mid), build(rs, mid+1, r);
pushup(id);
}
inline void addtag(int id, int val){
if(val == 1){
t[id].num[2] += t[id].num[1]; t[id].num[1] = 0;
t[id].num[1] += t[id].num[0]; t[id].num[0] = 0;
} else if(val == -1){
t[id].num[0] += t[id].num[1]; t[id].num[1] = 0;
t[id].num[1] += t[id].num[2]; t[id].num[2] = 0;
} else if(val == 2){
t[id].num[2] += t[id].num[0]; t[id].num[0] = 0;
} else {
t[id].num[0] += t[id].num[2]; t[id].num[2] = 0;
}
t[id].tag += val;
}
inline void pushdown(int id){
if(t[id].tag != 0){
addtag(ls, t[id].tag);
addtag(rs, t[id].tag);
t[id].tag = 0;
}
}
inline void modify(int id, int l, int r, int val){
if(l > r) return;
if(l <= t[id].l && t[id].r <= r) return addtag(id, val);
pushdown(id); int mid = (t[id].l + t[id].r) >> 1;
if(l <= mid) modify(ls, l, r, val);
if(r > mid) modify(rs, l, r, val);
pushup(id);
}
}
namespace Sub2{
int flag[N], Lans[N];
struct XiaoLe{
int st[N], tl; bitset<N> ok;
inline void insert_mn(int x){
while(tl && a[st[tl]] > a[x]){
if(ok[st[tl]]){
ST::modify(1, st[tl-1]+1, st[tl]-1, -1);
ok[st[tl]] = 0;
}
--tl;
}
st[++tl] = x;
if(tl > 1 && !ok[st[tl-1]]){
ok[st[tl-1]] = 1;
ST::modify(1, st[tl-2]+1, st[tl-1]-1, 1);
}
}
inline void insert_mx(int x){
while(tl && a[st[tl]] < a[x]){
if(ok[st[tl]]){
ST::modify(1, st[tl-1]+1, st[tl]-1, -1);
ok[st[tl]] = 0;
}
--tl;
}
st[++tl] = x;
if(tl > 1 && !ok[st[tl-1]]){
ok[st[tl-1]] = 1;
ST::modify(1, st[tl-2]+1, st[tl-1]-1, 1);
}
}
}rmn, rmx;
struct XiaoLe2{
int st[N], tl; bitset<N> ok;
inline void insert_mn(int x){
while(tl && a[st[tl]] > a[x]){
if(ok[st[tl]]){
ST::modify(1, st[tl]+1, st[tl-1]-1, -1);
ok[st[tl]] = 0;
}
--tl;
}
st[++tl] = x;
if(tl > 1 && !ok[st[tl-1]]){
ok[st[tl-1]] = 1;
ST::modify(1, st[tl-1]+1, st[tl-2]-1, 1);
}
}
inline void insert_mx(int x){
while(tl && a[st[tl]] < a[x]){
if(ok[st[tl]]){
ST::modify(1, st[tl]+1, st[tl-1]-1, -1);
ok[st[tl]] = 0;
}
--tl;
}
st[++tl] = x;
if(tl > 1 && !ok[st[tl-1]]){
ok[st[tl-1]] = 1;
ST::modify(1, st[tl-1]+1, st[tl-2]-1, 1);
}
}
}lmn, lmx;
inline void solve(){
ST::build(1, 1, n);
lmn.st[0] = n+1, lmx.st[0] = n+1;
for(int i=n; i>=1; --i){
lmn.insert_mn(i);
lmx.insert_mx(i);
Lans[i] = ST::t[1].num[2];
}
for(int i=1; i<=n; ++i) wt(Lans[i]), pt(' ');
pt('\n'); ST::build(1, 1, n);
for(int i=1; i<=n; ++i){
rmn.insert_mn(i);
rmx.insert_mx(i);
wt(ST::t[1].num[2]), pt(' ');
} fw, exit(0);
}
}
int main(){
freopen("interval.in", "r", stdin); freopen("interval.out", "w", stdout);
rd(n, id); for(int i=1; i<=n; ++i) rd(a[i]);
if(id == 2){
for(int i=1; i<=n; ++i) pt('0'), pt(' ');
pt('\n');
for(int i=1; i<=n; ++i) pt('0'), pt(' ');
fw, exit(0);
}
Sub2::solve();
}

T4 禁止套娃

设选择的外层子序列下标为集合 I,内层为集合 JI。为了方便表述,设占位下标 $0\in

I,J$。同样只计贪心匹配的情况,限制如下:

  1. I 中相邻两个数 i,iai+1i1 中不存在 =ai 的值。
  2. J 中相邻两个数 j,jaI(j,j) 中不存在 =aj 的值。

考虑对 J dp。fi 表示目前考虑到 i 且内外层末尾均选 i 的答案。如果要从 fj 转移过来,那

么就要决定 aj+1i1 这部分如何选外层,设选择了集合 K,限制如下;

  1. K 中相邻两个数 k,kak+1k1 中不存在 =ak 的值。
  2. K 中最大值 krakr+1i1 中不存在 =ai 的值。
  3. K 中任意 kakai

一个简洁的处理方法是,对于每一个 i,dp 出 > 每个 j 的只需满足 1、3 条件的本质不同子序列

个数 gi,j,真正转移时 fi+(gi,jgprei,j)fj 即可。最后汇总答案可以弄一个

必选的占位下标 n+1

g 是 2D/0D,f 是 1D/1D,时间复杂度 O(n2),期望得分 100

#include<bits/stdc++.h>
using namespace std;
constexpr int N = 5005, M = 1e9 + 7;
int n, a[N], f[N], g[N][N], pre[N], lst[N], suf[N], h[N];
inline int add(initializer_list<int> Add){
int res = 0;
for(int v : Add) res = res + v >= M ? res + v - M : res + v;
return res;
}
inline int mul(initializer_list<int> Mul){
int res = 1;
for(int v : Mul) res = (long long)res * v % M;
return res;
}
int main(){
freopen("nest.in", "r", stdin); freopen("nest.out", "w", stdout);
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
cin>>n; for(int i=1; i<=n; ++i) cin>>a[i], suf[i] = n + 1;
for(int i=1; i<=n; ++i) suf[pre[i] = lst[a[i]]] = i, lst[a[i]] = i;
for(int i=1, sum=1; i<=n+1; ++i, sum=1) for(int j=i-1; j>=0; --j){
g[i][j] = sum; h[j] = a[i] == a[j] ? 0 : sum;
sum = add({sum, M-h[suf[j]], h[j]});
} f[0] = 1;
for(int i=1; i<=n+1; ++i) for(int j=0; j<i; ++j)
f[i] = add({f[i], mul({add({g[i][j], M-g[pre[i]][j]}), f[j]})});
return cout<<f[n+1], 0;
}

本文作者:XiaoLe_MC

本文链接:https://www.cnblogs.com/xiaolemc/p/18555482

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   XiaoLe_MC  阅读(28)  评论(1编辑  收藏  举报
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
收起