CSP-S模拟7
A. 序列问题
考场先写出一个 \(f_i\) 表示长度为 \(i\) 的子序列的最大贡献, 然后发现每个点的贡献在坐标系里是一个三角形,非常恶心,然后不知道怎么想到换定义了
定义 \(f_i\) 为以 \(i\) 为结尾的,并且 \(i\) 位置有贡献的最大贡献
那么能转移到 \(i\) 的 \(j\) 需要满足
\(j < i\)
\(a_j < a_i\)
\(j - a_j <= i - a_i\)
发现第一个条件可以由下面两个得到,那么只需要处理下面两个,那么这就是一个二维偏序 ,排序 + \(BIT\) 即可
code
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
inline int read(){
int x = 0; char c = getchar();
while(c < '0' || c > '9')c = getchar();
do{x = (x << 3) + (x << 1) + (c ^ 48); c = getchar();}while(c <= '9' && c >= '0');
return x;
}
const int maxn = 500005;
int f[maxn], n, a[maxn], cnt;
struct node{
int x, y;
friend bool operator < (const node &x, const node &y){
return x.y == y.y ? x.x < y.x : x.y < y.y;
}
}d[maxn];
struct BIT{
int t[maxn];
int lowbit(int x){return x & -x;}
void add(int x, int val){
while(x <= n){
t[x] = max(val, t[x]);
x += lowbit(x);
}
}
int query(int x){
int ans = 0;
while(x){
ans = max(ans, t[x]);
x -= lowbit(x);
}
return ans;
}
}t;
int main(){
freopen("sequence.in","r",stdin);
freopen("sequence.out","w",stdout);
n = read();
for(int i = 1; i <= n; ++i)a[i] = read();
for(int i = 1; i <= n; ++i)if(a[i] <= i){d[++cnt].x = a[i]; d[cnt].y = i - a[i];}
sort(d + 1, d + cnt + 1);
int p = 1, ans = 0;
while(p <= cnt){
int nans = t.query(d[p].x - 1) + 1;
ans = max(ans, nans);
t.add(d[p].x, nans);
++p;
}
printf("%d\n",ans);
return 0;
}
B. 钱仓
首先显然可以断环为链, 然后考虑一个起点需要满足该位置有数, 上一个位置最大为 \(1\)
进一步发现如果一串连续的 \(1\) 那么从哪个开始本质相同, 于是起点变成上一个位置为 \(0\) 该位置有数
考虑推的过程中,如果当前起点推到某个位置推不下去,那么从该位置往后到那个位置都不满足条件,可以直接跳过
到此为止是我赛时做法,不知道能不能通过构造卡掉
其实离正解差一个结论,就是从不同合法起点开始得到的答案相同,因为各个起点其实独立处理了他到下一个起点的区间
那么我们只要找到答案就可以退了
正解还不太一样,他直接找了前缀和最大的点,这样每个位置的前缀和减出来都是合法的
code
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
inline int read(){
int x = 0; char c = getchar();
while(c < '0' || c > '9')c = getchar();
do{x = (x << 3) + (x << 1) + (c ^ 48); c = getchar();}while(c <= '9' && c >= '0');
return x;
}
const int maxn = 100005;
const ll inf = 0x3f3f3f3f3f3f3f3f;
int n, c[maxn + maxn], tmp[maxn + maxn];
ll ans = inf;
int pos, flag;
ll solve(int l, int r){
flag = 0;
for(int i = l; i <= r; ++i)tmp[i] = c[i];
int pr = l + 1;
ll nans = 0;
for(int pl = l; pl < r; ++pl){
if(pl == pr) ++pr;
if(tmp[pl] < 1){
flag = 1;
pos = pl;
return inf;
}
while(tmp[pl] > 1){
nans += (pr - pl) * (pr - pl);
if(nans >= ans)return inf;
--tmp[pl]; ++tmp[pr]; ++pr;
}
}
return nans;
}
int main(){
freopen("barn.in","r",stdin);
freopen("barn.out","w",stdout);
n = read();
for(int i = 1; i <= n; ++i)c[i] = read();
for(int i = 1; i <= n; ++i)c[i + n] = c[i];
ans = solve(1, n);
for(int i = 2; i <= n; ++i)
if(c[i] && c[i + n - 1] < 1){
ans = min(ans, solve(i, i + n - 1));
if(flag)i = pos;
if(ans != inf)break;
}
printf("%lld\n",ans);
return 0;
}
C. 自然数
线段树套路,但是考场想的是部分分给的启发,然后假了
考虑每次删去一个点的贡献
初始令 \(l = 1\) 线段树第 \(x\) 个叶子维护 \([l , x]\) 的 \(mex\)
考虑每次移动 \(l - > l + 1\) 的影响, 他只会影响到下一个与 \(l\) 数字相同的位置之前
在该区间内,所有 \(mex\) 都变成原来的 \(mex\) 和删去的 \(l\) 位置的数的最小值
吉司机?
发现 \(mex\) 有单调性, 那么在线段树对应区间二分,然后区间覆盖即可
需要维护区间最小值,区间总和, 实现区间覆盖,区间查询
code
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
inline int read(){
int x = 0; char c = getchar();
while(c < '0' || c > '9')c = getchar();
do{x = (x << 3) + (x << 1) + (c ^ 48); c = getchar();}while(c <= '9' && c >= '0');
return x;
}
const int maxn = 200005;
int n, a[maxn], me[maxn];
bool vis[maxn];
struct tree{
struct node{
int tag, mi;
ll sum;
}t[maxn << 2 | 1];
void push_up(int x){
t[x].sum = t[x << 1].sum + t[x << 1 | 1].sum;
t[x].mi = min(t[x << 1].mi, t[x << 1 | 1].mi);
}
void built(int x, int l, int r){
t[x].tag = -1;
if(l == r){
t[x].mi = t[x].sum = me[l];
return;
}
int mid = (l + r) >> 1;
built(x << 1, l, mid);
built(x << 1 | 1, mid + 1, r);
push_up(x);
}
void upd(int x, int l, int r, int val){
t[x].mi = val;
t[x].sum = 1ll * (r - l + 1) * val;
t[x].tag = val;
}
void push_down(int x, int l, int r){
int mid = (l + r) >> 1;
upd(x << 1, l, mid, t[x].tag);
upd(x << 1 | 1, mid + 1, r, t[x].tag);
t[x].tag = -1;
return;
}
void modify(int x, int l, int r, int L, int R, int val){
int mid = (l + r) >> 1;
if(L <= l && r <= R){
if(t[x].mi > val){
upd(x, l, r, val);
return;
}
if(l == r)return;
if(t[x].tag != -1)push_down(x, l, r);
if(t[x << 1 | 1].mi > val){
upd(x << 1 | 1, mid + 1, r, val);
modify(x << 1, l, mid, L, R, val);
}else{
modify(x << 1 | 1, mid + 1, r, L, R, val);
}
push_up(x);
return;
}
if(t[x].tag != -1)push_down(x, l, r);
if(L <= mid)modify(x << 1, l, mid, L, R, val);
if(R > mid)modify(x << 1 | 1, mid + 1, r, L, R, val);
push_up(x);
}
ll query(int x, int l, int r, int L, int R){
if(L <= l && r <= R)return t[x].sum;
if(t[x].tag != -1)push_down(x, l, r);
int mid = (l + r) >> 1; ll ans = 0;
if(L <= mid)ans += query(x << 1, l, mid, L, R);
if(R > mid)ans += query(x << 1 | 1, mid + 1, r, L, R);
return ans;
}
}t;
int nxt[maxn], pos[maxn];
int main(){
freopen("mex.in","r",stdin);
freopen("mex.out","w",stdout);
n = read();
for(int i = 1; i <= n; ++i)a[i] = read();
int mex = 0;
for(int i = 1; i <= n; ++i){
if(a[i] < n){
vis[a[i]] = 1;
while(vis[mex])++mex;
}
me[i] = mex;
}
t.built(1, 1, n);
for(int i = n; i > 0; --i)if(a[i] < n){
nxt[i] = pos[a[i]];
pos[a[i]] = i;
}
ll ans = 0;
for(int l = 1; l <= n; ++l){
ans += t.query(1, 1, n, l, n);
t.modify(1, 1, n, l, nxt[l] ? nxt[l] - 1 : n, a[l]);
}
printf("%lld\n",ans);
return 0;
}
D. 环路
分治
发现其实把读入转成 \(0/1\) 矩阵 \(A\)
答案就是对 \(A, A^2, A^3 .... A^{k - 1}\) 的对角线求和
然后这个东西是可以分治优化的,简单来说就是类似快速幂的东西
\(\sum_{i = 1}^{k} A^i = \sum_{i = 1}^{k / 2}A^i + \sum_{i = 1}^{k / 2} A^{i}\times A^{k / 2} + [k \& 1] A^k\)
然后分治一下即可
code
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
inline int read(){
int x = 0; char c = getchar();
while(c < '0' || c > '9'){
if(c == 'Y')return 1;
if(c == 'N')return 0;
c = getchar();
}
do{x = (x << 3) + (x << 1) + (c ^ 48); c = getchar();}while(c <= '9' && c >= '0');
return x;
}
const int maxn = 105;
int n, k, m;
struct matrix{
int a[maxn][maxn];
void clear(){for(int i = 0; i < n; ++i) for(int j = 0; j < n; ++j)a[i][j] = 0;}
matrix(){memset(a, 0, sizeof(a));}
friend matrix operator * (const matrix &x, const matrix &y){
matrix c;
for(int i = 0; i < n; ++i)
for(int k = 0; k < n; ++k)
for(int j = 0; j < n; ++j)
c.a[i][j] = (c.a[i][j] + 1ll * x.a[i][k] * y.a[k][j]) % m;
return c;
}
friend matrix operator + (const matrix &x, const matrix &y){
matrix c;
for(int i = 0; i < n; ++i)
for(int j = 0; j < n; ++j)
c.a[i][j] = (x.a[i][j] + y.a[i][j]) % m;
return c;
}
}mp;
int ans;
typedef pair<matrix, matrix> pmm;
pmm solve(int k){
if(k == 1)return pmm(mp, mp);
pmm now = solve(k / 2);
matrix x = now.first, y = now.second;
matrix ans = x * now.second;
now.second = now.second * now.second;
if(k & 1){
now.second = now.second * mp;
ans = ans + now.second;
}
return pmm(ans + x, now.second);
}
int main(){
n = read();
for(int i = 0; i < n; ++i)
for(int j = 0; j < n; ++j)
mp.a[i][j] = read();
k = read(), m = read();
matrix ma = solve(k - 1).first;
for(int i = 0; i < n; ++i)ans = (ans + ma.a[i][i]) % m;
printf("%d\n",ans);
return 0;
}