CSP-S模拟7
学校体检:
内科:问,你是神经病吗;答,不是。下一个。
外科:问,你做过手术吗;答,没有。下一个。
基础检查:问,你身高体重是多少;答,……。下一个。
此处应有乌鸦飞过***
A. 序列问题
我连那个有假三维条件的dp式子都没想到,直接整了一个二维的玩意?!f[i][j]表示在B里填到了第i个数,这个数是A里的第j个,于是f[i][j] = max f[i-1][k]+[a[k]==i].可以加个滚动数组,不过还是50pts.
code
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 1002;
const int mod = 1e9 + 7;
int n, a[maxn], f[maxn][maxn], ans;
inline int read()
{
int x = 0, f = 1;
char ch = getchar();
while(ch < '0' || ch > '9')
{
if(ch == '-')
{
f = -1;
}
ch = getchar();
}
while(ch >= '0' && ch <= '9')
{
x = (x << 1) + (x << 3) + (ch ^ 48);
ch = getchar();
}
return x * f;
}
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++)
{
for(int j=i; j<=n; j++)
{
int t = a[j]==i ? 1 : 0;
for(int k=i-1; k<j; k++)
{
f[i][j] = max(f[i][j], f[i-1][k]+t);
ans = max(ans, f[i][j]);
}
}
}
printf("%d\n", ans);
return 0;
}
我本来也以为题解假了,不过题解它是对的,跳出来为题解伸个冤:
状态f[i]表示处理A中1~i位置的数,并且处理后的序列以ai结尾的最优答案,因为它是最优答案,所以ai一定对应了B里的某个位置而不是无效的值,无效的值填空的意义只是让后面的值有效。那么只有ai<=i的时候f[i]是合法的。
考虑一个j能转移到i必须满足这些条件:j < i aj < ai ai - aj <= i - j 就是j原来也在B中某个可以对应的位置上,B要往后加,a一定会更大,中间可以用无效的值来填空,但中间空不能太大,把i~j之间的数都用来填空是差值的上限。
这才是真的暴力转移50分,我那个连暴力dp都算不上555 f[i] = max f[j] + 1,j满足条件。
于是你把a[]sort了一下,求了个不下降子序列,WA 0.然后开始抱怨题解假了?
可是题解说了a是要求严格递增的呀!!!sort又没有去重!!
于是你想到了把重复的a设为最大值,让它不可能和上一个同等的值,但是这样显然不对,因为不一定最终会用到这一堆ai里的哪一个。
正确的做法应该是把所有的相等的ai看成一个组,分组转移。
code
//这种求最长不下降子序列的方法似乎比老师课件上的原版更好背
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 5e5 + 7;
const int mod = 1e9 + 7;
int n, a[maxn], tot, f[maxn], low[maxn], ans;
struct node
{
int a, b;
bool operator < (const node &T) const
{
return a < T.a;
}
}e[maxn];
inline int read()
{
int x = 0, f = 1;
char ch = getchar();
while(ch < '0' || ch > '9')
{
if(ch == '-')
{
f = -1;
}
ch = getchar();
}
while(ch >= '0' && ch <= '9')
{
x = (x << 1) + (x << 3) + (ch ^ 48);
ch = getchar();
}
return x * f;
}
int main()
{
freopen("sequence.in", "r", stdin);
freopen("sequence.out", "w", stdout);
n = read();
for(int i=1; i<=n; i++)
{
a[i] = read();
if(a[i] <= i) {e[++tot].a = a[i], e[tot].b = i-a[i];}
}
for(int i=1; i<=tot; i++) low[i] = mod;
sort(e+1, e+1+tot);
//f[j]表示以j为结尾的最长不下降子序列的长度
//low[i]表示最长不下降子序列的长度是i要求的关键字最小值
//长度增加,对关键字的要求单调不下降
for(int i=1; i<=tot; i++)//右端点也被统计了,i++不要扔
{
int l = i;
while(i<tot && e[i].a==e[i+1].a) i++;
int r = i;
//因为要求a严格递增
for(int j=l; j<=r; j++)
{
f[j] = upper_bound(low+1, low+1+tot, e[j].b)-1-low+1;
//由小于等于当前关键字的最大长度转移
}
for(int j=l; j<=r; j++)
{
//转移完了把答案接在后面
low[f[j]] = min(e[j].b, low[f[j]]);
}
}
for(int i=1; i<=tot; i++)
{
ans = max(ans, f[i]);
}
printf("%d\n", ans);
return 0;
}
然后还有一种树状数组的做法,也需要看出来“假三维偏序”:
为了让ai和i-ai都是有序的,可以先把ai排序,再按照i-ai的顺序来查找,使ai按顺序有效,也就是循环枚举的顺序变了!
code
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 5e5 + 2;
const int mod = 1e9 + 7;
int n, a[maxn], cnt;
inline int read()
{
int x = 0, f = 1;
char ch = getchar();
while(ch < '0' || ch > '9')
{
if(ch == '-')
{
f = -1;
}
ch = getchar();
}
while(ch >= '0' && ch <= '9')
{
x = (x << 1) + (x << 3) + (ch ^ 48);
ch = getchar();
}
return x * f;
}
struct node
{
int x, y;
bool operator < (const node &T) const
{
return y == T.y ? x < T.x : y < T.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];}
//提前把a[i]>i的不合法情况去掉
//它不能当f[i]的结尾,所以不能参与转移
}
sort(d+1, d+cnt+1);
int p = 1, ans = 0;
for(int i=0; i<=n; i++)//枚举i-a[i]所有可能的结果,每一次增长都有上界
//就使得更新顺序按i-a[i]有序
{
while(p <= cnt && d[p].y <= i)//处理后以a[p]结尾
{
int nans = t.query(d[p].x-1)+1;//a严格递增
ans = max(ans, nans);
t.add(d[p].x, nans);
p++;
}
}
printf("%d\n", ans);
return 0;
}
B. 钱仓
Step1: 暴力枚举
code
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 2e5 + 7;
const int mod = 1e9 + 7;
int n, c[maxn], p[maxn];
ll ans = 1e17;;
inline int read()
{
int x = 0, f = 1;
char ch = getchar();
while(ch < '0' || ch > '9')
{
if(ch == '-')
{
f = -1;
}
ch = getchar();
}
while(ch >= '0' && ch <= '9')
{
x = (x << 1) + (x << 3) + (ch ^ 48);
ch = getchar();
}
return x * f;
}
inline bool check(int l, int r)
{
for(int i=l; i<=r; i++)
{
if(p[i] != 1) return 0;
}
return 1;
}
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];
for(int i=1; i<=n; i++)
{
if(!c[i]) continue;
int j = i+n-1;
ll res = 0;
if(c[j] > 1) continue;
for(int k=i; k<=j; k++)
{
p[k] = c[k];
}
for(int k=j; k>=i; k--)
{
if(!p[k])
{
for(int s=k-1; s>=i; s--)
{
if(!p[s]) continue;
p[s]--; p[k]++;
res += 1ll*(k-s)*(k-s);
break;
}
}
}
if(!check(i, j)) continue;
ans = min(ans, res);
}
printf("%lld\n", ans);
return 0;
}
Chen_jr一语点醒梦中Cat——从不同的节点开始得到的答案相同,因为各个起点其实独立处理了它到下一个起点的区间,所以找到一组解就结束了。
但只知道这个并不能拯救Cat原本的TTTTLE解法,她把每种情况都算了出来,最后看看是不是每个值都变成了1,以此来检验起点的合法性。
351238指出了不T的好方法,那就是后缀和不能大于后缀的大小,否则它一定需要往后运输而后面又盛不下这么多,于是Cat加了一堆特判,其中只有上面的那条后缀和是有用的。懒得改了。
加特判判完之后不要忘了break!否则和不判断没有毛线区别&**
code
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 2e5 + 7;
const int mod = 1e9 + 7;
int n, c[maxn], p[maxn];
ll ans = 1e17;;
inline int read()
{
int x = 0, f = 1;
char ch = getchar();
while(ch < '0' || ch > '9')
{
if(ch == '-')
{
f = -1;
}
ch = getchar();
}
while(ch >= '0' && ch <= '9')
{
x = (x << 1) + (x << 3) + (ch ^ 48);
ch = getchar();
}
return x * f;
}
inline bool check(int l, int r)
{
for(int i=l; i<=r; i++)
{
if(p[i] != 1) return 0;
}
return 1;
}
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];
for(int i=1; i<=n; i++)
{
bool f = 0;
if(!c[i]) continue;
int j = i+n-1;
ll res = 0;
if(c[j] > 1) continue;
ll cnt = 0, sum = 0;
for(int k=j; k>=i; k--)
{
cnt++;
sum += c[k];
if(sum > cnt)
{
f = 1; break;
}
}
if(f) continue;
for(int k=i; k<=j; k++)
{
p[k] = c[k];
}
for(int k=j; k>=i; k--)
{
if(!p[k])
{
for(int s=k-1; s>=i; s--)
{
if(!p[s]) continue;
p[s]--; p[k]++;
res += 1ll*(k-s)*(k-s);
break;
}
}
if(p[k] > 1)
{
f = 1; break;
}
if(f) break;
}
if(f) continue;
if(!check(i, j)) continue;
ans = min(ans, res);
break;
}
printf("%lld\n", ans);
return 0;
}
C. 自然数
第一印象:题目这个定义,为什么我联想到了SG函数!?难道是数论题!?
第二印象:所有区间问题?我想到了线段树和CDQ分治两种套路形式,but不会实现……
我的暴力枚举好像和别人的都不太一样?不去记录取过哪些数,用一个set上二分找到哪些数还没取,比n^2还慢……
code
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 3002;
const int mod = 1e9 + 7;
int n, a[maxn];
ll ans;
set<int> s;
set<int>::iterator it;
inline int read()
{
int x = 0, f = 1;
char ch = getchar();
while(ch < '0' || ch > '9')
{
if(ch == '-')
{
f = -1;
}
ch = getchar();
}
while(ch >= '0' && ch <= '9')
{
x = (x << 1) + (x << 3) + (ch ^ 48);
ch = getchar();
}
return x * f;
}
int main()
{
freopen("mex.in", "r", stdin);
freopen("mex.out", "w", stdout);
n = read();
for(int i=1; i<=n; i++) a[i] = read();
for(int i=0; i<=n; i++)
{
s.insert(i);
}
for(int i=1; i<=n; i++)
{
for(int j=i; j<=n; j++)
{
it = s.find(a[j]);
if(it != s.end()) s.erase(it);
int y = *s.lower_bound(0);
//printf("mex(%d, %d) = %d\n", i, j, y);
ans += y;
}
for(int j=i; j<=n; j++)
{
if(a[j] <= n) s.insert(a[j]);
}
}
printf("%lld\n", ans);
return 0;
}
鹤题解,尽管我自己抄了一遍,但它还是被查重了***谁让你不改变量名!我偏不改!!
考虑每次删去一个点的贡献,初始令l=1线段树第x个叶子维护[l,x]的mex,考虑每次移动l到l+1的影响,它只会影响下一个与l数字相同的位置之前,在该区间内,所有mex都变成原来的mex和删去的l位置的数的最小值。发现mex有单调性,那么在线段树对应区间二分,然后区间覆盖即可。
需要维护区间最小值,区间总和,实现区间覆盖,区间查询。
——直接引用自Chen_jr
code
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 2e5 + 5;
const int mod = 1e9 + 7;
int nxt[maxn], pos[maxn], n, a[maxn], me[maxn];
bool vis[maxn];
inline int read()
{
int x = 0, f = 1;
char ch = getchar();
while(ch < '0' || ch > '9')
{
if(ch == '-')
{
f = -1;
}
ch = getchar();
}
while(ch >= '0' && ch <= '9')
{
x = (x << 1) + (x << 3) + (ch ^ 48);
ch = getchar();
}
return x * f;
}
struct tree
{
struct node
{
int tag, mi;
ll sum;
}t[maxn<<2];
void pushup(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 build(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;
build(x<<1, l, mid);
build(x<<1|1, mid+1, r);
pushup(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 pushdown(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;
}
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) pushdown(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);
}
pushup(x);
return;
}
if(t[x].tag != -1) pushdown(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);
pushup(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) pushdown(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 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.build(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.环路
∑i=1~k Ai = ∑i=1~k/2 Ai + Ai=1~k/2 Ai * A^k/2 + [k&1] A^k
不得不说我的数学公式写的实在是太丑啦!亲爱的读者还是去mod Chen_jr吧!!
code
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 105;
const int mod = 1e9 + 7;
int n, k, m;
/*
inline int read()
{
int x = 0, f = 1;
char ch = getchar();
while(ch < '0' || ch > '9')
{
if(ch == '-')
{
f = -1;
}
ch = getchar();
}
while(ch >= '0' && ch <= '9')
{
x = (x << 1) + (x << 3) + (ch ^ 48);
ch = getchar();
}
return x * f;
}*/
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;
}
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()
{
freopen("tour.in", "r", stdin);
freopen("tour.out", "w", stdout);
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;
}