首页 > 其他分享 >[考试记录] 2024.11.19 noip模拟赛17

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

时间:2024-11-19 19:40:09浏览次数:1  
标签:2024.11 17 noip int res st tl num id

T1 选取字符串

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

挺典的 kmp。考虑到题目中的串都是一个串的前缀,那么所选出来的串,他们的前缀一定是最短的那个串。不妨直接枚举每一个前缀,也就是枚举每一个串,看他们是否可以作为前缀出现,hash即可,复杂度 \(\mathcal{O}(N^2)\)。换个思路,考虑有多少个串包含某一个前缀,预处理 kmp 的 next 数组可以 \(\mathcal{O}(N)\) 求出每个串的最大公共前后缀,不过前缀的前缀也是自己的前缀,所以需要做一个前缀和来统计每个前缀被多少个串包含。然后枚举前缀,令 \(tot\) 表示当前前缀的前后缀数量(包含自己),\(num\) 表示包含此前缀的串的数量,贡献就是:\((2tot+1)\times \binom{num}{k}\)。至于为什么是 \(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. 如果 \(\sum_i a_i\) 是奇数,先手取 \(1\) 个必胜,因为每个人之后都只能取 \(1\) 个;
  2. 否则,先手最优一定取偶数个并且后面每个人能取偶数个都只会取偶数个(否则留给对手总和为奇

数的情况,自己必败),所以可以递归到 \(K\gets \lfloor \frac{K}{2} \rfloor , a_i\gets \lfloor\frac{a_i}{2} \rfloor\)。

解得先手必胜当且仅当对于某个 \(t\le \log_2 K\),\(\sum{i=1}^n \lfloor \frac{a_i}{2^t}\rfloor\)模 \(2\) 和

为 \(1\),也即 \(\mathop{\oplus}i a_i \not\equiv 0\pmod {2^{\lfloor \log_2 K\rfloor}}\)。

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

\((2k+1)\cdot \mathrm{lowbit}(\mathop{\oplus}i a_i)\)个。枚举取的堆,假设是第 \(i\) 堆,考虑枚举取

完之后另一个人面对的剩下的异或和的 \(\mathrm{lowbit}\),假设是\(2^t\),那么先手取的个数为 \(a_i - (\mathop{\oplus}{j\ne i} a_j)\oplus 2^t \pmod {2^{t+1}}\)。由于必胜,后手不能取到 \(2^t\),于是自己

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

的情况。

综上,答案至多 \(O(n\log a)\) 种。

时间复杂度:\(O(n\log a)\)

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

#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\) 为当前右端点,并把 \(a_r\) 加到单调栈中,那么该单调栈的图像大致为这样:

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

代码有点小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\),内层为集合 \(J\subseteq I\)。为了方便表述,设占位下标 $0\in

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

  1. \(I\) 中相邻两个数 \(i,i'\),\(a{i+1\sim i'-1}\) 中不存在 \(=a{i'}\) 的值。
  2. \(J\) 中相邻两个数 \(j,j'\),\(a{I\cap(j,j')}\) 中不存在 \(=a{j'}\) 的值。

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

么就要决定 \(a_{j+1\sim i-1}\) 这部分如何选外层,设选择了集合 \(K\),限制如下;

  1. \(K\) 中相邻两个数 \(k,k'\),\(a{k+1\sim k'-1}\) 中不存在 \(=a{k'}\) 的值。
  2. \(K\) 中最大值 \(k_r\),\(a_{k_r+1\sim i-1}\) 中不存在 \(=a_i\) 的值。
  3. \(K\) 中任意 \(k\),\(a_k\ne a_i\)。

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

个数 \(g{i,j}\),真正转移时 \(f_i\xleftarrow{+}(g{i,j}-g_{pre_i,j})\cdot f_j\) 即可。最后汇总答案可以弄一个

必选的占位下标 \(n+1\)。

\(g\) 是 2D/0D,\(f\) 是 1D/1D,时间复杂度 \(\mathrm{O}(n^2)\),期望得分 \(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;
}

标签:2024.11,17,noip,int,res,st,tl,num,id
From: https://www.cnblogs.com/xiaolemc/p/18555482

相关文章

  • 多校A层冲刺NOIP2024模拟赛24
    多校A层冲刺NOIP2024模拟赛24\(T1\)A.选取字符串\(100pts\)考虑建出失配树,然后等价于询问\(\sum\limits_{S\sube\{0,1,2,\dots,n\},|S|=k}dep_{\operatorname{LCA}\{S\}}^{2}\)。不妨从\(\operatorname{LCA}\)的角度考虑,统计\(x\)能作为多少个\(|S|\)......
  • noip模拟17
    A选取字符串会\(n^2\)。直接枚举全部的\(q,p\)然后开一个二维的bitset去存一个数是否是某个数的前后缀。选到两个\(p,q\)时把这两个数的bitset与起来,贡献是\(\binom{count}{k}\)。正解就是先用kmp去求出来全部的border,然后用border关系建一棵树,这棵树上满足......
  • P1014 [NOIP1999 普及组] Cantor 表
    这道题需要我们按照Z形,给出第N项的值。按照Z形对表进行观察,我们可以对表中的数据进行一个分组如图,发现第一层有一个数,第二层有两个数,第三层有三个数,第n层有n个数,且奇数层的分母是从层数p开始数到1,也就是p,p-1,p-2,p-3........,分子是1数到p,偶像层与奇数层相反。那么知道这个......
  • 洛谷:P1008 [NOIP1998 普及组] 三连击
    这道题需要我们找出所有符合要求的数对,由于数据量不大,这里我们可以使用枚举的方法进行枚举,那么我们从最小的三位数100到最大数999进行遍历寻找,再对这三个数进行判断,判断这三个数的每一位是否由1-9这9个数组成,且每个数只出现一次。在判断这个地方我们可以用一个数组来进行计数,将......
  • [71] (多校联训) A层冲刺NOIP2024模拟赛24
    bydT3放道这种题有什么深意吗flowchartTB A(选取字符串) styleAcolor:#ffffff,fill:#00c0c0,stroke:#ffffff确实是签,但是一直在想组合意义,最后因为没提前处理逆元遗憾离场了,赛后看题解发现的确是往树上转化更简单点赛时的组合意义代码没过#include<bits/stdc++.h>us......
  • 【NOIP提高组】 统计数字
    【NOIP提高组】统计数字C语言代码C++代码Java代码Python代码......
  • 【NOIP普及组】记数问题
    【NOIP普及组】记数问题C语言代码C++代码Java代码Python代码......
  • 【NOIP普及组】 排座椅
    【NOIP普及组】排座椅C语言版本C++版本Java版本Python版本......
  • Docker部署ELK7.17.10
    一.安装前准备    需要准备elasticsearch_7.17.10,kibana_7.17.10,logstash7.17.10三个镜像,这里我用的离线镜像包elasticsearch_7.17.10.tar,kibana_7.17.10.tar,logstash7.17.101.先执行命令包导入镜像dockerload-ielasticsearch_7.17.10.tardockerload-ikiban......
  • 洛谷题单指南-二叉堆与树状数组-P5677 [GZOI2017] 配对统计
    原题链接:https://www.luogu.com.cn/problem/P5677题意解读:所谓好的配对,通过分析公式∣ax−ay∣≤∣ax−ai∣(i≠x),可以得知就是一个ax与其差的绝对值最小的形成的配对,在数轴上就是距离ax最近的点ay,配对是下标(x,y),给定若干个区间[l,r],每个区间的配对数*区间编号的累加。解题思路:......