NOIP模拟2

A. 将军棋

先看数据骗分

A直接问相邻两个即可

D扫两便问前后缀

一般情况记录每个颜色最后出现的位置进行二分

然后你发现每个位置可能多问一个

我的做法是当 \(l == r\) 并且没有合法的,所有颜色都出现过,那么直接确定该颜色

其实复杂了,只要出现过所有颜色后不考虑最远的那个即可

code
#include "generals.h"
#include<bits/stdc++.h>

using namespace std;

const int maxn = 1005;
vector<int>ans;
int col[maxn];
bool vis[maxn];
vector<int>solA(int n){
	col[n] = 1;
	for(int i = n - 1; i >= 1; --i)col[i] = col[i + 1] + query(i, i + 1) - 1;
	for(int i = 1; i <= n; ++i)ans.push_back(col[i]);
	return ans;
}
vector<int>solD(int n){
	if(query(1, n) == n){
		for(int i = 1; i <= n; ++i)ans.push_back(i);
		return ans;
	}
	int pos = n;
	for(int i = 2; i < n; ++i)if(query(1, i) != i){pos = i; break;}
	int pl = 1;
	for(int l = pos - 1; l >= 1; --l)if(query(l, pos) != pos - l + 1){pl = l;break;}
	int cnt = 0; for(int i = 1; i <= n; ++i)if(i != pos)col[i] = ++cnt; else col[i] = col[pl];
	for(int i = 1; i <= n; ++i)ans.push_back(col[i]);
	return ans;
}
int rpos[maxn], lpos[maxn]; set<int>s;
vector<int> getColor(int T, int n, int Q){
	ans.clear();
	if(T <= 15)return solA(n);
	if(T >= 49 && T <= 60)return solD(n);
	int mx = query(1, n);
    int cnt = 0; s.clear();
	for(int i = n; i >= 1; --i){
		int l = 1, r = cnt, acol = -1, p = 0;
		for(int x : s)rpos[++p] = x;
		while(l <= r){
			int mid = (l + r) >> 1;
			if(l == r && p == mx && acol == -1){acol = col[rpos[l]]; break;}
			if(query(i, rpos[mid]) == mid + 1)l = mid + 1;
			else r = mid - 1, acol = col[rpos[mid]];
		}
		if(acol == -1)col[i] = ++cnt, lpos[cnt] = i, s.insert(i);
		else col[i] = acol, s.erase(lpos[acol]), lpos[acol] = i, s.insert(i);
	}
	for(int i = 1; i <= n; ++i)ans.push_back(col[i]);
    return ans;
}


B. 小明的变换

水题,赛时 \(CE\) 方式非常无语

下次粘代码一定先 \(ctrl + A\)

能匹配就匹配,不能匹配就放到下一个与当前数相同的位置,开桶记录个数即可

code
#include<bits/stdc++.h>

using namespace std;

typedef long long ll;
typedef unsigned long long ull;

int read(){
	int x = 0; char c = getchar();
	while(!isdigit(c))c = getchar();
	do{x = x * 10 + (c ^ 48); c = getchar();}while(isdigit(c));
	return x;
}

const int maxn = 1000005;

int nxt[maxn], pos[maxn], a[maxn], b[maxn], n, cnt[maxn];

bool sol(){
	n = read();
	for(int i = 1; i <= n; ++i)a[i] = read();
	for(int i = 1; i <= n; ++i)b[i] = read();
	for(int i = 1; i <= n; ++i)pos[a[i]] = -1;
	for(int i = n; i >= 1; --i){ nxt[i] = pos[a[i]]; pos[a[i]] = i;}
	for(int i = 1; i <= n; ++i)cnt[i] = 1;
	int pl = 1;
	for(int i = 1; i <= n; ++i){
		while(a[pl] != b[i]){
			if(nxt[pl] == -1)return false;
			cnt[nxt[pl]] += cnt[pl];
			++pl;
		}
		if(a[pl] == b[i]){
			--cnt[pl];
			if(cnt[pl] == 0)++pl;
		}else return false;
	}
	return true;
}

int main(){
	freopen("trans.in","r",stdin);
	freopen("trans.out","w",stdout);
	int t = read();
	for(int i = 1; i <= t; ++i)if(sol())printf("YES\n");else printf("NO\n");
	return 0;
}

C. 小明过生日

转化为图论题,要造一条链,可以构造所有点都加一条边

注意到偶回文每个点度为 \(2\), 奇回文中间点度为 \(1\), 那么奇回文最多两个,否则一定无解

构造将奇回文放在两边,然后\(--a[1], ++a[m]\)即可

注意\(1\)减成 \(0\) 的情况

以及 \(m = 1\)需要简单特判,由于赛时脑抽,所以方案奇特且复杂,请忽略他

code
#include<bits/stdc++.h>

using namespace std;

typedef long long ll;
typedef unsigned long long ull;

int read(){
	int x = 0; char c = getchar();
	while(!isdigit(c))c = getchar();
	do{x = x * 10 + (c ^ 48); c = getchar();}while(isdigit(c));
	return x;
}

const int maxn = 105;
int n, m, a[maxn];
vector<int> cut1(int x){
	vector<int>ans;
	if(x & 1){
		while(x > 1){ans.push_back(2); x -= 2;} ans.push_back(1);
	}else{
		int h = x >> 1;
		if(h & 1){
			while(h > 1){ans.push_back(2); h -= 2;} ans.push_back(1);
			h = x >> 1;
			while(h > 1){ans.push_back(2); h -= 2;} ans.push_back(1);
		}else{
			while(h){ans.push_back(2); h -= 2;}
			h = x >> 1; h -= 2;
			ans.push_back(1);
			while(h){ans.push_back(2); h -= 2;}
			ans.push_back(1);
		}
	}
	return ans;
}
vector<int>p[maxn];
int main(){
	n = read(), m = read();
	for(int i = 1; i <= m; ++i)a[i] = read();
	if(m == 1){
		vector<int>ans = cut1(a[1]);
		printf("%d\n%d\n",a[1],(int)ans.size());
		for(int x : ans)printf("%d ",x);
		return 0;
	}
	int cnt1 = 0;
	for(int i = 1; i <= m; ++i)cnt1 += (a[i] & 1);
	if(cnt1 > 2){printf("-1\n"); return 0;}
	for(int i = 1; i <= m; ++i)if(a[i] & 1){swap(a[i], a[1]);break;}
	for(int i = m; i >= 1; --i)if(a[i] & 1){swap(a[i], a[m]);break;}
	for(int i = 1; i <= m; ++i)printf("%d ",a[i]); printf("\n");
	--a[1]; ++a[m]; int i = 1;
	if(a[1] == 0)i = 2;
	printf("%d\n",m - i + 1);
	for(; i <= m; ++i)printf("%d ",a[i]);
	return 0;
}


D. 小明爱数数

\(f_{i, j, k}\) 表示考虑前 \(i\) 个数, 有 \(j\) 种不同的数,当前 \(mex\)\(k\) 的方案数

考虑转移

  1. 新放入的数与原来某个数相同

\(f_{i, j, k} \times j - > f_{i + 1, j, k}\)

  1. 新放入的数与原来不同并且不是 \(mex\)

\(f_{i, j, k} - > f_{i + 1,j + 1, k}\)

  1. 新放入的数为 \(mex\)

此时我们需要枚举新的 \(mex\) 并钦定还没有确定是什么数的那些数取值

设新的\(mex\)\(t\)

\(f_{i, j, k} \times (j - k)! / (j + 1 - t)!- > f_{i + 1, j + 1, t}\)

注意到 \((j - k)!\) 只与 \(j, k\) 有关

\((j + 1 - t)!\)只与 \(j, t\) 有关

于是可以进行前缀和优化,在前缀和数组累加时乘上 \((j - k)!\),转移到新状态乘上 \(inv_{j + 1 - t}\)

然后代码看的题解,写的填表法

自己尝试打刷表法,打自闭了,如果有人刷表法过了请务必告诉我。(有兴趣帮我调一下就更好了

code

#include<bits/stdc++.h>

using namespace std;

typedef long long ll;
typedef unsigned long long ull;

int read(){
	int x = 0;bool f = false; char c = getchar();
	while(!isdigit(c)){f = c == '-'; c = getchar();}
	do{x = x * 10 + (c ^ 48); c = getchar();}while(isdigit(c));
	return f ? -x : x;
}

const int maxn = 2010;
const int mod = 998244353;
int qpow(int x, int y){
	int ans = 1;
	for(; y; y >>= 1, x = 1ll * x * x % mod)if(y & 1)ans = 1ll * ans * x % mod;
	return ans;
}
int fac[maxn], inv[maxn];
void add(int &x, int y){x += y; x = x >= mod ? x - mod : x;}
int f[2][maxn][maxn], sf[2][maxn][maxn];
int n, K, a[maxn], l[maxn], r[maxn];
int main(){
	// freopen("numb.in","r",stdin);
	// freopen("numb.out","w",stdout);
	n = read(), K = read();
	for(int i = 1; i <= n; ++i)a[i] = read(), l[i] = max(0, a[i] - K), r[i] = min(i, a[i] + K);
	fac[0] = inv[0] = 1; for(int i = 1; i <= n; ++i)fac[i] = 1ll * fac[i - 1] * i  % mod;
	inv[n] = qpow(fac[n], mod - 2); for(int i = n - 1; i >= 1; --i)inv[i] = 1ll * inv[i + 1] * (i + 1) % mod;
	f[0][0][0] = sf[0][0][0] = 1;
	for(int i = 1, now = 1; i <= n; ++i){
		int las = now ^ 1;
		for(int j = 0; j <= i; ++j)
			for(int k = l[i]; k <= r[i] && k <= j; ++k){
				f[now][j][k] = (f[now][j][k] + 1ll * f[las][j][k] * j % mod) % mod;
				if(j)f[now][j][k] = (f[now][j][k] + f[las][j - 1][k]) % mod;
				if(j && k)f[now][j][k] = (f[now][j][k] + 1ll * sf[las][j - 1][min(k - 1, r[i - 1])] * inv[j - k]) % mod;
				sf[now][j][k] = 1ll * f[now][j][k] * fac[j - k] % mod;
				if(k)sf[now][j][k] = (sf[now][j][k] + sf[now][j][k - 1]) % mod;
			}
		now ^= 1;
		for(int j = 0; j < i; ++j)
			for(int k = l[i - 1]; k <= r[i - 1] && k <= j; ++k)
				f[now][j][k] = sf[now][j][k] = 0;
	}
	int now = n & 1;
	int ans = 0;
	for(int i = 0; i <= n; ++i)
		for(int j = l[n]; j <= r[n] && j <= i; ++j)
			ans = (ans + 1ll * f[now][i][j] * fac[n - j] % mod * inv[n - i] % mod) % mod;
	printf("%d\n",ans);
	return 0;
}

posted @ 2022-11-10 19:22  Chen_jr  阅读(5)  评论(0编辑  收藏  举报