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\) 的方案数
考虑转移
- 新放入的数与原来某个数相同
\(f_{i, j, k} \times j - > f_{i + 1, j, k}\)
- 新放入的数与原来不同并且不是 \(mex\)
\(f_{i, j, k} - > f_{i + 1,j + 1, k}\)
- 新放入的数为 \(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;
}