2023年石门中学NOIP模拟测试(2023.10.17)
原题大战,还是 \(4\) 道计数...
放个头图:
一蓝一紫两黑,简单且原题 0.o?
出模拟赛搬原题演都不演了,他真的我哭死。那这总结不写也罢
T1
\(n\leq 10^3\)。
简单来说,要选出子序列满足相同颜色连续的方案数。
签到题,但写了 \(\text{1h}\) 的我是 sb。
直接大力状压,设 \(dp_{i,s,c}\) 表示考虑了前 \(i\) 个,选了的颜色集合是 \(s\),且上一个结尾颜色段为 \(c\) 的方案数,转移随便搞就好了。但是这唐题空间只给 \(\text{16MiB}\),还得把 \(i\) 那维给滚了。
Code
#include <bits/stdc++.h>
#define il inline
#define rint register int
#define ll long long
using namespace std;
const int N = 1e3 + 10, INF = 2147483647, mod = 998244353;
char *p1, *p2, buf[N];
#define nc() (p1==p2 && (p2=(p1=buf)+fread(buf,1,100000,stdin),p1==p2)?EOF:*p1++)
#define gc() getchar()
il int rd() {
int x = 0, f = 1;
char ch = gc();
while (!isdigit(ch)) {
if (ch == '-')f = -1;
ch = gc();
}
while (isdigit(ch))x = (x << 3) + (x << 1) + (ch ^ 48), ch = gc();
return x * f;
}
int n;
char s[N];
ll f[1 << 10], dp[1 << 10][11];
void Main () {
ll ans = 0;
int all = 1 << 10;
n = rd();
scanf("%s", s + 1);
memset(dp, 0, sizeof(dp));
for (int i = 1; i <= n; ++i) {
int k = s[i] - 'A';
memset(f, 0, sizeof(f));
f[1 << k] = 1;
for (int j = all - 1; j >= 0; --j) {
if (!((j >> k) & 1)) {
for (int t = 0; t < 10; ++t)
if ((j >> t) & 1) (f[j] += dp[j][t]) %= mod;
(dp[j | (1 << k)][k] += f[j]) %= mod;
} else {
(f[j] += dp[j][k]) %= mod;
(dp[j][k] += f[j]) %= mod;
}
(ans += f[j]) %= mod;
}
}
printf("%lld\n", ans);
}
signed main() {
// freopen("counta.in", "r", stdin);
// freopen("counta.out", "w", stdout);
int T = rd();
while (T --) Main();
return 0;
}
T2
我们定义一个数组 \(b\) 是好的当且仅当所有的 \(b_i \ge i\)。
现在给你 \(q\) 次操作,每次操作有两个数 \(p\) 和 \(x\),问把 \(a_p\) 赋值成 \(x\) 后 \(a\) 数组好的子串(连续的子序列)的个数。
数据范围:
- \(1 \le n \le 2 \times 10^5\),\(1 \le q \le 2 \times 10^5\)。
- \(1 \le a_i \le n\),\(1 \le p_j,x_j \le n\)。
典题,考虑对每个右端点记录最左端点 \(l_i\),则 \(l_i=\max(l_{i-1},i-a_i+1)\),那么显然这玩意具有单调性。然后给原系列 \(a\) 拍到线段树上,那么 \(l\) 就是个前缀取 \(\max\) 的形式,考虑每次修改会带来的影响,维护个区间最大值,线段树二分即可。
Code
#include <bits/stdc++.h>
#define lc k << 1
#define rc k << 1 | 1
#define il inline
#define rint register int
#define ll long long
using namespace std;
const int N = 2e5 + 10, INF = 2147483647;
char *p1, *p2, buf[N];
#define nc() (p1==p2 && (p2=(p1=buf)+fread(buf,1,100000,stdin),p1==p2)?EOF:*p1++)
#define gc() getchar()
il int rd() {
int x = 0, f = 1;
char ch = gc();
while (!isdigit(ch)) {
if (ch == '-')f = -1;
ch = gc();
}
while (isdigit(ch))x = (x << 3) + (x << 1) + (ch ^ 48), ch = gc();
return x * f;
}
int n, m, a[N];
ll ans[N << 2], mx[N << 2];
ll calc (int k, int l, int r, int v) {
if (mx[k] <= v) return 1ll * (r - l + 1) * v;
if (l == r) return mx[k];
int mid = (l + r) >> 1;
if (mx[lc] >= v) return ans[k] - ans[lc] + calc(lc, l, mid, v);
return calc(rc, mid + 1, r, v) + 1ll * v * (mid - l + 1);
}
void pushup (int k, int l, int mid, int r) {
mx[k] = max(mx[lc], mx[rc]);
ans[k] = ans[lc] + calc(rc, mid + 1, r, mx[lc]);
}
void build (int k, int l, int r) {
if (l == r) return ans[k] = mx[k] = max(1, l - a[l] + 1), void();
int mid = (l + r) >> 1;
build(lc, l, mid), build(rc, mid + 1, r);
pushup(k, l, mid, r);
}
void update (int k, int l, int r, int v, int x) {
if (l == r) return ans[k] = mx[k] = max(1, l - x + 1), void();
int mid = (l + r) >> 1;
v <= mid ? update(lc, l, mid, v, x) : update(rc, mid + 1, r, v, x);
pushup(k, l, mid, r);
}
void Main() {
n = rd();
for (int i = 1; i <= n; ++i) a[i] = rd();
build(1, 1, n);
m = rd();
for (int i = 1; i <= m; ++i) {
int x, k;
ll ret = 0;
x = rd(), k = rd();
update(1, 1, n, x, k);
ret = 1ll * n * (n + 1) / 2 + n - ans[1];
printf("%lld\n", ret);
update(1, 1, n, x, a[x]);
}
}
signed main () {
// freopen("countb.in", "r", stdin);
// freopen("countb.out", "w", stdout);
int T = 1;
while (T--)Main();
return 0;
}
T3
题目懒得翻了。
比较好的一道题。
首先要找性质:对于相同颜色的奶牛在合法序列中一定不出现超过两次,且一定是一左一右。这个性质我们思考奶牛睡觉以及吃的草不会长回来很容易理解。
预处理每头奶牛在没有其他奶牛时从左边,右边走分别可以到达哪里,记作 \(l_i,r_i\)。
接下来考虑枚举第一只从左边走的奶牛 \(p\),那么他就会将序列分成 \([1,l_p)\) 与 \((p_l,n]\) 两部分,之后与 \(p\) 同色的奶牛只能走右边且往左不超过 \(l_p\),不同色两边走同理。
然后题目求方案数,与每种方案中奶牛的排列顺序无关,而且我们还发现每头奶牛只会在自己的颜色上睡觉。换言之,颜色之间互不影响,因为一定可以构造出合法的序列。
然后我们将不同颜色分开考虑,对于一种颜色 \(c\neq c_q\),我们考虑其方案数:
不妨在这些同色奶牛之间分个类,枚举 \(x\),统计出:
- \(S\) 表示 \(l_x<l_p,r_x>l_p\),这样的牛可以两边走
- \(Sl\) 表示 \(l_x<l_p,r_x\leq l_p\),这样的牛左边走
- \(Sr\) 表示 \(l_x\ge l_p,r_x>l_p\),这样的牛右边走
然后这三个集合不交,那么有:
- 当 \(S\ge 2\) 或 \(S=1\) 且 \(Sl+Sr>0\) 时,我们可以走 \(2\) 头牛,方案数就是 \(S\times (S-1)+S\times(Sl+Sr)\) 。
- 否则,当\(S,Sl,Sr\) 非空,只能满足一只,方案数 \(2S+Sl+Sr\)
对于 \(c=c_p\) 也差不多。
把贡献合在一起,取最大值以及对应方案数即可。
时间复杂度 \(O(n^2)\)。
但是还可以通过一些预处理以及二分算 \(S,Sl,Sr\) 做到 \(O(n\log n)\)。
Code($n^2$)
#include<bits/stdc++.h>
#define il inline
#define int long long
using namespace std;
il int rd(){
int x=0,f=1;
char ch=getchar();
while(!isdigit(ch)) {if(ch=='-')f=-1;ch=getchar();}
while(isdigit(ch)){x=(x<<3)+(x<<1)+(ch^48);ch=getchar();}
return x*f;
}
const int N=5e3+10,mod=1e9+7;
int n,m;
int a[N],c[N],h[N],l[N],r[N];
vector<int>p[N],g[N];
int tot[N];
int calc(int x,int p){
int mul=1,sum=(x>0);
for(int i=1; i<=n; ++i){
int s=0,sl=0,sr=0;
for(int y:g[i]){
if(y==x)continue;
if(l[y]<p&&r[y]>p)++s;
else if(l[y]<p)++sl;
else if(r[y]>p)++sr;
}
if(i==c[x]){
if(s+sr>0)++sum,mul=mul*(s+sr)%mod;
}else{
if(s>=2||(s==1&&(sl+sr>0)))sum+=2,mul=mul*s*(s-1+sl+sr)%mod;
else if(sl+sr+s>0)sum+=1,mul=mul*(sl+sr+s*2)%mod;
}
}
(tot[sum]+=mul)%=mod;
return sum;
}
signed main(){
n=rd(),m=rd();
for(int i=1; i<=n; ++i)a[i]=rd(),p[a[i]].push_back(i);
for(int i=1; i<=m; ++i){
c[i]=rd(),h[i]=rd();
if(h[i]>p[c[i]].size())continue;
l[i]=p[c[i]][h[i]-1];
r[i]=p[c[i]][p[c[i]].size()-h[i]];
g[c[i]].push_back(i);
}
int ans=calc(0,0);
for(int i=1; i<=n; ++i){
for(auto y:g[i])ans=max(ans,calc(y,l[y]));
}
cout<<ans<<' '<<tot[ans];
return 0;
}
T4
[ZJOI2019] 语言
有 \(n\) 个城市节点组成树。
在上古时代,这 \(n\) 个城市之间处于战争状态。在高度闭塞的环境中,每个城市都发展出了自己的语言。而在王国统一之后,语言不通给王国的发展带来了极大的阻碍。为了改善这种情况,国王下令设计了 \(m\) 种通用语,并进行了 \(m\) 次语言统一工作。在第 \(i\) 次统一工作中,一名大臣从城市 \(s_i\) 出发,沿着最短的路径走到了 \(t_i\),教会了沿途所有城市(包括 \(s_i, t_i\))使用第 \(i\) 个通用语。
一旦有了共通的语言,那么城市之间就可以开展贸易活动了。两个城市 \(u_i, v_i\) 之间可以开展贸易活动当且仅当存在一种通用语 \(L\) 满足 \(u_i\) 到 \(v_i\) 最短路上的所有城市(包括 \(u_i, v_i\)),都会使用 \(L\)。
为了衡量语言统一工作的效果,国王想让你计算有多少对城市 \((u, v)\)(\(u < v\)),他们之间可以开展贸易活动。
对于 \(100\%\) 的数据,有 \(1 \le x_i, y_i, s_i, t_i \le n\leq 10 ^ 5\),\(1\leq m\leq 10 ^ 5\),\(x_i\neq y_i\)。
啥都不会。