反思
A
没有想到维护 l i m lim lim 个单调栈的做法,感觉比较难想,之前没见过很难想出来,感觉还是见多识广
B
没想到 d p dp dp 的做法,感觉很妙
C
考试的时候死磕了 3 h 3h 3h 的 T 3 T3 T3,最后别人跟我讲假了,感觉不能一直弄一道题, T 2 T2 T2 比 T 3 T3 T3 感觉还是简单一些的
D
考试的时候没看完题感觉有点失败,感觉 T 4 T4 T4 还是比较板的一道题
题解
A
好题!!!
我们考虑
l
i
m
lim
lim 的范围很小,所以考虑维护
l
i
m
lim
lim 个单调栈,第
x
x
x 个单调栈里维护当前在
i
i
i 之后的
j
j
j 需要找到
a
n
s
j
,
x
ans_{j,x}
ansj,x 的
j
j
j,即后面需要找前面第
x
x
x 个比它大的位置集合
然后直接单调栈维护,注意一下加入顺序,需要保证加完之后栈仍是单调递减的
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N=3000100,NN=10000100;
int n,lim,type,a[NN];
int stk2[NN],tp,ans[NN];
int stk[5][N],top[5],tans[N][5],tmp[6][N],TP[6];
inline int read(){
int FF=0,RR=1;
char ch=getchar();
for(;!isdigit(ch);ch=getchar()) if(ch=='-') RR=-1;
for(;isdigit(ch);ch=getchar()) FF=(FF<<1)+(FF<<3)+ch-48;
return FF*RR;
}
void solve(int x,int numb){
while(top[numb]&&a[x]>=a[stk[numb][top[numb]]]){
tans[stk[numb][top[numb]]][numb]=x;
tmp[numb+1][++TP[numb+1]]=stk[numb][top[numb]];
top[numb]--;
}
}
void insert(int x){
for(int i=TP[x];i;i--) stk[x][++top[x]]=tmp[x][i];
}
int main(){
freopen("kth.in","r",stdin);
freopen("kth.out","w",stdout);
n=read(),lim=read(),type=read();
for(int i=1;i<=n;i++) a[i]=read();
if(lim==1){
for(int i=1;i<=n;i++){
while(tp&&a[i]>a[stk2[tp]]) tp--;
ans[i]=stk2[tp],stk2[++tp]=i;
}
if(!type)
for(int i=1;i<=n;i++) printf("%d\n",ans[i]);
else{
LL ANS=0;
for(int i=1;i<=n;i++) ANS^=1ll*ans[i]*i;
printf("%lld\n",ANS);
}
exit(0);
}
for(int i=n;i;i--){
for(int j=0;j<=lim;j++) TP[j]=0;
for(int j=0;j<lim;j++) solve(i,j);
stk[0][++top[0]]=i;
for(int j=1;j<lim;j++) insert(j);
}
if(!type)
for(int i=1;i<=n;i++){
for(int j=0;j<lim;j++) printf("%d ",tans[i][j]);
puts("");
}
else{
LL ANS=0;
for(int i=1;i<=n;i++) for(int j=0;j<lim;j++) ANS^=1ll*tans[i][j]*i*(j+1);
printf("%lld\n",ANS);
}
fprintf(stderr,"%d ms\n",int(1e3*clock()/CLOCKS_PER_SEC));
return 0;
}
B
我不知道如何才能想到这个
d
p
dp
dp 的
把过程倒过来,变成从叶子往根流
令
f
u
,
i
f_{u,i}
fu,i 为在
u
u
u 的子树中
i
i
i 时刻最多有多少水可以从叶子流到
u
u
u,转移很
s
i
m
p
l
e
simple
simple,发现对于
t
>
n
t>n
t>n 时流量即为
f
1
,
n
f_{1,n}
f1,n,求答案很
s
i
m
p
l
e
simple
simple
时间复杂度单次
O
(
n
2
)
O(n^2)
O(n2)
(有
O
(
n
l
o
g
n
)
O(nlogn)
O(nlogn) 的做法)
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N=1100,inf=1e9+5;
int n,k,c[N],dp[N][N];
int e[N],ne[N],h[N],idx;
inline int read(){
int FF=0,RR=1;
char ch=getchar();
for(;!isdigit(ch);ch=getchar()) if(ch=='-') RR=-1;
for(;isdigit(ch);ch=getchar()) FF=(FF<<1)+(FF<<3)+ch-48;
return FF*RR;
}
void dfs(int u){
for(int i=0;i<=n;i++) dp[u][i]=0;
bool leaf=1;
for(int i=h[u];~i;i=ne[i]){
int v=e[i];dfs(v);leaf=0;
for(int j=1;j<=n;j++){
dp[u][j]+=min(c[v],dp[v][j-1]);
if(dp[u][j]>inf) dp[u][j]=inf;
}
}
if(leaf) for(int i=0;i<=n;i++) dp[u][i]=inf;
}
void add(int x,int y){ e[idx]=y,ne[idx]=h[x],h[x]=idx++;}
void work(){
n=read(),k=read();
idx=0;
for(int i=1;i<=n;i++) h[i]=-1;
for(int i=2;i<=n;i++){
int fa=read();add(fa,i);
}
for(int i=2;i<=n;i++) c[i]=read();
dfs(1);
int res=0,ans=-1;
for(int i=1;i<=n;i++){
res+=dp[1][i];
if(res>=k){ ans=i;break;}
}
if(ans==-1){
int t=(k-res-1)/dp[1][n]+1;
ans=n+t;
}
printf("%d\n",ans);
}
int main(){
freopen("tree.in","r",stdin);
freopen("tree.out","w",stdout);
int T=read();
while(T--) work();
fprintf(stderr,"%d ms\n",int(1e3*clock()/CLOCKS_PER_SEC));
return 0;
}
C
CF原题的加强版(不过加强了很多)
沿用
C
F
CF
CF 原题的套路,考虑对于每一层记录下一层的逆序对和顺序对数
考虑在分治树上建出线段树
那么修改就是把一个区间
[
L
,
R
]
[L,R]
[L,R] 中所有深度
>
q
>q
>q 的点全部交换左右儿子,且交换顺序对合逆序对数
这个操作是可以用线段树做的
具体来说,我们对线段树的每个结点维护这个节点的子树中每层的和,然后修改时暴力修改,同时打
t
a
g
tag
tag 也是简单的
时间复杂度
O
(
n
l
o
g
n
+
m
l
o
g
2
n
)
O(nlogn+mlog^2n)
O(nlogn+mlog2n),因为一次
p
u
s
h
d
o
w
n
pushdown
pushdown 需要
O
(
l
o
g
n
)
O(logn)
O(logn)
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N=(1<<20)+100;
struct Node{
int lc,rc,tag;
LL sum[22][2];
}seg[N<<2];
int n,m,a[N],b[N],depth[N<<2];
inline int read(){
int FF=0,RR=1;
char ch=getchar();
for(;!isdigit(ch);ch=getchar()) if(ch=='-') RR=-1;
for(;isdigit(ch);ch=getchar()) FF=(FF<<1)+(FF<<3)+ch-48;
return FF*RR;
}
void cdq(int l,int r,int x){
depth[x]=depth[x>>1]+1;
if(l==r) return;
int mid=(l+r)>>1;
seg[x].lc=x<<1,seg[x].rc=x<<1^1;
cdq(l,mid,x<<1),cdq(mid+1,r,x<<1^1);
for(int i=1;i<=20;i++) for(int t:{0,1}) seg[x].sum[i][t]=seg[x<<1].sum[i][t]+seg[x<<1^1].sum[i][t];
for(int i=mid+1,j=l;i<=r;i++){
while(j<=mid&&a[j]<=a[i]) j++;
seg[x].sum[depth[x]][0]+=mid-j+1;
}
for(int i=l,j=mid+1;i<=mid;i++){
while(j<=r&&a[j]<=a[i]) j++;
seg[x].sum[depth[x]][1]+=r-j+1;
}
int j=mid+1,cnt=l;
for(int i=l;i<=mid;i++){
while(j<=r&&a[j]<=a[i]) b[cnt++]=a[j],j++;
b[cnt++]=a[i];
}
while(j<=r) b[cnt++]=a[j],j++;
for(int i=l;i<=r;i++) a[i]=b[i];
}
void make_tag(int x,int dep){
for(int i=dep;i<=n;i++){
swap(seg[x].sum[i][0],seg[x].sum[i][1]);
seg[x].tag^=1<<i;
}
}
void pushdown(int x){
if(seg[x].tag>>depth[x]&1) swap(seg[x].lc,seg[x].rc);
for(int i=1;i<=20;i++)
if(seg[x].tag>>i&1){
swap(seg[seg[x].lc].sum[i][0],seg[seg[x].lc].sum[i][1]);
swap(seg[seg[x].rc].sum[i][0],seg[seg[x].rc].sum[i][1]);
}
seg[seg[x].lc].tag^=seg[x].tag,seg[seg[x].rc].tag^=seg[x].tag;
seg[x].tag=0;
}
void modify(int l,int r,int x,int L,int R,int dep){
if(L<=l&&r<=R){ make_tag(x,dep);return;}
pushdown(x);
int mid=(l+r)>>1;
if(mid>=L) modify(l,mid,seg[x].lc,L,R,dep);
if(mid<R) modify(mid+1,r,seg[x].rc,L,R,dep);
for(int i=depth[x]+1;i<=n;i++) for(auto t:{0,1}) seg[x].sum[i][t]=seg[seg[x].lc].sum[i][t]+seg[seg[x].rc].sum[i][t];
}
int main(){
freopen("pair.in","r",stdin);
freopen("pair.out","w",stdout);
n=read(),m=read();
for(int i=1;i<=1<<n;i++) a[i]=read();
cdq(1,1<<n,1);
while(m--){
int q=read(),l=read(),r=read();
l=(l-1)*(1<<(n-q))+1,r=r*(1<<(n-q));
modify(1,1<<n,1,l,r,q+1);
LL ans=0;
for(int i=1;i<=n;i++) ans+=seg[1].sum[i][0];
printf("%lld\n",ans);
}
fprintf(stderr,"%d ms\n",int(1e3*clock()/CLOCKS_PER_SEC));
return 0;
}
D
有些套路的一道题
首先看到
k
k
k 次方,想到转下降幂
根据
n
k
=
∑
t
=
0
k
t
!
{
k
t
}
g
l
,
t
n^k=\sum\limits_{t=0}^kt!{k\brace t}g_{l,t}
nk=t=0∑kt!{tk}gl,t
其中
g
(
l
,
t
)
g(l,t)
g(l,t) 为从
x
x
x 到
1
1
1 走过
l
l
l 条边,从中选出
t
t
t 条边的期望
于是我们可以令
f
u
,
i
f_{u,i}
fu,i 表示从
u
u
u 到
1
1
1 走过了边中选出
i
i
i 条边的期望,这样我们就 不用考虑走过多少条边了
然后发现
f
u
,
i
f_{u,i}
fu,i 是由
f
?
,
i
−
1
f_{?,i-1}
f?,i−1 和
f
?
,
i
f_{?,i}
f?,i 共同转移过来的,不难想到树上高斯消元,直接推一推式子即可
时间复杂度
O
(
n
k
+
k
2
)
O(nk+k^2)
O(nk+k2)
#include <bits/stdc++.h>
using namespace std;
const int N=100100,K=1100,P=998244353;
int n,k,p[N],ans[N],s2[K][K];
int e[N<<1],ne[N<<1],h[N],idx;
int f[N],g[N];
inline int read(){
int FF=0,RR=1;
char ch=getchar();
for(;!isdigit(ch);ch=getchar()) if(ch=='-') RR=-1;
for(;isdigit(ch);ch=getchar()) FF=(FF<<1)+(FF<<3)+ch-48;
return FF*RR;
}
int qmi(int a,int b){
int res=1;
for(;b;b>>=1){
if(b&1) res=1ll*res*a%P;
a=1ll*a*a%P;
}
return res;
}
int A[N],B[N];
inline void inc(int &x,int y){ x+=y;if(x>=P) x-=P;}
void dfs(int u,int fa){
int deg=0,coef1=0,coef2=0;
for(int i=h[u];~i;i=ne[i]){
int v=e[i];
deg++,inc(coef2,g[v]);
if(v==fa) continue;
dfs(v,u);
inc(coef1,A[v]),inc(coef2,B[v]);
}
int ivdeg=1ll*qmi(deg,P-2)*(1-p[u]+P)%P;
coef1=(1ll*coef1*ivdeg+p[u])%P;
coef1=(P+1-coef1)%P,coef1=qmi(coef1,P-2);
A[u]=1ll*coef1*ivdeg%P;
B[u]=(1ll*coef2*ivdeg+1ll*p[u]*g[u])%P*coef1%P;
}
void dfs2(int u,int fa){
for(int i=h[u];~i;i=ne[i]){
int v=e[i];if(v==fa) continue;
f[v]=(1ll*f[u]*A[v]+B[v])%P;
dfs2(v,u);
}
}
void add(int x,int y){ e[idx]=y,ne[idx]=h[x],h[x]=idx++;}
int main(){
freopen("raiden.in","r",stdin);
freopen("raiden.out","w",stdout);
n=read(),k=read();
s2[0][0]=1;
for(int i=1;i<=k;i++) for(int j=1;j<=i;j++) s2[i][j]=(s2[i-1][j-1]+1ll*s2[i-1][j]*j)%P;
memset(h,-1,sizeof(h));
for(int i=1;i<n;i++){
int x=read(),y=read();
add(x,y),add(y,x);
}
int iv=qmi((int)1e6,P-2);
for(int i=2;i<=n;i++) p[i]=read(),p[i]=1ll*p[i]*iv%P;
for(int i=0,fac=1;i<=k;i++,fac=1ll*fac*i%P){
for(int j=1;j<=n;j++) g[j]=f[j];
f[1]=(!i);
dfs(1,-1),dfs2(1,-1);
for(int j=1;j<=n;j++) inc(ans[j],1ll*s2[k][i]*fac%P*f[j]%P);
}
for(int i=2;i<=n;i++) printf("%d\n",ans[i]);
fprintf(stderr,"%d ms\n",int(1e3*clock()/CLOCKS_PER_SEC));
return 0;
}