20200713晚 noip14
考场
很紧张,上午考太烂了
开场看到“影魔”,想起以前看过(但没做),心态爆炸,咆哮时被 hkh diss 了
T1 一开始想建边跑最长路,每个点在记录一下 \(\min\{a\}\),发现有问题。改成 DP,\(f[i,j]\) 表示前 \(i\) 个水晶只选选 \(a\ge j\) 的最长路,每次从 \(f[i-1][b[i]+1..\max\{a,b\}]\) 转移。
T2 隐约记得是线段树,具体想不起来了。发现 \(d=10^9\) 可以离线,线段树合并。
T3 先想了想 DP,发现不好去重,于是掉进序列自动机的大坑再也没出来。画了一页草稿纸只找到了一个 \(O(|S|L^2\log|S|)\) 的正确性不会证的做法。在想题的 1h 中这个题大概就废了 30min。
T1 写完暴力肉眼观察,想到把第二维改成线段树,以 \(a[i]\) 分界,变成区间查询 \(\max\),区间取 \(\max\),写完过不了样例,发现离散化锅了,后来顿悟 \(b[i]+1..a[i]\) 是区间加,发现自己不会推 \(\text{max,add}\) 两个懒标记,胡了半天也过不去,此时已经 8.20 了。
赶紧上了个厕所开始写 T2 T3 的暴力,T2 的部分分也没顾上写就 9.00 了。。。当时以为 9.20 结束,就很慌(T1 还处于爆 \(0\) 状态),想了想决定冲 T1,改了一堆地方最终在不到 9.20 过了样例开拍(但是以为懒标记推对了,考后发现是区间取 \(\max\) 改成了单点)。交题的时候发现 9.40 结束?!又开始 Rush T2 的部分分,仔细一想子树拉到 dfs 序上就变成了《HH的项链》,但是忘记了 BIT 的做法,于是大力卡常写了莫队,好在写完了。
res
rk3 90+50+50
T1 离散化出锅挂了 10pts,只能说数据水
T2 大力卡常能过随机数据的 10pts,小亏
rk1 杨卓凡 100+50+100
rk5 马瑞暄 60+60+50
队长快跑
线段树优化 DP
考场 shit
const int N = 1e5+5;
int n,a[N],b[N];
int mx,ans,lsh[N*2];
#define ls (u<<1)
#define rs (u<<1|1)
struct Seg {
int l,r,mx,add,lt;
} t[N*8];
void up(int u) { t[u].mx = max(t[ls].mx,t[rs].mx); }
void addd(int x,int u) { t[u].mx += x, t[u].lt += x, t[u].add += x; }
void maxx(int x,int u) { t[u].mx = max(t[u].mx,x), t[u].lt = max(t[u].lt,x); }
void down(int u) {
addd(t[u].add,ls), addd(t[u].add,rs);
maxx(t[u].lt,ls), maxx(t[u].lt,rs);
t[u].lt = t[u].add = 0;
}
void build(int u,int l,int r) {
t[u].l = l, t[u].r = r;
if( l == r ) return;
int mid = l+r>>1;
build(ls,l,mid), build(rs,mid+1,r);
}
int query(int u,int l,int r) {
if( l <= t[u].l && t[u].r <= r ) return t[u].mx;
down(u);
int res = 0;
if( l <= t[ls].r ) res = query(ls,l,r);
if( t[rs].l <= r ) res = max(res,query(rs,l,r));
return res;
}
void add(int u,int l,int r) {
if( l <= t[u].l && t[u].r <= r ) { addd(1,u); return; }
// if( t[u].l == t[u].r ) { ++t[u].mx; return; }
down(u);
if( l <= t[ls].r ) add(ls,l,r);
if( t[rs].l <= r ) add(rs,l,r);
up(u);
}
void modify(int u,int l,int r,int x) {
if( l <= t[u].l && t[u].r <= r ) { maxx(x,u); return; }
down(u);
if( l <= t[ls].r ) modify(ls,l,r,x);
if( t[rs].l <= r ) modify(rs,l,r,x);
up(u);
}
#undef ls
#undef rs
/*
void debug() {
For(i,1,mx) printf("%d ",query(1,i,i));
putchar(10);
// For(i,1,n*8) if( t[i].l )
// printf("> [%d,%d] %d\n",t[i].l,t[i].r,t[i].mx);
}
*/
signed main() {
// freopen("a.in","r",stdin);
// freopen("a.out","w",stdout);
read(n);
For(i,1,n) read(a[i],b[i]), lsh[i] = a[i], lsh[i+n] = b[i];
sort(lsh+1,lsh+n*2+1);
mx = unique(lsh+1,lsh+n*2+1)-lsh-1;
For(i,1,n)
a[i] = lower_bound(lsh+1,lsh+mx+1,a[i])-lsh,
b[i] = lower_bound(lsh+1,lsh+mx+1,b[i])-lsh;
build(1,1,mx);
// debug();
For(i,1,n) {
if( b[i] < a[i] ) {
add(1,b[i]+1,a[i]);
if( a[i] < mx ) modify(1,a[i],a[i],query(1,a[i]+1,mx)+1);
} else if( b[i] < mx ) modify(1,a[i],a[i],query(1,b[i]+1,mx)+1);
// debug();
}
printf("%d",t[1].mx);
return 0;
}
影魔
先说在线做法:
考虑没有深度限制,那就找到每个点 \(u\) 在 dfs 序上同色的前驱 \(pre\)、后继 \(suf\),然后树上差分+容斥 \(u+1,lca(pre,u)-1,lca(u,suf)-1,lca(pre,suf)+1\),线段树维护子树和。
有深度限制就按深度插入主席树。每个版本上要修改多次,注意细节。
code
const int N = 1e5+5;
int n,m,fa[N],col[N];
vector<int> to[N];
int ind,mxdep,dep[N],siz[N],son[N],top[N],dfn[N],id[N],rt[N];
queue<int> que;
set<int> st[N];
void dfs1(int u) {
dep[u] = dep[fa[u]]+1, mxdep = max(mxdep,dep[u]);
siz[u] = 1;
for(int v : to[u]) {
dfs1(v);
siz[u] += siz[v];
if( siz[v] > siz[son[u]] ) son[u] = v;
}
}
void dfs2(int u,int tp) {
top[u] = tp, dfn[u] = ++ind, id[ind] = u;
if( son[u] ) dfs2(son[u],tp);
for(int v : to[u]) if( v != son[u] ) dfs2(v,v);
}
int lca(int u,int v) {
while( top[u] != top[v] ) {
if( dep[top[u]] < dep[top[v]] ) swap(u,v);
u = fa[top[u]];
}
return dep[u]<dep[v] ? u : v;
}
#define mid ((l+r)>>1)
class HJT {
private:
struct Seg {
int ch[2],siz;
#define ls(x) t[x].ch[0]
#define rs(x) t[x].ch[1]
} t[N*68];
public:
void insert(int u,int &v,int l,int r,int p,int x) {
t[ v=++ind ] = t[u], t[v].siz += x;
if( l == r ) return;
if( p <= mid ) insert(ls(u),ls(v),l,mid,p,x);
else insert(rs(u),rs(v),mid+1,r,p,x);
}
int query(int u,int l,int r,int ql,int qr) {
if( !u || (ql <= l && r <= qr) ) return t[u].siz;
int res=0;
if( ql <= mid ) res = query(ls(u),l,mid,ql,qr);
if( mid < qr ) res += query(rs(u),mid+1,r,ql,qr);
return res;
}
} hjt;
#undef mid
signed main() {
read(n,m);
For(i,1,n) read(col[i]);
For(i,2,n) {
read(fa[i]);
to[fa[i]].pb(i);
}
dfs1(1), dfs2(1,1);
ind = 0;
que.push(1);
while( !que.empty() ) {
int u = que.front(), pre=0, suf=0; que.pop();
auto it = st[col[u]].upper_bound(dfn[u]);
if( it != st[col[u]].end() ) suf = id[*it];
if( it != st[col[u]].begin() ) pre = id[*--it];
if( !rt[dep[u]] ) hjt.insert(rt[dep[u]-1],rt[dep[u]],1,n,dfn[u],1);
else hjt.insert(rt[dep[u]],rt[dep[u]],1,n,dfn[u],1);
if( pre ) hjt.insert(rt[dep[u]],rt[dep[u]],1,n,dfn[lca(pre,u)],-1);
if( suf ) hjt.insert(rt[dep[u]],rt[dep[u]],1,n,dfn[lca(u,suf)],-1);
if( pre && suf )
hjt.insert(rt[dep[u]],rt[dep[u]],1,n,dfn[lca(pre,suf)],1);
st[col[u]].insert(dfn[u]);
for(int v : to[u]) que.push(v);
}
while( m-- ) {
int u,d; read(u,d);
printf("%d\n",
hjt.query(rt[min(dep[u]+d,mxdep)],1,n,dfn[u],dfn[u]+siz[u]-1));
}
return 0;
}
但这个题没有强制在线,我们就可以离线水过去,而且时间、代码复杂度吊打在线做法。
这个离线做法的统计答案部分与树上逆序对有点像。
抛硬币
出题人说是“⼀道送温暖的⼩⽔题”,本场最简单的题。。。
设 \(f[i,j]\) 为前 \(i\) 个字符中长度为 \(j\) 的本质不同子序列数。显然有 \(f[i,j]=f[i-1,j]+f[i-1,j-1]\)。问题在于会算重,那么找到最大的 \(lst\) 使 \(lst<i,s[lst]=s[i]\),算重的部分即为 \(f[lst-1,j-1]\)
code
const int N = 3005, mod = 998244353;
int l;
char s[N];
int n,lst[26],f[N][N];
signed main() {
scanf("%s%d",s+1,&l); n = strlen(s+1);
For(i,0,n) f[i][0] = 1;
For(i,1,n) {
int x = s[i]-'a';
For(j,1,l) {
f[i][j] = (f[i-1][j] + f[i-1][j-1]) %mod;
if( lst[x] ) f[i][j] = (f[i][j] - f[lst[x]-1][j-1] +mod) %mod;
}
lst[x] = i;
}
printf("%d",f[n][l]);
return 0;
}