恩偶挨批模拟试题-14
水博客太快乐了
考场
先看 \(T1\)
题中给出的式子怎么看都像某种逆序对,考虑各种分治,感觉肯定能 \(A\) 。
想了一个小时没想出来,一看时间发现不太对,用半个小时把 \(T2\) \(T3\) 的暴力打了,由于一直还想着写 \(T1\) 而且暴力分给的真的多,所以没去想后两题的正解,实际上 \(T3\) 是个 \(O(n^{2})\) 的水 \(dp\) ,血亏。
之后的时间一直挂在 \(T1\) 上,一直在想用各种奇奇怪怪的方法优化分治,从单调栈想到主席树,发现都是假的。。。。。
直到最后才想到 \(dp\) ,但是感觉时间不太对,也没往下想。。。
最后交了一个玄学,两个暴力上去。。。
分数
预估 : \(t1\) \(60pts\) \(+\) \(t2\) \(40pts\) \(+\) \(t3\) \(50pts\) \(=\) \(150pts\)
实际 : \(t1\) \(10pts\) \(+\) \(t2\) \(50pts\) \(+\) \(t3\) \(50pts\) \(=\) \(110pts\)
发现 \(t1\) 挂成 \(10pts\) 了,连暴力也没过。
\(t2\) 本来觉得是能过 \(n,m \le 1000\) 和随机数据的点,结果随机数据的点挂了,莫名过了 \(n,m \le 20000\) 的点???数据水。。。
题解
A. 队长快跑
这题给的式子乍一看就很可以分治。
所以请务必不要往分治上想
这种求子序列的题显然是 \(dp\) 啊。
先离散化。。。
设 \(f_{i,j}\) 表示选到第 \(i\) 个数,之前选择的最大值是 \(j\) 是最长的序列。。。
写出状态转移方程就很好想了,但是我懒得写了。
列出转移方程发现这是个 \(O(n^{3})\) \(dp\) 但是显然可以用线段树优化。。。
最终时间复杂度 \(O(n\) \(logn)\)
code
#include<bits/stdc++.h>
using namespace std;
const int N=2e5+10;
inline int read(){
int f=1, x=0; char ch=getchar();
while(!isdigit(ch)) { if(ch=='-') f=-1; ch=getchar(); }
while(isdigit(ch)) { x=x*10+ch-48; ch=getchar(); }
return f*x;
}
int n, a[N], b[N], c[N];
struct TRE{
int l, r, num, lz;
}t[N<<2];
void built(int l, int r, int p){
t[p].l=l, t[p].r=r;
if(l==r) return;
int mid=(l+r)>>1;
built(l, mid, p<<1);
built(mid+1, r, p<<1|1);
}
void pushdown(int p){
if(!t[p].lz) return;
t[p<<1].lz+=t[p].lz, t[p<<1|1].lz+=t[p].lz;
t[p<<1].num+=t[p].lz, t[p<<1|1].num+=t[p].lz;
t[p].lz=0;
}
int maxn(int l, int r, int p){
if(l<=t[p].l&&r>=t[p].r) return t[p].num;
pushdown(p); int mid=(t[p].l+t[p].r)>>1;
if(l<=mid&&r>mid) return max(maxn(l, r, p<<1), maxn(l, r, p<<1|1));
if(l<=mid) return maxn(l, r, p<<1);
return maxn(l, r, p<<1|1);
}
void add(int l, int r, int p, int x){
if(l<=t[p].l&&r>=t[p].r) { t[p].lz+=x, t[p].num+=x; return; }
pushdown(p); int mid=(t[p].l+t[p].r)>>1;
if(l<=mid) add(l, r, p<<1, x);
if(r>mid) add(l, r, p<<1|1, x);
t[p].num=max(t[p<<1].num, t[p<<1|1].num);
}
void add1(int x, int y, int p){
t[p].num=max(t[p].num, y);
if(t[p].l==x&&t[p].r==x) return;
pushdown(p); int mid=(t[p].l+t[p].r)>>1;
if(x<=mid) add1(x, y, p<<1);
else add1(x, y, p<<1|1);
}
int main(void){
n=read();
for(int i=1; i<=n; ++i) c[i]=a[i]=read(), c[i+n]=b[i]=read();
sort(c+1, c+1+(n<<1)); int len=unique(c+1, c+1+(n<<1))-c-1;
for(int i=1; i<=n; ++i){
a[i]=lower_bound(c+1, c+1+len, a[i])-c;
b[i]=lower_bound(c+1, c+1+len, b[i])-c;
}
built(1, ++len, 1);
for(int i=1; i<=n; ++i){
if(a[i]<=b[i]){
int ul=maxn(b[i]+1, len, 1);
add1(a[i], ul+1, 1);
}else{
int ul=maxn(a[i]+1, len, 1);
add(b[i]+1, a[i], 1, 1);
add1(a[i], ul+1, 1);
}
}
printf("%d\n", maxn(1, len, 1));
return 0;
}
B. 影魔
本场比赛最难的题。
既然没有强制在线,那么这一定是离线题
先将所有询问离线下来,存到每一个节点上。
看到题中要求距离小于 \(d\) 的子树,想到以深度为下标建立树状数组, \(dfs\) 时更新树状数组,每个询问直接从树状数组中查询。
但是同一种颜色可能在树状数组中多次出现,怎么办?
显然对于一种颜色,只要在它最浅的位置存下来就可以了,所以在回溯的时候用线段树合并(以颜色为下标,每个节点存这个颜色出现的最浅位置),将同一种颜色较深的点删掉。
还有一个问题,每 \(dfs\) 进入一个节点时,树状数组中都已经包含了不属于当前节点子树的信息,如果这样直接处理,显然答案会偏大,所以 \(dfs\) 一进入该节点,就立即让每个询问的答案减去当前树状数组中对应深度的值即可。。。
。。。就这样吧。。。
。。直接看代码吧。。
。。。异常好写。。。
code
#include<bits/stdc++.h>
using namespace std;
#define d first
#define id second
const int N=2e5+10;
inline int read(){
int f=1, x=0; char ch=getchar();
while(!isdigit(ch)) { if(ch=='-') f=-1; ch=getchar(); }
while(isdigit(ch)) { x=x*10+ch-48; ch=getchar(); }
return f*x;
}
int n, m, a[N], dep[N], ans[N], rt[N], tot;
#define lb(x) x&-x
class T{
private :
int a[N], n;
inline int sum(int p) { int ans=0; for(; p; p-=lb(p)) ans+=a[p]; return ans; }
public :
inline void init(int x) { n=x; }
inline void add(int p, int x) { for(; p<=n; p+=lb(p)) a[p]+=x; }
inline int query(int l, int r) { return sum(r)-sum(l-1); }
}t1;
#undef lb
struct TRE{
int l, r, dep, ls, rs;
}t[N<<4];
vector<int> l[N];
vector<pair<int, int> > q[N];
void add(int l, int r, int x, int &p){
if(!p) p=++tot; t[p]=(TRE){ l, r, dep[x] };
if(l==r) return; int mid=(l+r)>>1;
if(a[x]<=mid) add(l, mid, x, t[p].ls);
else add(mid+1, r, x, t[p].rs);
}
void merge(int &l, int r){
if(!l) { l=r; return; }
if(!r) return;
if(t[l].l==t[l].r){
if(t[l].dep>=t[r].dep) t1.add(t[l].dep, -1);
else t1.add(t[r].dep, -1);
t[l].dep=min(t[l].dep, t[r].dep);
return;
}
merge(t[l].ls, t[r].ls);
merge(t[l].rs, t[r].rs);
}
void dfs(int u){
for(pair<int, int > Q : q[u]) ans[Q.id]-=t1.query(dep[u], min(dep[u]+Q.d, n));;
add(1, n, u, rt[u]); t1.add(dep[u], 1);
for(int v : l[u]){
dep[v]=dep[u]+1; dfs(v);
merge(rt[u], rt[v]);
}
for(pair<int, int > Q : q[u]) ans[Q.id]+=t1.query(dep[u], min(dep[u]+Q.d, n));
}
int main(void){
n=read(); m=read(); t1.init(n);
int x, y;
for(int i=1; i<=n; ++i) a[i]=read();
for(int i=2; i<=n; ++i) l[read()].push_back(i);
for(int i=1; i<=m; ++i){
x=read(), y=read();
q[x].push_back(make_pair(y, i));
}
dep[1]=1; dfs(1);
for(int i=1; i<=m; ++i) printf("%d\n", ans[i]);
return 0;
}
C. 抛硬币
本场比赛最水的题。。。
本来是送温暖的,然而我拿了暴力就跑了。。。
一看数据范围就是 \(O(n^{2})\) \(dp\) 。
设 \(f_{i,j}\) 表示前 \(i\) 个字符,选了 \(j\) 个的总方案数。
设计出状态转移就很好想了。。。。
主要是我懒得写了。。。
因为是本质不同,所以要去重。。。。
就这样。。
code
#include<bits/stdc++.h>
using namespace std;
const int N=3010, mod=998244353;
char a[N];
int l, vis[N];
long long f[N][N];
int main(void){
scanf("%s", a+1);
scanf("%d", &l);
int len=strlen(a+1);
for(int i=0; i<=len; ++i) f[i][0]=1;
for(int i=1; i<=len; ++i){
int ul=a[i];
for(int j=1; j<=l; ++j){
f[i][j]=(f[i-1][j]+f[i-1][j-1])%mod;
if(vis[ul]) f[i][j]-=f[vis[ul]-1][j-1];
f[i][j]=(f[i][j]%mod+mod)%mod;
}
vis[ul]=i;
}
printf("%lld\n", f[len][l]);
return 0;
}
反思
由于这场比赛实在考的太差了(rk19),所以有必要写下反思。。
看到一个题像是某个算法,想不出来怎么写不行就换个思路(虽然第一印象往往是对的),或先看看其他题,不要一直干一个题(老毛病了,该改改了)。。。
\(dp\) 学的太烂了(那么明显两个 \(dp\) 都看不出来),滚去重学 \(dp\) 吧。
一直不会写就去上个厕所。。。